源码分析
为了清楚,摘抄了核心代码,一次只分析一方面.
这次从上次发的源码开始分析(见: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级别,好象有脉络可寻.
希望对以后自己封装类会起到指导作用.