基于Select模型的通信仿真

基于Select模型的通信仿真

一、实验要求

  编写Win32程序模拟实现基于Select模型的两台计算机之间的通信,要求编程实现服务器端与客户端之间双向数据传递。客户端向服务器端发送“计算从1到100的奇数和”,服务器回应客户端并给出从1到100的奇数和结果。

二、 编程环境

  vs2022
由于Win32工程中要自己创建和释放控制台,较为麻烦,所以使用vs2022的控制台应用程序,可以直接在windows的控制台上输出和输入

注意要关掉每个项目的SDL检查,否则可能会编译不通过。

如果要使用Visual C++ 6.0,可参考
https://www.cnblogs.com/wa2211lq/p/18509428
基于Select模型的通信仿真--win32编程代码:
https://www.cnblogs.com/wa2211lq/p/18553351

三、流程图(TCP)

1、创建监听套接字                SOCKET()
2、给监听套接字绑定端口号        BIND()
3、给监听套接字开启监听属性      LISTEN()
4、初始化文件描述符集合          FD_ZERO()
5、添加要检测的监听文件描述符    FD_SET()
6、不停地检测文件描述符      while(1)SELECT()
   6.1 超时  select()=0 再次检测或关闭套接字
   6.2 异常  select()=-1 异常处理
   6.3 成功  selct()>0 
7、判断文件描述符属于哪一类 FD_ISSET()
    通过将原来redset集合中的文件描述符与select处理过的tmp集合比较,判断哪些文件描述符就绪,如果就绪,是哪一类
    7.1 是否为监听文件描述符     //监听套接字的读缓冲区是否有数据,有新的连接 
        等待客户端连接               ACCEPT()      //不会阻塞,因为select已经检测过此监听描述符的读缓冲区里有客户端连接请求
        添加得到的通信文件描述符     FD_SET()
        开始新一轮的检测
    7.2 通信文件描述符
        接收数据                     RECV()
            RECV()=0,客户端已断开连接
                关闭通信套接字                  CLOSE()
                从集合中删除该通信文件描述符    FD_CLR()
            RECV()>0,服务器接收到客户端的数据
        发送数据                     SEND()

前三步和socket套接字流程一样,直到第四步开始通过select()函数实现在单个线程内同时并发处理多个套接字连接:
IO多路转接(复用)将对文件描述符的缓冲区的检测交给内核,同时检测多个文件描述符的读写缓冲区,每检测一轮之后内核将可以使用的文件描述符告诉我们,此时再调用accept、recv、send,不会导致阻塞。

四、编程准备工作

创建1个基于select模型的server和3个客户端client1、client2、client3用来模拟并发通信

  1. 创建控制台项目,不要将解决方案和项目放在同一目录。

  2. 关掉每个项目的SDL检查,否则可能会编译不通过。
  3. 正确调用头文件和库
#include<stdio.h>
#include<string.h>
#include<WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
  1. 初始化和清理winsock
// 初始化 Winsock
WSADATA wsaData;
int result = WSAStartup(MAKEWORD(2, 2), &wsaData);

if (result != 0)
{
    printf("WSAStartup failed with error: %d\n", result);
    return 1;
}
//网络编程相关的操作,如创建套接字等

// 清理 Winsock
 WSACleanup();

五、select()

批量检测缓冲区

服务器端有两类文件描述符(套接字):

  1. 监听:(仅1个)
    读缓冲区:(检测是否为空)
    存储所有客户端的连接请求
    accept读取里面是否有有客户端的请求(是否为空),如果为空,就一直阻塞,直到里面有客户端的请求,则解除阻塞,建立连接
    写缓冲区
  2. 通信:(N个:每和一个客户端建立一个新连接,就加1个)
    读缓冲区:(检测是否为空)
    存储客户端发来的数据
    调用recv方法读取,如果为空,则阻塞
    写缓冲区:(检测剩余空间)
    存储服务器通过send发送的数据
    如果写缓冲区满了,则数据无法通过send写入写缓冲区,无法发送到客户端,阻塞,直到写缓冲区的数据被发送到客户端,清空写缓冲 区。

客户端只有通信文件描述符(套接字)
通信:(仅1个)
读缓冲区:(检测是否为空)
存储服务端发来的数据,
调用recv方法读取,如果为空,则阻塞
写缓冲区:(检测剩余空间)
存储客户端通过send要发送的数据
如果写缓冲区满了,则数据无法通过send写入写缓冲区,无法发送到服务器,阻塞,直到写缓冲区的数据被发送到服务器,清空写缓冲区。

