首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >一个线程搞定TCP服务器

一个线程搞定TCP服务器

作者头像
gaigai
发布2019-08-29 17:35:36
发布2019-08-29 17:35:36
1.3K0
举报
文章被收录于专栏:Windows开发Windows开发

本文参考IBM知识库的文章进行翻译修改

版权归原作者所有,如有任何问题请及时联系我们


本示例代码介绍如何用非阻塞socket和select() API,只用一个线程实现一个TCP服务器。

socket调用流如下图所示。

本示例代码调用包括:

1. socket()API创建一个套接字,指定使用TCP协议。

2. ioctlsocket()API 设置使用非阻塞模式。

3. bind()API 将套接字绑定到指定端口。

4. listen()API 允许服务器开始接收客户端连接。

5. accept()API 接受客户端连接。

6. select()API 查询指定套接字列表有哪些需要处理,返回值0表示超时,值-1表示调用失败WSAGetLastError()获取具体错误码,值n(>0)表示有n个套接字需要处理。

7. accept()和 recv() API 循环执行,直到返回EWOULDBLOCK错误码。

8. send()API发送接收到的数据。

9. closesocket()API关闭套接字。

具体API使用详见MSDN,对照示例代码去理解。

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>

#define SERVER_PORT  12345

#define TRUE             1
#define FALSE            0

