zoukankan      html  css  js  c++  java
  • Windows网络通信(二):socket异步编程

    简述

    这里使用的API和同步编程的API是差不多的,只多了一个ioctlsocket和select函数。这里面涉及一个很重要的结构体fd_set。这里用到的API大部分都是windows和linux通用的。

    1. ioctlsocket控制socket的IO模型

    int ioctlsocket(
      _In_    SOCKET s,
      _In_    long   cmd,
      _Inout_ u_long *argp
    );

    s:需要设置的socket

    cmd:想要对socket执行的命令,异步编程需要FIONBIO命令

    argp:执行命令的参数,FIONBIO的参数如果为0表示阻塞模式,非0表示非阻塞模式

    2. select获取一个或多个套接字的状态(可读可写或其他状态)

    int select(
      _In_    int                  nfds,
      _Inout_ fd_set               *readfds,
      _Inout_ fd_set               *writefds,
      _Inout_ fd_set               *exceptfds,
      _In_    const struct timeval *timeout
    );

    nfds:无用

    readfds:一个fd_set,表示哪些套接字需要判断是否”可读”状态,其中”可读”状态可以是accept,recv或者套接字已经关闭,重置,中断。

    writefds:一个fd_set,表示哪些套接字需要判断是否”可写”状态,其中”可写”状态可以是connect成功,send。一般来说send是会立刻返回的,但是当send缓存区被装满了,数据无法放入时就会导致send函数阻塞,异步模式下可以用select判断缓存区是否有空间。

    exceptfds:一个fd_set,表示哪些套接字需要判断是否”异常”状态,其中”异常”状态一般是connect失败。

    timeout:等待的超时时间,select会等待timeout毫秒,NULL表示无限等待

    成功返回所有fd_set响应的套接字数目,错误发生返回SOCKET_ERROR,超时返回0.

    3. fd_set就是一个简单的结构体,内部有一个socket数组和一个数组成员个数。

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

    可以使用如下宏操作fd_set

    FD_CLR(fd, set):从fd_set中删除指定的socket

    FD_SET(fd, set):从fd_set中添加指定的socket

    FD_ZERO(set):清空fd_set

    FD_ISSET(fd, set):判断指定socket是否在fd_set中

    异步通信示例

    下面是异步通信服务端的代码,用于回发客户端发过来的消息,单线程中处理多个客户端的通信。

    #include <winsock2.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #pragma comment(lib,"ws2_32.lib")
    
    #define IP "127.0.0.1"
    #define DEFAULT_PORT 12345
    
    #define Print_ErrCode(e) fprintf(stderr,"
    [Server]%s 执行失败: %d
    ",e,WSAGetLastError())
    #define DEFAULT_BACKLOG 5
    #define MAX_IO_PEND 10
    int curr_size = 0; //当前的句柄数
    #define OP_READ 0x10
    #define OP_WRITE 0x20
    
    //定义结构体用于储存通信信息
    typedef struct _socklist
    {
        SOCKET sock;
        DWORD Op;
        char name[100];
        char Buffer[128];
        int  bufLen;
    } Socklist;
    
    int main(int argc, char **argv)
    {
        int nStartup = 0;
        struct sockaddr_in clientService;
        WSADATA wsaData;
        SOCKET sockListen = INVALID_SOCKET;
        int nRet = 0;
        //保存所有的客户端、服务端的SOCKET信息
        if (0 != (nStartup = WSAStartup(MAKEWORD(2, 2), &wsaData)))
        {
            WSASetLastError(nStartup); //WSAStartup不会自动设置错误代码
            Print_ErrCode("WSAStartup()");
            return 1;
        }
        clientService.sin_family = AF_INET;
        clientService.sin_addr.s_addr = inet_addr(IP);
        clientService.sin_port = htons(DEFAULT_PORT);
    
        if (INVALID_SOCKET == 
            (sockListen = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))
            )
        {
            Print_ErrCode("socket()");
            return 1;
        }
        u_long type = 1;
        ioctlsocket(sockListen, FIONBIO, &type);
    
        if (SOCKET_ERROR == bind(sockListen,
                                 (SOCKADDR *)&clientService,
                                 sizeof(clientService)
                                ))
        {
            Print_ErrCode("bind()");
            closesocket(sockListen);
            return 1;
        }
    
        if (SOCKET_ERROR == listen(sockListen, DEFAULT_BACKLOG))
        {
            Print_ErrCode("listen()");
            closesocket(sockListen);
        }
        printf("[Server]监听 %s:%d
    ", IP, DEFAULT_PORT);
        
        //存放所有的socket,包括用于accept的socket。
        Socklist sockList[10];
        //将监听socket设为socklist第一个元素
        sockList[0].sock = sockListen;
        curr_size = 1;
    
        // 一个大循环,不断的接收客户端请求
        while(true)
        {
            //循环判断是否有请求需要处理
            fd_set fdRead, fdWrite;
            while (true)
            {
                FD_ZERO(&fdRead);
                FD_ZERO(&fdWrite);
                FD_SET(sockList[0].sock, &fdRead);
                for (int i = 1; i < curr_size; i++)
                {
                    //对需要send的客户端连接select
                    if (sockList[i].Op == OP_WRITE)
                    {
                        FD_SET(sockList[i].sock, &fdWrite);
                    }
                    //对所有的客户端连接select
                    FD_SET(sockList[i].sock, &fdRead);
                }
                //这个操作会被阻塞
                nRet = select(0, &fdRead, &fdWrite, NULL, NULL);
                if (FD_ISSET(sockList[0].sock, &fdRead))
                {
                    //第0个socket可用了,这时accept一定会立刻返回成功或失败 这里需要处理最大连接数
                    SOCKET sockNewClient = accept(sockListen,NULL,NULL);
                    sockList[curr_size].sock = sockNewClient;
                    sockList[curr_size++].Op = OP_READ;
                    break;
                }
                //其他socket可用了,判断哪些能读,哪些能写
                if (fdRead.fd_count > 0)
                {
                    for (int i = 1; i < curr_size; i++)
                    {
                        if (FD_ISSET(sockList[i].sock, &fdRead))
                        {
                            //开始recv
                            nRet = recv(sockList[i].sock, sockList[i].Buffer, 127, 0);
                            if (nRet == SOCKET_ERROR)
                            {
                                closesocket(sockList[i].sock);
                                //移除sockList
                                for (int j = i; j < curr_size-1; j++)
                                {
                                    sockList[i].sock = sockList[i + 1].sock;
                                }
                                curr_size--;
                            }
                            else
                            {
                                sockList[i].Buffer[nRet] = '';
                                sockList[i].bufLen = nRet;
                                sockList[i].Op = OP_WRITE;
                                printf("[Server]接收到:%s
    ", sockList[i].Buffer);
                            }
                        }
                    }
                }
                if (fdWrite.fd_count > 0)
                {
                    for (int i = 1; i < curr_size; i++)
                    {
                        if (FD_ISSET(sockList[i].sock, &fdWrite))
                        {
                            if (sockList[i].Op == OP_WRITE)
                            {
                                //开始send
                                nRet = send(sockList[i].sock, sockList[i].Buffer, sockList[i].bufLen, 0);
                                //事实上,这里可能会有nRet小于bufLen的情况
                                if (nRet == SOCKET_ERROR)
                                {
                                    closesocket(sockList[i].sock);
                                    //移除sockList
                                    for (int j = i; j < curr_size - 1; j++)
                                    {
                                        sockList[i].sock = sockList[i + 1].sock;
                                    }
                                    curr_size--;
                                }
                                else
                                {
                                    sockList[i].Op = OP_READ;
                                    printf("[Server]已发送:%d
    ", nRet);
                                }
                            }
                        }
                    }
                }
            }
        }
    }
  • 相关阅读:
    axure 用中继器实现下拉多选框
    excel 常用全局变量
    win10 x64 python3.6 pycharm 安装statsmodels
    Oracle 10进制转36进制
    概念数据模型设计与逻辑数据模型设计、物理数据模型设计的关系
    does not support ASP.NET compatibility 错误
    Oracle日志文件管理与查看
    oradmin相关用法
    Oracle Standby Database 实现方案
    c# 调用zebra打印指令 打印到USB端口
  • 原文地址:https://www.cnblogs.com/Reyzal/p/6759978.html
Copyright © 2011-2022 走看看