accept、recv、send检测缓冲区是否可用,三个互斥,一次只能执行一个(顺序执行),若其中有一个阻塞,则无法继续

IO多路转接(复用)将对文件描述符的缓冲区的检测交给内核,同时检测多个文件描述符的读写缓冲区,每检测一轮之后内核将可以使用的文件描述符告诉我们,此时再调用accept、recv、send,不会导致阻塞
即select模型批量检测缓冲区是否可用

函数及其参数详解

select()

int select                                  //返回值:>0 成功,返回集合中已就绪的文件描述符的总个数;
                                             // 0 超时,没有检测到就绪的文件描述符;-1 函数调用失败
(
    int nfds,                               //检测的三个文件描述符集合中最大的文件描述符+1
                                           //将集合拷贝到内核,内核要线性遍历文件描述符,这个值是循环结束的条件
                                            //Window中此参数无效,指定为-1
    fd_set * readfds,                       //要检测的读集合的指针,检测后可读集合的指针
    fd_set * writefds,                      //要检测的写集合的指针,检测后可写集合的指针
    fd_set * exceptfds,                     //要检测是否有异常的集合的指针,检测后异常集合的指针
                                            //(传入传出参数)内核检测成功后返回可读/可写/异常的文件描述符到对应指针指向的地址
    const struct timeval * timeout          //select函数检测时长,
                                           //等待固定时长:函数检测不到就绪的文件描述符,在固定时长之后解除阻塞,函数返回0
                                           //如果 timeout 设为 NULL,select 将会无限阻塞。
    );

第一个参数

select模型是跨平台的。
在类 Unix 系统中,select 的第一个参数是** maxfd + 1,表示需要检查的文件描述符集合中最大的文件描述符加一。这是循环结束的条件,因为 select 函数会检查所有小于或等于 maxfd 的文件描述符。
在 Windows 系统中,select 的第一个参数是
-1**,表示 select 函数应该检查所有套接字,直到找到可读的套接字为止,而不需要指定最大的文件描述符加一。
在 Windows 中,select 函数的第一个参数通常被忽略,因此即使传入 maxfd + 1 也能正常工作,但是最好遵循每个平台的最佳实践。
即在 Windows 上,使用 -1 作为 select 的第一个参数;在类 Unix 系统上,使用 maxfd + 1。

fd_set

typedef struct fd_set {
        u_int fd_count;                 //套接字数量
        SOCKET  fd_array[FD_SETSIZE];   //套接字集合 
};

fd_set类型参数的操作函数

void FD_CLR(int fd,fd_set *set);    //将文件描述符fd从set集合中删除(fd对应标志位设为0)
int FD_ISSET(int fd,fd_set *set);   //判断文件描述符fd是否在set集合中(判断fd对应标志位是否为1)
void FD_SET(int fd,fd_set *set);    //将文件描述符fd添加到set集合中(fd对应标志位设为1)
void FD_ZERO(fd_set *set);          //初始化set集合(所有标志位置0)

struct timeval * timeout

struct timeval              //总时长=秒+微秒
{
        long    tv_sec;     //秒  
        long    tv_usec;    //微秒,用不到时也要初始化为0
};

六、Server端详解

前三步和socket通信一模一样,详情参考
https://www.cnblogs.com/wa2211lq/p/18509428
或直接看下面完整代码

4、初始化文件描述符集合

fd_set redset;
 //4、初始化文件描述符集合  
 FD_ZERO(&redset);

5、添加要检测的监听文件描述符

//5、添加要检测的监听文件描述符    
FD_SET(listen_socket,&redset);

6、不停地检测文件描述符

window

  printf("This is SERVER!\n");
  while (1)
  {
      fd_set tmp = redset;
      /* 6、不停地检测文件描述符
            6.1 超时  select() = 0 再次检测或关闭套接字
            6.2 异常  select() = -1 异常处理
            6.3 成功  selct() >0
     */
      printf("selecting...\n");
      int ret = select(-1, &tmp, NULL, NULL, NULL);
      if (ret <= 0)
      {
          printf("select failed!!! errcode: %d\n", GetLastError());
          closesocket(listen_socket);
          WSACleanup();
          return -1;
      }
     // printf("select = %d\n",ret);
      //7、判断文件描述符属于哪一类
}

