Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1032974
  • 博文数量: 177
  • 博客积分: 3629
  • 博客等级: 中校
  • 技术积分: 1839
  • 用 户 组: 普通用户
  • 注册时间: 2005-02-23 21:21
文章分类

全部博文(177)

文章存档

2021年(1)

2020年(5)

2019年(4)

2018年(7)

2017年(1)

2016年(4)

2014年(1)

2013年(8)

2012年(10)

2011年(50)

2009年(12)

2008年(10)

2006年(56)

2005年(8)

分类: C/C++

2006-04-07 22:05:36

源码分析
为了清楚,摘抄了核心代码,一次只分析一方面.
这次从上次发的源码开始分析(见:Sockets库尝试之一)
 
主要是按程序流程顺序写写几个有关的关键函数

 
1
1.1 基础:
1.1.1 connect()调用在异步模式下,正常返回值是-1,同时生成WSAEWOULDLOCK错误,我们只需在需要的时候查询此SOCKET的状态就可以知道连接是否可用.
1.1.2 下面两行代码就是查询连接是否可用的.
int err;
getsockopt(m_socket, SOL_SOCKET, SO_ERROR, (char *)&err, &errlen);
1.1.3 如果上面的代码查询结果说明连接可用,则SetConnecting(true);其它任何情况下都SetConnecting(false),这一点在2 SocketHandler::Select()里很重要.其实就是一bool值而已.
 
1.2 代码
下面的代码其实是调用 的TcpSocket::Open:
mydd.Open("umn.dl.sourceforge.net",80);
bool TcpSocket::Open(ipaddr_t ip,port_t port,bool skip_socks)
{
 SetConnecting(false);  //this flag will control fd_set's
 SOCKET s = CreateSocket(AF_INET, SOCK_STREAM, "tcp");
 SetNonblocking(true, s); //设定非阻塞模式,否则connect可能会阻塞线程
 ......省略一些sa的初始化
 int n = connect(s, (struct sockaddr *)&sa, sa_len); //看windows网络编程第二版第100页表5.2说明的与这有关,(WSAEWOULDLOCK错误)
 SetConnecting( true );
}
 
