首页 > 其他分享 >基于Select模型的通信仿真

基于Select模型的通信仿真

时间:2024-11-18 17:57:08浏览次数:1  
标签:仿真 文件 socket 模型 描述符 fd Select printf select

目录

基于Select模型的通信仿真

一、实验要求

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

二、 编程环境

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

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

如果要使用Visual C++ 6.0,可参考
https://www.cnblogs.com/wa2211lq/p/18509428

三、流程图(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

标签:仿真,文件,socket,模型,描述符,fd,Select,printf,select
From: https://www.cnblogs.com/wa2211lq/p/18553178

相关文章

  • 清华姚班校友马腾宇,发布了他的首个多模态嵌入模型:「多模态检索」实现SOTA
    清华姚班校友马腾宇和他的团队,推出了自创业以来的首个多模态嵌入模型voyage-multimodal-3,而且发布即“SOTA”。据介绍,在对3个多模态检索任务(共20个数据集)进行评估时,voyage-multimodal-3比第二名平均高出了19.63%的检索准确率。这是为包含丰富视觉和文本的文档提供......
  • 使用 PyTorch 从头构建最小的 LLM 该项目构建了一个简单的字符级模型
    简介我开始尝试各种受Pokémon启发的猫名变体,试图赋予它独特、略带神秘感的氛围。在尝试了“Flarefluff”和“Nimblepawchu”等名字后,我突然想到:为什么不完全使用人工智能,让字符级语言模型来处理这个问题呢?这似乎是一个完美的小项目,还有什么比创建自定义Pokémon名......
  • 大模型实战(二):langchain+Ollama调用本地大模型实现RAG(保姆级)
    文章目录一、任务描述1.环境2.功能二、代码拆解1.导入包2.配置本地模型3.实例化embedding模型4.导入向量化知识库5.加入提示词6.定义查询方法7.问答三、总体代码一、任务描述由于显卡仍然较为昂贵,个人笔记本的硬件条件很难带动大模型,因此我们可以调用一......
  • 基于大模型LLM(包括ChatGPT)的应用开发与辅助编程技能
    《基于大模型LLM(包括ChatGPT)的应用开发与辅助编程技能》在当今数字化飞速发展的时代,大模型LLM(大型语言模型)如ChatGPT等正掀起一场前所未有的技术革命,它们在应用开发与辅助编程领域展现出了巨大的潜力和影响力,正逐渐改变着开发者们的工作模式与思维方式。大模型LLM在应用......
  • 多模态大模型LLM与AIGC前沿技术实战,基于训练数据和生成算法模型
    多模态大模型LLM与AIGC前沿技术实战,基于训练数据和生成算法模型在当今人工智能领域,多模态大模型LLM(大型语言模型)与AIGC(人工智能生成内容)正以前所未有的发展态势,引领着技术革新的浪潮。它们的强大能力背后,训练数据和生成算法模型起着至关重要的作用,深入探究这两方面并了解其在实......
  • AI 大模型应用开发实战营:全流程深度实践大模型应用开发落地
    AI大模型应用开发实战营:全流程深度实践大模型应用开发落地在人工智能蓬勃发展的时代,AI大模型以其强大的能力和广泛的适用性,正逐渐成为推动各行业变革的核心力量。然而,从理论认知到实际应用开发并成功落地,中间存在着诸多技术与实践的挑战。AI大模型应用开发实战营应运而生,旨在......
  • GPT 大模型 + AIGC 技术实操课:GPT 大模型部署使用与 AIGC 实战落地方案
    GPT大模型+AIGC技术实操课:GPT大模型部署使用与AIGC实战落地方案在人工智能蓬勃发展的今天,GPT大模型与AIGC(人工智能生成内容)技术成为了各界瞩目的焦点,它们正以前所未有的力量重塑着内容创作、智能交互等诸多领域的生态。而开展一门关于GPT大模型+AIGC技术实操课,帮助......
  • 在深度学习模型中添加灵敏度和精确度评价指标
    在深度学习模型中添加灵敏度和精确度评价指标引言在深度学习的图像分割任务中,评价模型性能的指标至关重要。常用的指标如IoU(IntersectionoverUnion)和Dice系数能够有效地衡量模型的分割效果。然而,单一的评价指标往往无法全面反映模型的性能。因此,本文将介绍如何在深度学......
  • AI大模型实战训练营-大模型原理及训练技巧、大模型微调的核心原理
    《AI大模型实战训练营:原理、训练技巧与微调核心原理探究》在当今科技飞速发展的时代,AI大模型已然成为了众多领域关注的焦点,它们正以强大的能力重塑着我们认知世界和解决问题的方式。而参与AI大模型实战训练营,则是深入了解其背后奥秘、掌握关键技术的绝佳途径。今天,就让我们一......
  • 基座模型、聊天模型和指令模型的区别
    目录三者的区别基座模型(BaseModel)聊天模型(ChatModel)指令模型(InstructionModel)总结示例基座模型(BaseModel)聊天模型(ChatModel)指令模型(InstructionModel)大白话解释基座模型(BaseModel)聊天模型(ChatModel)指令模型(InstructionModel)总结三者的区别基座模型(basemodel)、聊天模型......