类unix

  int maxfd = listen_socket;
  printf("This is SERVER!\n");
  while (1)
  {
      fd_set tmp = redset;
      /* 6、不停地检测文件描述符
            6.1 超时  select() = 0 再次检测或关闭套接字
            6.2 异常  select() = -1 异常处理
            6.3 成功  selct() >0
     */
      printf("selecting...\n");
      int ret = select(maxfd + 1, &tmp, NULL, NULL, NULL);
      if (ret <= 0)
      {
          printf("select failed!!! errcode: %d\n", GetLastError());
          closesocket(listen_socket);
          WSACleanup();
          return -1;
      }
    //7、判断文件描述符属于哪一类
}

7、判断文件描述符属于哪一类

window

//7、判断文件描述符属于哪一类
    //通过将原来redset集合中的文件描述符与select处理过的tmp集合比较,判断哪些文件描述符就绪,如果就绪,是哪一类
for (int i = 0; i < (int)redset.fd_count; i++)
{
    if (FD_ISSET(redset.fd_array[i], &tmp))//判断文件描述符(套接字)i的读缓冲区是否有数据
    {
        //就绪文件描述符是监听描述符
        if (redset.fd_array[i] == listen_socket)    // 监听套接字接收到新连接
        {
            if (redset.fd_count < FD_SETSIZE)
            {
                sockaddr_in addrRemote;
                int nAddrLen = sizeof(addrRemote);
                //接收客户端的连接请求
                SOCKET client_socket = ::accept(listen_socket, (SOCKADDR*)&addrRemote, &nAddrLen);
                FD_SET(client_socket, &redset);
                printf("与主机 %s 建立连接\n", inet_ntoa(addrRemote.sin_addr));
            }
            else
            {
                printf("Too much connections!\n");
                continue;
            }
        }
        else//就绪文件描述符不是监听描述符,是通信描述符
        {
            //接收信息
            char rbuffer[1024] = { 0 };
            int len = recv(redset.fd_array[i], rbuffer, 1024, 0);

            if (len <= 0)
            {
                printf("The client %d has disconnected.\n", i);
                FD_CLR(redset.fd_array[i], &redset);
                shutdown(redset.fd_array[i], SD_BOTH);
                closesocket(redset.fd_array[i]);
                break;
            }
            printf("recive from client%d:\t%s\n", i, rbuffer);

            //发送信息
            char sbuffer[1024] = { 0 };
            // 检查接收到的消息
            if (strcmp(rbuffer, "计算从1到100的奇数和") == 0)
            {
                int sum = 0;
                for (int j = 1; j <= 100; j += 2)
                {
                    sum += j;
                }
                printf("send to client%d:\t1到100的奇数和是 %d\n", i, sum);

                sprintf(sbuffer, "1到100的奇数和是 %d\n", sum);
            }
            else
            {
                printf("send to client%d:\tunknow!\n", i);
                sprintf(sbuffer, "unknow!");
            }

            len = send(redset.fd_array[i], sbuffer, strlen(sbuffer), 0);
            if (len == -1)
            {
                perror("send error");
                exit(1);
            }
           
        }
    }
}

类unix

//7、判断文件描述符属于哪一类
if (FD_ISSET(listen_socket, &tmp))//7.1监听套接字的读缓冲区有数据
{
    printf("\nWaiting for connect...\n");//不阻塞
    //等待客户端连接
    SOCKET client_socket = accept(listen_socket, NULL, NULL);
    if (INVALID_SOCKET == client_socket)
    {
        printf("Connect invalid!!!\n");
    }
    else
    {
        printf("Connected successfully!\n\n");
        //添加得到的通信文件描述符
        FD_SET(client_socket, &redset);
        maxfd = client_socket > maxfd ? client_socket : maxfd;
    }
}