void main(int argc, char *argv[])
{
    u_int    i;
    int len, rc;
    u_long on = 1;
    int    listen_sd, new_sd, current_sd;
    int    end_server = FALSE;
    int    close_conn;
    char   buffer[80];
    struct sockaddr_in addr;
    struct timeval      timeout;
    fd_set              master_set, working_set;

    /*************************************************************/
    /* init WinSocket library      */
    /*************************************************************/
    WSADATA wsa;
    if (WSAStartup(MAKEWORD(2, 2), &wsa) != NO_ERROR)
    {
        perror("WSAStartup() failed");
        exit(-1);
    }

    /*************************************************************/
    /* Create an AF_INET6 stream socket to receive incoming      */
    /* connections on                                            */
    /*************************************************************/
    listen_sd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_sd < 0)
    {
        perror("socket() failed");
        exit(-1);
    }

    /*************************************************************/
    /* Set socket to be nonblocking. All of the sockets for      */
    /* the incoming connections will also be nonblocking since   */
    /* they will inherit that state from the listening socket.   */
    /*************************************************************/
    rc = ioctlsocket(listen_sd, FIONBIO, &on);
    if (rc < 0)
    {
        perror("ioctl() failed");
        closesocket(listen_sd);
        exit(-1);
    }

    /*************************************************************/
    /* Bind the socket                                           */
    /*************************************************************/
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = htons(SERVER_PORT);
    rc = bind(listen_sd,
        (struct sockaddr *)&addr, sizeof(addr));
    if (rc < 0)
    {
        perror("bind() failed");
        closesocket(listen_sd);
        exit(-1);
    }

    /*************************************************************/
    /* Set the listen back log                                   */
    /*************************************************************/
    rc = listen(listen_sd, 32);
    if (rc < 0)
    {
        perror("listen() failed");
        closesocket(listen_sd);
        exit(-1);
    }

    /*************************************************************/
    /* Initialize the master fd_set                              */
    /*************************************************************/
    FD_ZERO(&master_set);
    FD_SET(listen_sd, &master_set);

    /*************************************************************/
    /* Initialize the timeval struct to 3 minutes.  If no        */
    /* activity after 3 minutes this program will end.           */
    /*************************************************************/
    timeout.tv_sec = 3 * 60;
    timeout.tv_usec = 0;

    /*************************************************************/
    /* Loop waiting for incoming connects or for incoming data   */
    /* on any of the connected sockets.                          */
    /*************************************************************/
    do
    {
        /**********************************************************/
        /* Copy the master fd_set over to the working fd_set.     */
        /**********************************************************/
        memcpy(&working_set, &master_set, sizeof(master_set));

        /**********************************************************/
        /* Call select() and wait 3 minutes for it to complete.   */
        /**********************************************************/
        printf("Waiting on select()...\n");
        rc = select(0, &working_set, NULL, NULL, &timeout);

        /**********************************************************/
        /* Check to see if the select call failed.                */
        /**********************************************************/
        if (rc < 0)
        {
            perror("  select() failed");
            break;
        }

        /**********************************************************/
        /* Check to see if the 3 minute time out expired.         */
        /**********************************************************/
        if (rc == 0)
        {
            printf("  select() timed out.  End program.\n");
            break;
        }
      
        for (i = 0; i <= working_set.fd_count; ++i)
        {  
            current_sd = working_set.fd_array[i];
            /****************************************************/
            /* Check to see if this is the listening socket     */
            /****************************************************/
            if (current_sd == listen_sd)
            {
                printf("  Listening socket is readable\n");
                /*************************************************/
                /* Accept all incoming connections that are      */
                /* queued up on the listening socket before we   */
                /* loop back and call select again.              */
                /*************************************************/
                do
                {
                    /**********************************************/
                    /* Accept each incoming connection.  If       */
                    /* accept fails with EWOULDBLOCK, then we     */
                    /* have accepted all of them.  Any other      */
                    /* failure on accept will cause us to end the */
                    /* server.                                    */
                    /**********************************************/
                    new_sd = accept(listen_sd, NULL, NULL);
                    if (new_sd < 0)
                    {
                        if (WSAGetLastError() != WSAEWOULDBLOCK)
                        {
                            perror("  accept() failed");
                            end_server = TRUE;
                        }
                        break;
                    }

                    /**********************************************/
                    /* Add the new incoming connection to the     */
                    /* master read set                            */
                    /**********************************************/
                    printf("  New incoming connection - %d\n", new_sd);
                    FD_SET(new_sd, &master_set);    

                    /**********************************************/
                    /* Loop back up and accept another incoming   */
                    /* connection                                 */
                    /**********************************************/
                } while (new_sd != -1);
            }

            /****************************************************/
            /* This is not the listening socket, therefore an   */
            /* existing connection must be readable             */
            /****************************************************/
            else
            {
                printf("  Descriptor %d is readable\n", current_sd);
                close_conn = FALSE;
                /*************************************************/
                /* Receive all incoming data on this socket      */
                /* before we loop back and call select again.    */
                /*************************************************/
                do
                {
                    /**********************************************/
                    /* Receive data on this connection until the  */
                    /* recv fails with EWOULDBLOCK.  If any other */
                    /* failure occurs, we will close the          */
                    /* connection.                                */
                    /**********************************************/
                    rc = recv(current_sd, buffer, sizeof(buffer), 0);
                    if (rc < 0)
                    {
                        if (WSAGetLastError() != WSAEWOULDBLOCK)
                        {
                            perror("  recv() failed");
                            close_conn = TRUE;
                        }
                        break;
                    }

                    /**********************************************/
                    /* Check to see if the connection has been    */
                    /* closed by the client                       */
                    /**********************************************/
                    if (rc == 0)
                    {
                        printf("  Connection closed\n");
                        close_conn = TRUE;
                        break;
                    }

                    /**********************************************/
                    /* Data was received                          */
                    /**********************************************/
                    len = rc;
                    printf("  %d bytes received\n", len);

                    /**********************************************/
                    /* Echo the data back to the client           */
                    /**********************************************/
                    rc = send(current_sd, buffer, len, 0);
                    if (rc < 0)
                    {
                        perror("  send() failed");
                        close_conn = TRUE;
                        break;
                    }

                } while (TRUE);

                /*************************************************/
                /* If the close_conn flag was turned on, we need */
                /* to clean up this active connection.  This     */
                /* clean up process includes removing the        */
                /* descriptor from the master set and            */
                /* determining the new maximum descriptor value  */
                /* based on the bits that are still turned on in */
                /* the master set.                               */
                /*************************************************/
                if (close_conn)
                {
                    closesocket(current_sd);
                    FD_CLR(current_sd, &master_set);
                }
            } /* End of existing connection is readable */
        } /* End of loop through selectable descriptors */

    } while (end_server == FALSE);

    /*************************************************************/
    /* Clean up all of the sockets that are open                 */
    /*************************************************************/
    for (i = 0; i <= master_set.fd_count; ++i)
    {
        closesocket(master_set.fd_array[i]);
    }
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-07-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Windows开发 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档