当前位置: 代码迷 >> 综合 >> 异步TCP
  详细解决方案

异步TCP

热度:11   发布时间:2024-02-20 09:11:09.0

工作中,经常使用到TCP通讯,做个记录。后续需要的话,可以拿过来直接使用。

1. 客户端连接请求。

首先将socket设置为NON_BLOCK。然后调用connect函数。如果没有返回成功,判断返回值是否是阻塞中。如果是,调用poll或者select,判断socket是否可写。如果可写,那么表明连接已经建立。否则报失败。

int _connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen,
                    const struct timeval *ro_tv)
{
    int rc = connect(sockfd, addr, addrlen);

#ifdef OS_WIN
    int wsaError = 0;
    if (rc == -1)
    {
        wsaError = WSAGetLastError();
    }

    if (wsaError == WSAEWOULDBLOCK || wsaError == WSAEINPROGRESS)
    {
#else
    if (rc == -1 && errno == EINPROGRESS)
    {
#endif
        fd_set wset;
        int optval;
        socklen_t optlen = sizeof(optval);
        struct timeval tv = *ro_tv;

        /* Wait to be available in writing */
        FD_ZERO(&wset);
        FD_SET(sockfd, &wset);
        rc = select(sockfd + 1, NULL, &wset, NULL, &tv);
        if (rc <= 0)
        {
            /* Timeout or fail */
            return -1;
        }

        /* The connection is established if SO_ERROR and optval are set to 0 */
        rc = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void *)&optval, &optlen);
        if (rc == 0 && optval == 0)
        {
            return 0;
        }
        else
        {
            errno = ECONNREFUSED;
            return -1;
        }
    }
    return rc;
}

该函数被tcp_connect调用

int tcp_connect(const char* destIP,const short & destPort,struct timeval response_timeout)
{
    int rc;
    int s=-1;
    /* Specialized version of sockaddr for Internet socket address (same size) */
    struct sockaddr_in addr;
    int flags = SOCK_STREAM;

#ifdef OS_WIN
    if (tcpip_init_win32() == -1)
    {
        return -1;
    }
#endif

    s = socket(AF_INET, flags, 0);
    if (s == -1)
    {
        return -1;
    }

    rc = tcp_set_ipv4_options(s);
    if (rc == -1)
    {
        close(s);
        s = -1;
        return -1;
    }

    if (debugFlag)
    {
        printf("Connecting to %s:%d\n", destIP, (unsigned short)destPort);
    }

    addr.sin_family = AF_INET;
    addr.sin_port = htons(destPort);

    unsigned long testAdd = inet_addr(destIP);
    if (testAdd == INADDR_NONE) //不是有效的ip可能就是域名
    {
        struct hostent *host;
        struct in_addr **addr_list;
        int i;

        if (NULL == (host = gethostbyname(destIP)))
        {
            if (debugFlag)
            {
                printf("域名ip不存在 %s\n", destIP);
            }
            close(s);
            s = -1;
            return -1;
        }

        addr_list = (struct in_addr **)host->h_addr_list;
        char realIp[16];
        for (i = 0; addr_list[i] != NULL; i++)
        {
            // inet_ntoa()将long类型的IP地址转化为圆点的字符串形式,作用与inet_addr()相反
            strcpy(realIp, inet_ntoa(*addr_list[i]));
            if (debugFlag)
            {
                printf("real ip: %s\n", realIp);
            }
            addr.sin_addr.s_addr = inet_addr(realIp);
        }
    }
    else
    {
        addr.sin_addr.s_addr = testAdd;
    }

    rc = _connect(s, (struct sockaddr *)&addr, sizeof(addr), &response_timeout);
    if (rc == -1)
    {
        close(s);
        s = -1;
        return -1;
    }

    return s;
}

2. select操作

这个函数,目前只关注了read

int tcp_select(int s, fd_set *rset, struct timeval *tv,int& errorCode)
{
    int s_rc;
    if(NULL==rset)
    {
        return -1;
    }
    while ((s_rc = select(s + 1, rset, NULL, NULL, tv)) == -1)
    {
        errorCode = errno;
        if (errorCode == EINTR)
        {
            if (debugFlag)
            {
                //fprintf(stderr, "A non blocked signal was caught\n");
                printf("A non blocked signal was caught\n");
            }
            /* Necessary after an error */
            FD_ZERO(rset);
            FD_SET(s, rset);
        }
        else
        {
            return -1;
        }
    }

    if (s_rc == 0)
    {
        //errno = ETIMEDOUT;
        errorCode = ETIMEDOUT;
        return -1;
    }

    return s_rc;
}

3. 服务端监听

int tcp_listen(const char* listenIP,unsigned short listenPort,int nb_connection)
{
    int new_s;
    int enable;
    int flags;
    struct sockaddr_in addr;

    flags = SOCK_STREAM;

    new_s = socket(AF_INET, flags, IPPROTO_TCP);
    if (new_s == -1)
    {
        return -1;
    }

    enable = 1;
    if (setsockopt(new_s, SOL_SOCKET, SO_REUSEADDR,
                   (char *)&enable, sizeof(enable)) == -1)
    {
        close(new_s);
        return -1;
    }

    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
 
    addr.sin_port = htons(listenPort);
    if (listenIP[0] == '0')
    {
        /* Listen any addresses */
        addr.sin_addr.s_addr = htonl(INADDR_ANY);
    }
    else
    {
        /* Listen only specified IP address */
        addr.sin_addr.s_addr = inet_addr(listenIP);
    }
    if (bind(new_s, (struct sockaddr *)&addr, sizeof(addr)) == -1)
    {
        close(new_s);
        return -1;
    }

    if (listen(new_s, nb_connection) == -1)
    {
        close(new_s);
        return -1;
    }

    return new_s;
}
4. 服务端接收连接

int tcp_accept(int s)
{
    struct sockaddr_in addr;
    socklen_t addrlen;
    int newSock = -1;

    addrlen = sizeof(addr);

    newSock = accept(s, (struct sockaddr *)&addr, &addrlen);

    if (newSock == -1) 
    {
        return -1;
    }

    if (debugFlag) 
    {
        printf("The client connection from %s is accepted\n",
               inet_ntoa(addr.sin_addr));
    }

    return newSock;
}

5. 关闭连接

void tcp_close(int& s)
{
    if (s != -1)
    {
        shutdown(s, SHUT_RDWR);
        close(s);
        s = -1;
    }
}
6. 接收指定长度的数据

int tcp_receive_msg(int s, char *msg, int length_to_read,struct timeval timeout,int& errorCode)
{
    int rc;
    fd_set rset;

    int msg_length = 0;

    /* Add a file descriptor to the set */
    FD_ZERO(&rset);
    FD_SET(s, &rset);

    while (length_to_read != 0) 
    {
        rc = tcp_select(s, &rset, &timeout,errorCode);
        if (rc == -1) 
        {
            _error_print("select");
            return -1;
        }

        rc = recv(s, msg + msg_length, length_to_read,0);
        if (rc == 0) 
        {
            errorCode = ECONNRESET;
            rc = -1;
        }

        if (rc == -1) 
        {
            return -1;
        }

        /* Display the hex code of each character received */
        if (debugFlag) 
        {
            int i;
            for (i=0; i < rc; i++)
                printf("<%.2X>", (unsigned char)msg[msg_length + i]);
        }

        /* Sums bytes received */
        msg_length += rc;
        /* Computes remaining bytes */
        length_to_read -= rc;
    }

    if (debugFlag)
        printf("\n");
    
    return 0;
}

  相关解决方案