非阻塞模式select模型

要同时接收多个客户端就不能单纯用accept了,因为accept会阻塞程序的运行,用多线程也不能实现,因为客户端断开时情况十分复杂,可能一个线程开出来就再也关不掉了,导致电脑线程开太多死机,所以被逼无奈得用非阻塞模式的select模型,这样就算连接客户端程序也不会阻塞住,能干很多其他的事情。

首先介绍设为非阻塞模式的函数

int ioctlsocket(SOCKET s,long cmd,u_long* argp);
// cmd 为发给套接字的i/o控制命令,取值如下
// FIONBIO:设置或清除阻塞模式命令,当argp非0时将s设为非阻塞模式。WSAAsynSelect会讲套接字自动设为非阻塞模式,这时再调用这个函数设为非阻塞模式会报错
// FIONREAD: 用于确定套接字s自动读入数据量的命令,若s是流套接字类型,argp得到函数recv一次调用时可读入的数据量,是数据报套接字就得到第一个数据包的大小。
// FIOASYNC:设置或清除异步i/o的命令。

这个函数是在客户端上用的。

然后介绍select这个系列的函数。

// 套接字集合
typedef struct fd_set {
        u_int fd_count;               /* how many are SET? */
        SOCKET  fd_array[FD_SETSIZE];   /* an array of SOCKETs */
} fd_set;

 

// 初始化
FD_ZERO(fd_set*);
//
FD_SET(SOCKET,fd_set*);
//
FD_CLR(SOCKET,fd_set*);
//
FD_ISSET(SOCKET,fd_set*);
// 貌似没有改的操作,总之这系列的函数就是简单的动态数组的实现,而且还是内存固定的动态数组

 

int select(int nfds,fd_set* readfds,fd_set* writefds,fd_set* exceptfds,const struct timeval * timeout);
// 第一个参数被忽略,传0即可
// 第二个参数是可读的套接字集合
// 第三个参数是可写的套接字集合
// 第四个参数是出错的套接字集合
// 最后一个参数是等待时间的指针,传NULL则无限等待

 

select其实是个阻塞函数,但它把accept,send,recv的阻塞时间放到一起来等待的,并且是所有套接字集合里的套接字的等待时间都放一起了,因此才显得方便。

服务器端:

#include<WinSock2.h>
#include<Windows.h>
#include<strsafe.h>
#pragma comment(lib,"Ws2_32.lib")

int main()
{
    WSAData wsa;
    WSAStartup(MAKEWORD(2, 2), &wsa);

    SOCKET serveSocket = socket(AF_INET, SOCK_STREAM, 0);

    sockaddr_in serveAddress;
    serveAddress.sin_addr.S_un.S_addr = htonl(ADDR_ANY);
    serveAddress.sin_family = AF_INET;
    serveAddress.sin_port = htons(6000);

    bind(serveSocket, (sockaddr*)&serveAddress, sizeof(serveAddress));// 绑定

    listen(serveSocket, 5);// 监听

    fd_set fdSocket;
    FD_ZERO(&fdSocket);
    FD_SET(serveSocket, &fdSocket);
    while (true)
    {
        fd_set fdRead = fdSocket;
        int iRet = select(0, &fdRead, NULL, NULL, NULL);
        if (iRet > 0)
        {
            for (size_t i = 0;i < fdSocket.fd_count;i++)
            {
                if (FD_ISSET(fdSocket.fd_array[i], &fdRead))// 如果连到服务器的套接字在可读的区域里
                {
                    if (fdSocket.fd_array[i] == serveSocket)// 如果可读的是自己
                    {
                        if (fdSocket.fd_count < FD_SETSIZE)
                        {
                            sockaddr_in clientAddress;
                            int clientlen = sizeof(clientAddress);
                            SOCKET clientSocket = accept(serveSocket, (sockaddr*)&clientAddress, &clientlen);// 接受
                            FD_SET(clientSocket, &fdSocket);
                            printf("接收到连接:%s\n", inet_ntoa(clientAddress.sin_addr));
                        }
                        else
                        {
                            printf("连接太多");
                        }
                    }
                    else
                    {
                        char szText[256];
                        int iRecv = recv(fdSocket.fd_array[i], szText, 128, 0);
                        if (iRecv > 0)
                        {
                            szText[iRecv] = '\0';
                            printf("接收到数据:%s\n", szText);
                        }
                        else
                        {
                            closesocket(fdSocket.fd_array[i]);
                            FD_CLR(fdSocket.fd_array[i], &fdSocket);
                        }
                    }
                }
                
            }
            
        }
        else
        {
            printf("故障了:%d\n", WSAGetLastError());
            closesocket(serveSocket);
            WSACleanup();
            break;
        }
    }
    shutdown(serveSocket, SD_RECEIVE);
    WSACleanup();
    return 0;
}

客户端:

#include<WinSock2.h>
#include<strsafe.h>
#pragma comment(lib,"Ws2_32.lib")

int main()
{
    WSAData wsa;
    WSAStartup(MAKEWORD(2, 2), &wsa);

    SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    sockaddr_in serveAddress;
    serveAddress.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    serveAddress.sin_port = htons(6000);
    serveAddress.sin_family = AF_INET;

    if (SOCKET_ERROR == connect(clientSocket, (sockaddr*)&serveAddress, sizeof(serveAddress)))
    {
        printf("连接到服务器失败");
        getchar();
        return 0;
    }

    int iMode = 1;
    int retVal = ioctlsocket(clientSocket, FIONBIO, (u_long*)&iMode);
    if (retVal == SOCKET_ERROR)
    {
        printf("ioctlsocket failed!");
        WSACleanup();
        return -1;
    }
    printf("clinet is running...\n");
    while (true)
    {
        retVal = connect(clientSocket, (sockaddr*)&serveAddress, sizeof(serveAddress));
        if (SOCKET_ERROR == retVal)
        {
            int err = WSAGetLastError();
            if (err == WSAEWOULDBLOCK || err == WSAEINVAL)// windows异步系统将要阻塞
            {
                Sleep(1);
                printf("check connect\n");
                continue;
            }
            else if (err == WSAEISCONN)break;
            else
            {
                printf("connection failed\n");
                closesocket(clientSocket);
                WSACleanup();
                return -1;
            }
        }
    }

    while (true)
    {
        printf("请输入要发送的内容:");
        char sendBuf[128];
        scanf("%s", sendBuf);
        if (strcmp(sendBuf, "quit") == 0)break;
        while (true)
        {
            retVal=send(clientSocket, sendBuf, strlen(sendBuf), 0);
            if (SOCKET_ERROR == retVal)
            {
                int err = WSAGetLastError();
                if (err == WSAEWOULDBLOCK)
                {
                    Sleep(5);
                    continue;
                }
                else
                {
                    printf("send failed\n");
                    closesocket(clientSocket);
                    WSACleanup();
                    return -1;
                }
            }
            break;
        }
    }


    getchar();
    getchar();
    closesocket(clientSocket);
    WSACleanup();
    return 0;
}

 

posted @ 2022-04-26 17:34  才出昆仑便不清  阅读(93)  评论(0)    收藏  举报