FileZilla 源代碼分析6
FileZilla是一種快速、可信賴的FTP客戶端以及服務(wù)器端開放源代碼程式,具有多種特色、直覺的接口。本文就給大家分析下FileZilla的源代碼。
CListenSocket是CAsyncSocketEx類的子類,在啟動的時候用來監(jiān)聽21端口。
pListenSocket->Create(nPort, SOCK_STREAM, FD_ACCEPT, NULL)
可見,CListenSocket只處理FD_ACCEPT消息。
看一下:
void CListenSocket::OnAccept(int nErrorCode)
{
CAsyncSocketEx socket;
if (!Accept(socket)) // 這里調(diào)用了win API: accept方法,建立了一個新連接,socket就這個新連接的SOCKET
{
int nError = WSAGetLastError();
CStdString str;
str.Format(_T("Failure in CListenSocket::OnAccept(%d) - call to CAsyncSocketEx::Accept failed, errorcode
%d"), nErrorCode, nError);
SendStatus(str, 1);
SendStatus(_T("If you use a firewall, please check your firewall configuration"), 1);
return;
}
// 權(quán)限檢查,先不管
if (!AccessAllowed(socket))
{
CStdStringA str = "550 No connections allowed from your IP\r\n";
socket.Send(str, str.GetLength());
return;
}
// 檢查FileZilla Server是否處于鎖定狀態(tài),即不允許建立新連接
if (m_bLocked)
{
CStdStringA str = "421 Server is locked, please try again later.\r\n";
socket.Send(str, str.GetLength());
return;
}
// 下面從可用的線程中,找出目前負(fù)荷最小的線程,即線程中負(fù)責(zé)的connection最少的線程
int minnum = 255*255*255;
CServerThread *pBestThread=0;;
for (std::list
{
int num=(*iter)->GetNumConnections();
if (num
{
minnum=num;
pBestThread=*iter;
if (!num)
break;
}
}
if (!pBestThread)
{
char str[] = "421 Server offline.";
socket.Send(str, strlen(str)+1);
socket.Close();
return;
}
/* Disable Nagle algorithm. Most of the time single short strings get
* transferred over the control connection. Waiting for additional data
* where there will be most likely none affects performance.
*/
BOOL value = TRUE;
socket.SetSockOpt(TCP_NODELAY, &value, sizeof(value), IPPROTO_TCP); // 設(shè)置不使用Nagle算法,參見TCP協(xié)議Nagle算法部分
SOCKET sockethandle = socket.Detach();
pBestThread->AddSocket(sockethandle, m_ssl); // 轉(zhuǎn)交服務(wù)線程來處理
CAsyncSocketEx::OnAccept(nErrorCode); // 父類的缺省處理為空
}
可見,CListenSocket::OnAccept主要工作是
1、創(chuàng)建一個socket來接收新的客戶端連接
2、進(jìn)行一些檢查設(shè)置,如權(quán)限檢查,Nagle算法設(shè)置等
3、找到一個負(fù)荷最小的后臺服務(wù)線程,由交那個線程處理
為了更清楚服務(wù)線程CServerThread的機制,先看回顧一下當(dāng)時這個服務(wù)線程是如何被創(chuàng)建的。
CServer類的Create()片斷:
for (int i = 0; i < num; i++) // 這里num是需要創(chuàng)建的服務(wù)線程的數(shù)量
{
int index = GetNextThreadNotificationID(); // 得到這個線程的一個標(biāo)識,即在線程數(shù)組std::vector
m_ThreadNotificationIDs中的index
CServerThread *pThread = new CServerThread(WM_FILEZILLA_SERVERMSG + index);
m_ThreadNotificationIDs[index] = pThread;
// If the CREATE_SUSPENDED flag is specified, the thread is created in a suspended state,
// and will not run until the ResumeThread function is called.
// If this value is zero, the thread runs immediately after creation.
if (pThread->Create(THREAD_PRIORITY_NORMAL, CREATE_SUSPENDED))
{
pThread->ResumeThread();
m_ThreadArray.push_back(pThread);
}
}
看一下pThread->Create(THREAD_PRIORITY_NORMAL, CREATE_SUSPENDED),由于CServerThread繼承于CThread,因此調(diào)用了CThread的create:
BOOL CThread::Create(int nPriority /*=THREAD_PRIORITY_NORMAL*/, DWORD dwCreateFlags /*=0*/)
{
m_hThread=CreateThread(0, 0, ThreadProc, this, dwCreateFlags, &m_dwThreadId); // 調(diào)用win api創(chuàng)建一個線程
if (!m_hThread)
{
delete this;
return FALSE;
}
::SetThreadPriority(m_hThread, nPriority);
return TRUE;
}
注意創(chuàng)建線程的時候,指定線程的初始狀態(tài)為CREATE_SUSPENDED。
創(chuàng)建成功后,調(diào)用pThread->ResumeThread():
DWORD CThread::ResumeThread()
{
// 下面使用win API:ResumeThread啟動這個線程,線程開動后, 會自動跑到ThreadProc函數(shù)(create時指定)
DWORD res=::ResumeThread(m_hThread); // 這個函數(shù)過后,有兩個線程在跑,一個是剛才的主線程,一個是剛啟動的線程
if (!m_started) // 主線程運行到這里,由于m_started還是0,所以通過下面的WaitForSingleObject,進(jìn)行了等待狀態(tài)
{
WaitForSingleObject(m_hEventStarted, INFINITE);
}
return res;
}
剛啟動的線程進(jìn)入了ThreadProc函數(shù):
DWORD WINAPI CThread::ThreadProc(LPVOID lpParameter)
{
// 在CreateThread時指定的參數(shù)LPVOID lpParameter為this,即CThread
return ((CThread *)lpParameter)->Run();
}
即運行Run方法:
DWORD CThread::Run()
{
InitInstance(); // 這里CServerThread類重寫了這個方法,因此進(jìn)入CServerThread::InitInstance(),進(jìn)行了一些內(nèi)存變量的初始
化
// The SetEvent function sets the specified event object to the signaled state.
SetEvent(m_hEventStarted); // 設(shè)置event為active,使得剛才在等待的主線程復(fù)活,繼續(xù)CServer的啟動工作
m_started = true;
MSG msg;
while (GetMessage(&msg, 0, 0, 0)) // 進(jìn)入這個線程的消息循環(huán)
{
TranslateMessage(&msg);
if (!msg.hwnd)
OnThreadMessage(msg.message, msg.wParam, msg.lParam); // 調(diào)用OnThreadMessage處理消息
DispatchMessage(&msg);
}
DWORD res=ExitInstance();
delete this;
return res;
}
可見,服務(wù)器啟動后,剛開始沒有消息時,CServerThread在GetMessage()時進(jìn)入了block狀態(tài),一旦有消息到來,這個服務(wù)線程就蘇醒,接著
處理消息。
下面回到最初的CListenSocket::OnAccept(),最后調(diào)用了pBestThread->AddSocket(sockethandle, m_ssl); // 轉(zhuǎn)交服務(wù)線程來處理
仔細(xì)看一下后臺的服務(wù)線程是如何處理消息的。
void CServerThread::AddSocket(SOCKET sockethandle, bool ssl)
{
// 調(diào)用了父類的方法
PostThreadMessage(WM_FILEZILLA_THREADMSG, ssl ? FTM_NEWSOCKET_SSL : FTM_NEWSOCKET, (LPARAM)sockethandle);
}
接著:
BOOL CThread::PostThreadMessage(UINT message, WPARAM wParam, LPARAM lParam)
{
// posts a message to the message queue of the specified thread.
BOOL res=::PostThreadMessage(m_dwThreadId, message, wParam, lParam);;
ASSERT(res);
return res;
}
PostThreadMessage是windows API,作用是把消息message發(fā)送到線程m_dwThreadId,
根據(jù)前面的代碼,在這里就是把消息WM_FILEZILLA_THREADMSG,以及參數(shù)FTM_NEWSOCKET, sockethandle發(fā)送到那個負(fù)荷最小的后臺服務(wù)線程,
由于在啟動時,那個后臺線程處于GetMessage()的block中,因此收到這個消息到,那個后臺線程蘇醒,接著調(diào)用OnThreadMessage來處理這個WM_FILEZILLA_THREADMSG消息。
通過文章完整的描述,大家應(yīng)該知道了FileZilla 源代碼,希望對大家有幫助!
【編輯推薦】