Chinaunix首页 | 论坛 | 博客
  • 博客访问: 4943421
  • 博文数量: 1696
  • 博客积分: 10870
  • 博客等级: 上将
  • 技术积分: 18357
  • 用 户 组: 普通用户
  • 注册时间: 2007-03-30 15:16
文章分类
文章存档

2017年(1)

2016年(1)

2015年(1)

2013年(1)

2012年(43)

2011年(17)

2010年(828)

2009年(568)

2008年(185)

2007年(51)

分类: C/C++

2010-03-21 21:59:49

服务线程苏醒后,调用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 m_userids;

 // hammering这块可以先不管
 // Check if remote IP is blocked due to hammering
 std::map::iterator iter = m_antiHammerInfo.find(sockAddr.sin_addr.s_addr);
 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::iterator iter = m_ParsedWelcomeMessage.begin(); iter != m_ParsedWelcomeMessage.end(); iter++)
  // 发送给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 notifications;
   // 与pThread中原有的m_pendingNotifications进行交换,
   // 等于是处理了pThread中所有的notifications,然后再删掉
   pThread->GetNotifications(notifications);
   for (std::list::const_iterator iter = notifications.begin(); iter != notifications.end(); iter++)
    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(lParam);
  ...
 }
 // 发送的参数是FSM_CONNECTIONDATA在这里处理
 else if (wParam == FSM_CONNECTIONDATA) // 跟connection相关的消息
 {
  // lParam这里t_connop类型的,与AddNewSocket中的对应
  t_connop *pConnOp = reinterpret_cast(lParam); 
  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了。

阅读(1773) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~