for (int i = 0; i <= maxfd; ++i)
{
    if (i != listen_socket && FD_ISSET(i, &tmp))// 7.2 i为通信文件描述符(套接字),且读缓冲区有数据
    {
        //接收信息
        char rbuffer[1024] = { 0 };
        int len = recv(i, rbuffer, 1024, 0);

        if (len <= 0)
        {
            printf("The client %d has disconnected.\n", i);
            FD_CLR(i, &redset);
            shutdown(i, SD_BOTH);
            closesocket(i);
            break;
        }
        printf("recive from client%d:\t%s\n", i, rbuffer);

        //发送信息
        char sbuffer[1024] = { 0 };
        // 检查接收到的消息
        if (strcmp(rbuffer, "计算从1到100的奇数和") == 0)
        {
            int sum = 0;
            for (int j = 1; j <= 100; j += 2)
            {
                sum += j;
            }
            printf("send to client%d:\t1到100的奇数和是 %d\n", i, sum);

            sprintf(sbuffer, "1到100的奇数和是 %d\n", sum);
        }
        else
        {
            printf("send to client%d:\tunknow!\n", i);
            sprintf(sbuffer, "unknow!");
        }

        send(i, sbuffer, strlen(sbuffer), 0);
    }
}

七、完整代码

server

window

#include<stdio.h>
#include<string.h>
#include<WinSock2.h>

#pragma comment(lib,"ws2_32.lib")

int main()
{
    

    // 初始化 Winsock
    WSADATA wsaData;
    int result = WSAStartup(MAKEWORD(2, 2), &wsaData);

    if (result != 0)
    {
        printf("WSAStartup failed with error: %d\n", result);
        return 1;
    }

    //1.创建socket套接字
    SOCKET listen_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (INVALID_SOCKET == listen_socket)
    {
        printf("create listen socket failed!!! errcode: %d\n", GetLastError());
        WSACleanup();
        return -1;
    }

    //2.给socket绑定端口号
    struct sockaddr_in local = { 0 };

    local.sin_family = AF_INET;
    local.sin_port = htons(8080);//绑定端口
    //local.sin_addr.s_addr = htonl(INADDR_ANY);//接收全部网卡的数据 大小端转化
    local.sin_addr.s_addr = inet_addr("0.0.0.0");//接收全部网卡的数据 字符串ip转成整数ip
    if (-1==bind(listen_socket, (struct sockaddr*)&local, sizeof(local)))
    {
        printf("bind failed!!! errcode: %d\n", GetLastError());
        WSACleanup();
        return -1;
    }

    //3.给socke开启监听属性,只用来接收连接
    if (-1 == listen(listen_socket, 10))
    {
        printf("start listen failed!!! errcode: %d\n", GetLastError());
        WSACleanup();
        return -1;
    }
          
    fd_set redset;
    //4、初始化文件描述符集合  
    FD_ZERO(&redset);
    //5、添加要检测的监听文件描述符    
    FD_SET(listen_socket,&redset);

    
    printf("This is SERVER!\n");
    while (1)
    {
        fd_set tmp = redset;
        /* 6、不停地检测文件描述符
              6.1 超时  select() = 0 再次检测或关闭套接字
              6.2 异常  select() = -1 异常处理
              6.3 成功  selct() >0
       */
        printf("selecting...\n");
        int ret = select(-1, &tmp, NULL, NULL, NULL);
        if (ret <= 0)
        {
            printf("select failed!!! errcode: %d\n", GetLastError());
            closesocket(listen_socket);
            WSACleanup();
            return -1;
        }
       // printf("select = %d\n",ret);
        //7、判断文件描述符属于哪一类
            //通过将原来redset集合中的文件描述符与select处理过的tmp集合比较,判断哪些文件描述符就绪,如果就绪,是哪一类
        for (int i = 0; i < (int)redset.fd_count; i++)
        {
            if (FD_ISSET(redset.fd_array[i], &tmp))//判断文件描述符(套接字)i的读缓冲区是否有数据
            {
                //就绪文件描述符是监听描述符
                if (redset.fd_array[i] == listen_socket)    // 监听套接字接收到新连接
                {
                    if (redset.fd_count < FD_SETSIZE)
                    {
                        sockaddr_in addrRemote;
                        int nAddrLen = sizeof(addrRemote);
                        //接收客户端的连接请求
                        SOCKET client_socket = ::accept(listen_socket, (SOCKADDR*)&addrRemote, &nAddrLen);
                        FD_SET(client_socket, &redset);
                        printf("与主机 %s 建立连接\n", inet_ntoa(addrRemote.sin_addr));
                    }
                    else
                    {
                        printf("Too much connections!\n");
                        continue;
                    }
                }
                else//就绪文件描述符不是监听描述符,是通信描述符
                {
                    //接收信息
                    char rbuffer[1024] = { 0 };
                    int len = recv(redset.fd_array[i], rbuffer, 1024, 0);

                    if (len <= 0)
                    {
                        printf("The client %d has disconnected.\n", i);
                        FD_CLR(redset.fd_array[i], &redset);
                        shutdown(redset.fd_array[i], SD_BOTH);
                        closesocket(redset.fd_array[i]);
                        break;
                    }
                    printf("recive from client%d:\t%s\n", i, rbuffer);

                    //发送信息
                    char sbuffer[1024] = { 0 };
                    // 检查接收到的消息
                    if (strcmp(rbuffer, "计算从1到100的奇数和") == 0)
                    {
                        int sum = 0;
                        for (int j = 1; j <= 100; j += 2)
                        {
                            sum += j;
                        }
                        printf("send to client%d:\t1到100的奇数和是 %d\n", i, sum);

                        sprintf(sbuffer, "1到100的奇数和是 %d\n", sum);
                    }
                    else
                    {
                        printf("send to client%d:\tunknow!\n", i);
                        sprintf(sbuffer, "unknow!");
                    }

                    len = send(redset.fd_array[i], sbuffer, strlen(sbuffer), 0);
                    if (len == -1)
                    {
                        perror("send error");
                        exit(1);
                    }
                   
                }
            }
        }
    }

    closesocket(listen_socket);
    // 清理 Winsock
    WSACleanup();


    return 0;
}

