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);
}
posted @ 2019-02-22 16:17  armme  阅读(3438)  评论(0编辑  收藏  举报