Connector
Connector主要用于发起连接,并带有自动重连的功能,成员主要有一个channel_。Connector只负责建立socket连接,不负责创建TcpConnection。一般也不单独使用,作为TcpClient的成员。主动发起连接比被动接受连接要更复杂,一方面是错误处理麻烦,另一方面是要考虑重试。当socket变得可写时表明连接建立完毕。Connector的实现几个难点:
socket是一次性的,一旦出错(比如对方拒绝连接)。就无法恢复,只能关闭重来。但Connector 是可以反复使用的,因此每次尝试连接都要使用新的socket文件描述符和新的Channel对象。
int ret = sockets::connect(sockfd, serverAddr_.getSockAddrInet());
int savedErrno = (ret == 0) ? 0 : errno;
// errno本身是一个整型的全局变量。在执行出错时,具体的出错原因会被赋值到errno中。
// 通过查询errno可以确定具体的出错原因。
switch (savedErrno)
{
case 0:
case EINPROGRESS:
case EINTR:
case EISCONN:
connecting(sockfd);
break;
case执行哪句应该看break在哪里
socket可写,也不一定意味着连接已成功建立
,还需要用getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen) 再次确认一下
重试的间隔应该逐渐延长,例如0.5s, 1s, 2s, 4s,
直到30s。这会造成对象生命周期管理方面的困难,如果使用EventLoop::runAfter定时而Connector 在定时器到期之前析构了怎么办
,在Connector的析构函数中注销定时器。
要处理自连接
。出现这种情况的原因:在发起连接时,TCP/IP协议栈会选择 source IP 和
source port,在没有显示调用bind的情况下,source IP 由路由表确定,source port在选取本地未使用的port。如果目标IP正好是本机,而目标端口没有服务程序监听的话,source IP可能正好选中目标端口。此时就发生了自连接。
处理的办法是断开连接再次重连
,否则原本监听该端口的服务进程也无法启动了。
TcpClient
有了Connector,TcpClient就不难实现了,它的代码与TcpServer相似(都有newConnection 和 removeConnection),只不过TcpClient只管理一个TcpConnection。
TcpClient具备TcpConnection断开之后重新连接的功能
,加上Connector具备反复尝试连接的功能
,因此客户端和服务端的启动顺序无关紧要。可以先启动客户端,一旦服务端启动,半分钟内可以恢复连接。在客户端运行期间服务端可以重启,客户端也会自动重连。
连接断开后初次重试的延迟应该有随机性,比如服务器端崩溃,它的所以客户端连接同时断开,然后0.5s之后同时再次发起连接,这样既可能造成SYN丢包,也可能给服务器带来短期大负载,影响其服务质量。因此每个TcpClient应该等待一段随机的时间(0.5~2s),再重试,避免拥塞。
发起连接的时候如果发生TCP SYN
丢包,那么系统默认的重试间隔是3s,这期间不会返回错误码,而且这个间隔似乎不容易修改。如果需要缩短间隔,可以再用一个定时器,在0.5s或者1s之后发起另一个连接。如果有需求的话,这个功能可以做到Connector中。