类unix

#include<stdio.h>
#include<string.h>
#include<WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
int main()
{


    // 初始化 Winsock
    WSADATA wsaData;
    int result = WSAStartup(MAKEWORD(2, 2), &wsaData);

    if (result != 0)
    {
        printf("WSAStartup failed with error: %d\n", result);
        return 1;
    }

    //1.创建socket套接字
    SOCKET listen_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (INVALID_SOCKET == listen_socket)
    {
        printf("create listen socket failed!!! errcode: %d\n", GetLastError());
        WSACleanup();
        return -1;
    }

    //2.给socket绑定端口号
    struct sockaddr_in local = { 0 };

    local.sin_family = AF_INET;
    local.sin_port = htons(8080);//绑定端口
    //local.sin_addr.s_addr = htonl(INADDR_ANY);//接收全部网卡的数据 大小端转化
    local.sin_addr.s_addr = inet_addr("0.0.0.0");//接收全部网卡的数据 字符串ip转成整数ip
    if (-1 == bind(listen_socket, (struct sockaddr*)&local, sizeof(local)))
    {
        printf("bind failed!!! errcode: %d\n", GetLastError());
        WSACleanup();
        return -1;
    }

    //3.给socke开启监听属性,只用来接收连接
    if (-1 == listen(listen_socket, 10))
    {
        printf("start listen failed!!! errcode: %d\n", GetLastError());
        WSACleanup();
        return -1;
    }

    fd_set redset;
    //4、初始化文件描述符集合  
    FD_ZERO(&redset);
    //5、添加要检测的监听文件描述符    
    FD_SET(listen_socket, &redset);

    int maxfd = listen_socket;
    printf("This is SERVER!\n");
    while (1)
    {
        fd_set tmp = redset;
        /* 6、不停地检测文件描述符
              6.1 超时  select() = 0 再次检测或关闭套接字
              6.2 异常  select() = -1 异常处理
              6.3 成功  selct() >0
       */
        printf("selecting...\n");
        int ret = select(maxfd + 1, &tmp, NULL, NULL, NULL);
        if (ret <= 0)
        {
            printf("select failed!!! errcode: %d\n", GetLastError());
            closesocket(listen_socket);
            WSACleanup();
            return -1;
        }
        printf("select = %d\n", ret);
        //7、判断文件描述符属于哪一类
        if (FD_ISSET(listen_socket, &tmp))//7.1监听套接字的读缓冲区有数据
        {
            printf("\nWaiting for connect...\n");//不阻塞
            //等待客户端连接
            SOCKET client_socket = accept(listen_socket, NULL, NULL);
            if (INVALID_SOCKET == client_socket)
            {
                printf("Connect invalid!!!\n");
            }
            else
            {
                printf("Connected successfully!\n\n");
                //添加得到的通信文件描述符
                FD_SET(client_socket, &redset);
                maxfd = client_socket > maxfd ? client_socket : maxfd;
            }
        }

        for (int i = 0; i <= maxfd; ++i)
        {
            if (i != listen_socket && FD_ISSET(i, &tmp))// 7.2 i为通信文件描述符(套接字),且读缓冲区有数据
            {
                //接收信息
                char rbuffer[1024] = { 0 };
                int len = recv(i, rbuffer, 1024, 0);

                if (len <= 0)
                {
                    printf("The client %d has disconnected.\n", i);
                    FD_CLR(i, &redset);
                    shutdown(i, SD_BOTH);
                    closesocket(i);
                    break;
                }
                printf("recive from client%d:\t%s\n", i, rbuffer);

                //发送信息
                char sbuffer[1024] = { 0 };
                // 检查接收到的消息
                if (strcmp(rbuffer, "计算从1到100的奇数和") == 0)
                {
                    int sum = 0;
                    for (int j = 1; j <= 100; j += 2)
                    {
                        sum += j;
                    }
                    printf("send to client%d:\t1到100的奇数和是 %d\n", i, sum);

                    sprintf(sbuffer, "1到100的奇数和是 %d\n", sum);
                }
                else
                {
                    printf("send to client%d:\tunknow!\n", i);
                    sprintf(sbuffer, "unknow!");
                }

                send(i, sbuffer, strlen(sbuffer), 0);
            }
        }

    }
    closesocket(listen_socket);
    // 清理 Winsock
    WSACleanup();


    return 0;
}

