socket 客户端编程:非阻塞式 connect,错误判断及退出重连
本文将探讨 socket 客户端的非阻塞式 connect,连接成功后的错误判断及退出重连。
1. 连接方法
套接字创建之后,默认是阻塞式的,对其执行 connect 操作,如果服务端在监听,则会成功建立连接,但这只是理想情况。如果服务端没有开启,或是网络异常呢,connect 会一直阻塞到连接超时,这个超时时间在 Linux 下一般是 75 s 到几分钟。那么,如果在服务端未打开的时候 connect,连接建立会阻塞,即便服务端在这时候开启,connect 也必须在到达超时时间返回之后,才能执行下一次 connect,成功建立连接。也就是说,成功建立连接所需的时间最长是套接字的超时时间,而 75 s 是我们不能忍受的。我们要做的就是将 socket 设置为非阻塞模式,connect 成功与否都立即返回,以便及时响应服务端的监听。
网上一般的实现方式是,直接将 connect 设置为非阻塞模式,这种方法只有在 client, server 在同一台主机情况下,会成功建立连接返回,一般情况下,都不能建立连接,返回 -1,errno 置为 EINPROGRESS,于是需要利用 select 判断 socket 的连接建立状态。具体实现参考 https://blog.csdn.net/nphyez/article/details/10268723
以上实现方式比较繁琐,我的方法是,为阻塞式 socket 设置超时时间,准确地说,是调用 setsockopt 设置发送超时时间,设置了超时的阻塞式 socket 跟非阻塞 socket 操作起来是类似的,而且避免了有远程主机监听时仍然返回 -1 的情况,不用再调用 select 来做连接判断。实现代码为
while(1)
{
sock = socket(PF_INET, SOCK_STREAM, 0);
if (sock == -1)
{
LOG4CXX_WARN(logger, "Create client socket error");
goto exit;
}
if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &send_timeout, sizeof(send_timeout)) != 0)
{
LOG4CXX_WARN(logger, "client socket set send timeout option error");
}
if (connect(sock, (struct sockaddr*) &serv_addr, sizeof (serv_addr)) == -1)
{
LOG4CXX_WARN(logger, "client socket connect error, " << errno << ", " << strerror(errno));
close(sock);
sleep(2);
continue;
}
LOG4CXX_INFO(logger, "Connected to server");
}
2. 错误判断及退出重连
对于接收,非阻塞式(或者说设置了超时的阻塞式)socket,考虑四种情况
- 缓冲区无数据可读时,recv 返回 -1,errno 置为 EWOULDBLOCK,无妨,继续读取即可
- recv 期间,系统捕获中断,返回 -1,errno 置为 EINTR,无妨,继续读取即可
- recv 返回 -1,errno 为其他错误,需要退出当前线程,重开 client 线程
- recv 返回 0,表示接收到 server 的断开连接信号,需要退出当前线程,重开 client 线程
对于发送,只考虑两种情况
- send 返回 > 0,表示发送成功
- send 返回 -1,连接出错,需要退出当前线程,重开 client 线程
对于连接出错,笔者测试了两种情况,(1) 禁用网卡并重启,(2) 将动态 IP 设置为静态 IP 再改回来,send 返回 -1,errno 置为 EWOULDBLOCK,这两种情况是需要退出重连的,所以我们不能像 recv 那样把 EWOULDBLOCK 区别对待。
测试拔网线然后插上,不会返回错误,client 会自动恢复数据的读写。
测试 server 崩溃,recv 返回 -1,errno 置为 ECONNRESET(该错误下对 socket 写操作会导致程序崩溃,返回错误 broken pipe)。
测试时 server 可用 sokit 之类的 socket 调试工具。
完整代码如下
client 线程:
#include <iostream>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <errno.h>
using namespace std;
#define SERV_IP "192.168.1.9"
#define SERV_PORT 10001
#define SOCKET_SEND_TIMEOUT_MS 20*1000
#define SOCKET_RECV_TIMEOUT_MS 20*1000
void* ClientThread(void *data)
{
int sock;
struct sockaddr_in serv_addr;
int count;
char buf[NET_SOCK_RECEIVE_BUF_SIZE];
cout << "Enter NetClientOfNavi" << endl;
struct timeval send_timeout = {0, SOCKET_SEND_TIMEOUT_MS};
struct timeval recv_timeout = {0, SOCKET_RECV_TIMEOUT_MS};
memset(&serv_addr, 0, sizeof (serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr(SERV_IP);
serv_addr.sin_port = htons(SERV_PORT);
while(1)
{
sock = socket(PF_INET, SOCK_STREAM, 0);
if (sock == -1)
{
LOG4CXX_WARN(logger, "Create client socket error");
goto exit;
}
if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &send_timeout, sizeof(send_timeout)) != 0)
{
LOG4CXX_WARN(logger, "client socket set send timeout option error");
}
if (connect(sock, (struct sockaddr*) &serv_addr, sizeof (serv_addr)) == -1)
{
LOG4CXX_WARN(logger, "client socket connect error, " << errno << ", " << strerror(errno));
close(sock);
sleep(2);
continue;
}
LOG4CXX_INFO(logger, "Connected to server");
if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (void*) &recv_timeout, sizeof (recv_timeout)) != 0)
{
LOG4CXX_WARN(logger, "client socket set receive timeout option error");
}
while (1)
{
count = recv(sock, buf, NET_SOCK_RECEIVE_BUF_SIZE, 0);
if (count > 0)
{
buf[count] = 0;
}
else
{
if ((count == -1 && errno != EWOULDBLOCK && errno != EINTR) || count == 0)
{
LOG4CXX_WARN(logger, "connect closed, " << errno << ", " << strerror(errno));
goto exit;
}
}
count = send(sock, "data_send", 9, 0);
if (count == -1)
{
LOG4CXX_WARN(logger, "Send data to server error: " << errno << ", " << strerror(errno));
goto exit;
}
}
}
exit:
if (sock > 0) close(sock);
return NULL;
}
主线程:
#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
using namespace std;
pthread_t gClientPid = 0;
int main(int argc, char** argv)
{
int ret;
ret = pthread_create(&gClientPid, NULL, ClientThread, NULL);
if (ret != 0)
{
cout << "Create ClientThread error!" << endl;
return -1;
}
pthread_detach(gClientPid);
while (1)
{
ret = pthread_kill(gClientPid, 0);
if(ret == ESRCH)
{
ret = pthread_create(&gClientPid, NULL, ClientThread, NULL);
if (ret != 0)
{
cout << "Restart ClientThread error!" << endl;
return -1;
}
pthread_detach(gClientPid);
}
usleep(20000);
}
return (0);
}