分类:
2012-05-26 22:23:26
原文地址:FileZilla FTP服务器源代码分析10 作者:sinodragon21
服务线程苏醒后,调用OnThreadMessage来处理这个WM_FILEZILLA_THREADMSG消息,参数是 FTM_NEWSOCKET, sockethandle,接着进入AddNewSocket方法,表示有一个新的客户端需要连接上来。
void CServerThread::AddNewSocket(SOCKET sockethandle, bool ssl)
{
//
首先创建了新的CControlSocket类,这也是继承于CAsyncSocketEx类的,从下面起经过一些初始化之后,就由这个
CControlSocket类来接管这个客户连接了。
CControlSocket *socket = new
CControlSocket(this);
socket->Attach(sockethandle); //
加入到FileZilla消息机制中, 建立与分发线程等之间的关系
CStdString ip;
unsigned int
port;
SOCKADDR_IN sockAddr;
memset(&sockAddr, 0,
sizeof(sockAddr));
int nSockAddrLen = sizeof(sockAddr);
BOOL
bResult = socket->GetPeerName((SOCKADDR*)&sockAddr,
&nSockAddrLen); // 获取socket客户端的信息
if (bResult)
{
port =
ntohs(sockAddr.sin_port); // 端口
ip = inet_ntoa(sockAddr.sin_addr);
// IP地址
}
else
{
socket->m_RemoteIP = _T("ip
unknown");
socket->m_userid = 0;
socket->SendStatus(_T("Can't
get remote IP, disconnected"), 1);
socket->Close();
delete
socket;
return;
}
socket->m_RemoteIP= ip;
EnterCritSection(m_GlobalThreadsync);
int
userid = CalcUserID(); // 自动为当前socket连接生成一个客户号:userID
if (userid ==
-1)
{
LeaveCritSection(m_GlobalThreadsync);
socket->m_userid
= 0;
socket->SendStatus(_T("Refusing connection, server too
busy!"), 1);
socket->Send(_T("421 Server too busy, closing
connection. Please retry later!"));
socket->Close();
delete
socket;
return;
}
socket->m_userid = userid;
t_socketdata
data;
data.pSocket = socket;
data.pThread = this;
m_userids[userid]
= data; // m_userids是static的,定义为static std::map
// hammering这块可以先不管
// Check if remote IP is blocked due to
hammering
std::map
if (iter !=
m_antiHammerInfo.end())
{
if (iter->second > 10)
socket->AntiHammerIncrease(25);
// ~6 secs delay
}
LeaveCritSection(m_GlobalThreadsync);
EnterCritSection(m_threadsync);
//
下面记录这个服务线程所处理的CControlSocket
m_LocalUserIDs[userid] = socket;
LeaveCritSection(m_threadsync);
t_connectiondata_add *conndata = new t_connectiondata_add;
t_connop
*op = new t_connop;
op->data = conndata;
op->op =
USERCONTROL_CONNOP_ADD; // 新用户连接即将连接,在CServer的OnServerMessage中要用过
op->userid
= userid;
conndata->pThread = this;
memset(&sockAddr, 0, sizeof(sockAddr));
nSockAddrLen =
sizeof(sockAddr);
bResult =
socket->GetPeerName((SOCKADDR*)&sockAddr, &nSockAddrLen);
if
(bResult)
{
conndata->port = ntohs(sockAddr.sin_port);
#ifdef
_UNICODE
_tcscpy(conndata->ip,
ConvFromLocal(inet_ntoa(sockAddr.sin_addr))); // 拷贝字符串
#else
_tcscpy(conndata->ip,
inet_ntoa(sockAddr.sin_addr));
#endif
}
// 这里往全局的hMainWnd发送消息,
// 消息的wParam类型为FSM_CONNECTIONDATA,
指示消息是跟connection相关的消息,参数是t_connop
//
这些在CServer的WindowProc中处理这个消息时用到,这个消息处理结束后,会在admin窗口的下边显示
// 类似000001
(not logged in) 127.0.0.1 的信息
SendNotification(FSM_CONNECTIONDATA,
(LPARAM)op);
if (ssl) // SSL相关, 可以先跳过
if (!socket->InitImplicitSsl())
return;
socket->AsyncSelect(FD_READ|FD_WRITE|FD_CLOSE);
// 对socket上这些event建立侦听关系
//
SendStatus最终还是调用SendNotification方法,不过发送的参数是FSM_STATUSMESSAGE,
//
因此在CServer中的处理并不一样
socket->SendStatus(_T("Connected, sending
welcome message..."), 0);
// 这时,admin窗口的下半部分会显示类似(000003) 2006-8-24 3:26:47 - (not logged in)
(127.0.0.1)> Connected, sending welcome message...
//
下面格式化欢迎信息
CStdString msg =
m_pOptions->GetOption(OPTION_WELCOMEMESSAGE);
if
(m_RawWelcomeMessage != msg)
{
m_RawWelcomeMessage = msg;
m_ParsedWelcomeMessage.clear();
msg.Replace(_T("%%"), _T("\001"));
msg.Replace(_T("%v"),
GetVersionString());
msg.Replace(_T("\001"), _T("%"));
ASSERT(msg
!= _T(""));
int oldpos = 0;
msg.Replace(_T("\r\n"),
_T("\n"));
int pos=msg.Find(_T("\n"));
CStdString line;
while
(pos!=-1)
{
ASSERT(pos);
m_ParsedWelcomeMessage.push_back(_T("220-")
+ msg.Mid(oldpos, pos-oldpos) );
oldpos=pos + 1;
pos=msg.Find(_T("\n"),
oldpos);
}
line = msg.Mid(oldpos);
if (line !=
_T(""))
m_ParsedWelcomeMessage.push_back(_T("220 ") + line);
else
{
m_ParsedWelcomeMessage.back()[3]
= 0;
}
}
// hideStatus指示这个欢迎消息要不要发给admin port
bool
hideStatus = m_pOptions->GetOptionVal(OPTION_WELCOMEMESSAGE_HIDE) !=
0;
ASSERT(!m_ParsedWelcomeMessage.empty());
for
(std::list
// 发送给socket客户, 并且发送消息到admin port上,发送的参数是FSM_STATUSMESSAGE
if
(!socket->Send(*iter, !hideStatus))
break;
//
运行到这里,客户的登录界面上、admin窗口上半部已经出现了welcome信息,类似:
// (000003) 2006-8-24
3:27:19 - (not logged in) (127.0.0.1)> 220-FileZilla Server version
0.9.18 beta
// ((000003) 2006-8-24 3:27:22 - (not logged in)
(127.0.0.1)> 220-written by Tim Kosse ()
// ((000003)
2006-8-24 3:27:29 - (not logged in) (127.0.0.1)> 220 Please visit
}
看一下发消息的过程:
void CServerThread::SendNotification(WPARAM wParam, LPARAM lParam)
{
EnterCritSection(m_threadsync);
t_Notification
notification;
notification.wParam = wParam;
notification.lParam
= lParam;
// 由于在创建这个线程时, 指定了m_nNotificationMessageI = WM_FILEZILLA_SERVERMSG +
index
// 因此在CServer的WindowProc处理中, 通过message就可以知道是哪个服务线程发来的消息
//
也就得到了这个线程的m_pendingNotifications,m_pendingNotifications有详细的wParam,
lParam,
// CServer然后再调用CServer的OnServerMessage处理这个消息
//
因此实际上PostMessage只是发了个消息,关于这个消息的具体的信息还在m_pendingNotifications中
if
(m_pendingNotifications.empty())
PostMessage(hMainWnd,
m_nNotificationMessageId, 0, 0); //
hMainWnd是全局变量,就是CServer在Create时创建的那个窗口
m_pendingNotifications.push_back(notification);
// Check if main thread can't handle number of notifications fast
enough, throttle thread if neccessary
//
下面检查m_pendingNotifications里的数量,如果太多了,就降低这个线程的优先级,
// 让这个线程运行得慢一些
if
(m_pendingNotifications.size() > 200 && m_throttled < 3)
{
SetPriority(THREAD_PRIORITY_IDLE);
m_throttled
= 3;
}
else if (m_pendingNotifications.size() > 150
&& m_throttled < 2)
{
SetPriority(THREAD_PRIORITY_LOWEST);
m_throttled
= 2;
}
else if (m_pendingNotifications.size() > 100
&& !m_throttled)
{
SetPriority(THREAD_PRIORITY_BELOW_NORMAL);
m_throttled
= 1;
}
LeaveCritSection(m_threadsync);
}
到这里我们回去看一下CServer的相关代码:
LRESULT CALLBACK CServer::WindowProc(HWND
hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
...
else
if (message >= WM_FILEZILLA_SERVERMSG) // 指定的消息种类
{
UINT
index = message - WM_FILEZILLA_SERVERMSG;
if (index >=
pServer->m_ThreadNotificationIDs.size())
return 0;
CServerThread *pThread =
pServer->m_ThreadNotificationIDs[index]; // 发这个消息的CServerThread
if
(pThread)
{
std::list
// 与pThread中原有的m_pendingNotifications进行交换,
//
等于是处理了pThread中所有的notifications,然后再删掉
pThread->GetNotifications(notifications);
for
(std::list
if
(pServer->OnServerMessage(pThread, iter->wParam, iter->lParam)
!= 0)
break;
}
return 0;
}
return
::DefWindowProc(hWnd, message, wParam, lParam);
}
处理函数:
LRESULT CServer::OnServerMessage(CServerThread* pThread, WPARAM
wParam, LPARAM lParam)
{
// 发送的参数是FSM_STATUSMESSAGE在这里处理
if
(wParam == FSM_STATUSMESSAGE) // 记录当前活动, 将活动信息在admin窗口上显示, 并且记录到log文件中
{
t_statusmsg
*msg = reinterpret_cast
...
}
//
发送的参数是FSM_CONNECTIONDATA在这里处理
else if (wParam ==
FSM_CONNECTIONDATA) // 跟connection相关的消息
{
//
lParam这里t_connop类型的,与AddNewSocket中的对应
t_connop *pConnOp =
reinterpret_cast
if (!pConnOp)
return
0;
int len;
unsigned char *buffer;
switch (pConnOp->op)
{
case USERCONTROL_CONNOP_ADD: //
新用户连接即将连接,在AddNewSocket时指定的
...
}
仔细比较一下AddNewSocket发送消息和OnServerMessage处理消息的代码,两者肯定是一一对应的。
最后看一下CControlSocket::Send的代码,在AddNewSocket中,发送客户端是些welcome信息。在发送给
客户端的过程中,要求发送的数据实际上可能没有全部发送出去,这时程序把没有发出去的数据放到内存m_pSendBuffer中,然后给
HelperWindow发送事件FD_WRITE,经过消息的分发机制后,程序最终回到CControlSocket的OnOnSend方法,在
OnOnSend中,再根据m_pSendBuffer中的数据进行重新发送。
在进行代码调试的时候,最好打开FileZilla Server
Interface.exe,仔细看一下随着每一步的进行,在admin窗口上发生了什么。
程序运行完AddNewSocket后,只是在客户端显示了welcome信息,下面用户要登录到FileZilla Server了。