client1、2、3

客户端代码没有改变,和socket客户端代码相同

#include<stdio.h>
#include<WinSock2.h>
#pragma comment(lib,"ws2_32.lib")

int main()
{
   
    // 初始化 Winsock
    WSADATA wsaData;
    int result = WSAStartup(MAKEWORD(2, 2), &wsaData);

    if (result != 0)
    {
        printf("WSAStartup failed with error: %d\n", result);
        return 1;
    }

    //1.创建socket套接字
    SOCKET client_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (INVALID_SOCKET == client_socket)
    {
        printf("create socket failed!!! errcode: %d\n", GetLastError());
        WSACleanup();
        return -1;
    }
    //2.连接服务器
    struct sockaddr_in target;//目标服务器的ip结构体
    target.sin_family = AF_INET;
    target.sin_port = htons(8080);
    target.sin_addr.s_addr = inet_addr("127.0.0.1");

    if (-1 == connect(client_socket, (struct sockaddr*)&target, sizeof(target)))
    {
        printf("connect server failed!!!\n");
        shutdown(client_socket, SD_BOTH);
        closesocket(client_socket);
        WSACleanup();
        return -1;
    }
    //3.开始通讯

    printf("This is  Cilent1.\n\n");
    while (1)
    {
        //发送信息
        printf("send:\t");
        char sbuffer[1024] = { 0 };
        scanf_s("%s", sbuffer,1024);
        send(client_socket, sbuffer, strlen(sbuffer), 0);

        //接收消息
        char rbuffer[1024] = { 0 };
        int ret = recv(client_socket, rbuffer, 1024, 0);
        if (ret <= 0)
        {
            break;//断开连接
        }
        printf("recive:\t%s\n", rbuffer);
    }

    //4.关闭连接
    shutdown(client_socket, SD_BOTH); shutdown(client_socket, SD_BOTH);
    closesocket(client_socket);

    // 清理 Winsock
    WSACleanup();


    return 0;
}

八、调试结果

  1. 打开服务端,再依次打开客户端,client1、client2、client3依次与服务端建立连接
  2. 三个客户端同时向服务器发送消息“计算从1到100的奇数和”,分别得到回应“ 1到100的奇数和是 2500”
  3. 关闭client3,服务端依然与client1、client2通信
  4. 重新开启client3,与服务端连接后继续通信

    5、上一个套接字的实验服务器明显不能与多个客户端并发通信,当client1与服务器连接之后,client2能与服务端连接但不能通信,client2阻塞等待通信。即若client2与服务端断开连接,client2才能和服务端通信。


九、相关链接

socket通信

https://www.cnblogs.com/wa2211lq/p/18509428

基于Select模型的通信仿真--win32编程代码:

https://www.cnblogs.com/wa2211lq/p/18553351

posted @ 2024-11-18 18:05  刘倩_网安2211  阅读(9)  评论(0编辑  收藏  举报