2
2.1 基础:
2.1.1 select模式是windows 套接字I/O模型中的一种,具体请看 windows网络编程 第二版一书5。2节
2.1.2 新类型:
typedef std::list socket_v;
这是为了管理多个SOCKET而用的。
2.1.3 obuf是一个类,管理一个缓冲区。有空专门写它。
2.2 代码:
int SocketHandler::Select(struct timeval *tsel)
{
 int n = select( (int)(m_maxsock + 1),&rfds,&wfds,&efds,tsel);
 if (n > 0)
 {
  for (socket_v::iterator it2 = m_fds.begin(); it2 != m_fds.end() && n; it2++) //下面有n--,如果只一个连接,只循环一次
  {
   SOCKET i = *it2;
   if (FD_ISSET(i, &rfds))  //是否可读
   {
    Socket *p = GetSocket(i); //m_sockets[i]; 注意这是Socket *而不是HttpGetSocket *之类
    if (!p)
    {
     n--;
     continue;
    }
    // new SSL negotiate method
    if (p -> IsSSLNegotiate())         /*SSL与普通socket严格分开*/
    {
     p -> SSLNegotiate();
    }
    else
    {
     // LockWrite (save total output buffer size)
     // Sockets with write lock won't call OnWrite in SendBuf 注意这儿
     // That will happen in UnlockWrite, if necessary
     p -> OnRead();        /*回调函数,是Socket的OnRead(),其实是TcpSocket的OnRead() */
     if (p -> Socks4())                /*和LineProtocol做判断*/
     {
      bool need_more = false;
      TcpSocket *tcp = dynamic_cast(p);
      while (tcp && tcp -> GetInputLength() && !need_more && !p -> CloseAndDelete())
      {
       need_more = p -> OnSocks4Read();
      }
     }
     else
     {
      if (p -> LineProtocol())      /*LineProtocol表示命令行交互式登录功能是否启用*/
      {
       p -> ReadLine();
      }
//       p -> Touch();
     }
     // UnlockWrite (call OnWrite if saved size == 0 && total output buffer size > 0)
    }
    n--;
   }
   if (FD_ISSET(i, &wfds))  //第一次connect成功后,总会执行到这儿.为真值.往下有写入时的回调函数.
   {       //因为偶的程序只有一个GET连接,所以只有一个机会能运行到这儿,以后就只能rfds(只读)为真了.
    Socket *p = GetSocket(i); //m_sockets[i];
    if (!p)
    {
     n--;
     continue;
    }
    // new SSL negotiate method
    if (p -> IsSSLNegotiate())
    {
     p -> SSLNegotiate();
    }
    else
    {      //Connetcing的状态只在TcpSocket::Open()函数里被设为true,只要connect正常异步返回-1,就SetConnecting(true);
     if (p -> Connecting()) //如果为真,说明connect()已初始化,但仍需进行下一步检查连接状态.这是异步编程的特点
     {
      if (p -> CheckConnect()) //如为真,说明连接已成功,注:此函数如返回真会改变m_bConnecting状态(false)
      {
       p -> SetCallOnConnect(); //连接成功,允许调用回调函数OnConnect(),此处决定464行是否为真  line 412
      }
      else
      {
       TcpSocket *tcp = dynamic_cast(p);
       // failed
       if (p -> Socks4())
       {
        p -> OnSocks4ConnectFailed();
       }
       else
       if (tcp && (tcp -> GetConnectionRetry() == -1 ||
        (tcp -> GetConnectionRetry() &&
         tcp -> GetConnectionRetries() < tcp -> GetConnectionRetry() )))
       {
        // even though the connection failed at once, only retry after
        // the connection timeout.
        // should we even try to connect again, when CheckConnect returns
        // false it's because of a connection error - not a timeout...
       }
       else
       {
//         LogError(p, "connect failed", Errno, StrError(Errno), LOG_LEVEL_FATAL);
        p -> SetCloseAndDelete( true );
        p -> OnConnectFailed();
       }
      }
/////////////////////////////////p -> Touch();
     }
     else    //当p->Connecting()为false时才会执行到这,为false并不说明连接失败.能执行到这儿只是说明464行所在代码段已执行过了.
     {
      p -> OnWrite();
//        p -> Touch();
     }
    }
    n--;
   }
   if (FD_ISSET(i, &efds))
   {
    Socket *p = GetSocket(i); //m_sockets[i];
    if (!p)
    {
     n--;
     continue;
    }
    p -> OnException();
    n--;
   }
  }
 }
 
//line 464: 由412行决定是否为真,
 if (m_fds_callonconnect.size())
 {            //第一次select总是能执行到这儿
  socket_v tmp = m_fds_callonconnect;
  for (socket_v::iterator it = tmp.begin(); it != tmp.end(); it++)
  {
   Socket *p = GetSocket(*it); //m_sockets[*it];
   if (p)
   {
    if (p -> CallOnConnect() && p -> Ready() )
    {
     if (p -> IsSSL()) // SSL Enabled socket
      p -> OnSSLConnect();
     else
     if (p -> Socks4())
      p -> OnSocks4Connect();
     else
     {
      TcpSocket *tcp = dynamic_cast(p);
      if (tcp)
      {
       p -> SetConnected();
       if (tcp -> GetOutputLength())
       {
        p -> OnWrite();          /*这里发送的是obuf里的数据*/
       }
      }
      if (tcp && tcp -> IsReconnect())
       p -> OnReconnect();
      else
      {
       LogError(p, "Calling OnConnect", 0, "Because CallOnConnect", LOG_LEVEL_INFO);
       p -> OnConnect();                     /*唯一的调用OnConnect*/
      }
     }                               /*实际上,只有一次机会执行下面代码,也就是第一次可写入的socket调用时*/
     p -> SetCallOnConnect( false ); /*执行完后此处设为false,保证不重复执行.但上面412行设为true,由它控制是否执行本段代码*/
    }
   }
  }
 }
 
}
 
总结一下:
select函数是关键,主要是执行select调用,并根据连接状态选择不同的回调函数.
其中用了很多的变量,用来控制流程.如m_fds之类.
还有一些数据结构,如map,list等STL上的东东.来分别保存不同状态下的SOCKET和Socket类指针.
 
Socket类定义接口,是基类.
TcpSocket类实现了最重要的两个接口: OnWrite,OnRead
HttpSocket,HttpGetSocket之类封装得更多.
 
仔细观察Socket,TcpSocket,想想这个结构为什么会出现在这儿,而不是别的类中,从Tcp级别,从Socket级别,好象有脉络可寻.
希望对以后自己封装类会起到指导作用.
 
阅读(2220) | 评论(0) | 转发(0) |
0

上一篇:Sockets库尝试之一

下一篇:Socket库分析之三

给主人留下些什么吧!~~