• linux下IO模及其特点及select


    ftp实现

    模拟FTP核心原理:客户端连接服务器后,向服务器发送一个文件。文件名可以通过参数指定,服务器端接收客户端传来的文件(文件名随意),如果文件不存在自动创建文件,如果文件存在,那么清空文件然后写入。

    功能要求:

    1.项目基于tcp连接进行编写

    2.  客户端命令行传参,传入ip、port、文件路径,实现把指定目录下的文件发送到服务器

    3.  服务器接收并放到指定文件路径

    linux下IO模及其特点

    场景假设

    假设妈妈有一个孩子,孩子在房间里睡觉,妈妈需要及时获知孩子是否醒了,如何做?

    1.  进到房间陪着孩子一起睡觉,孩子醒了会吵醒妈妈:不累,但是不能干别的了

    2.  时不时进房间看一下:简单,空闲时间还能干点别的,但是很累

    3.  妈妈在客厅干活,小孩醒了他会自己走出房门告诉妈妈:互不耽误

    一、Linux下四种模型的特点: 

    阻塞式IO    非阻塞式IO    信号驱动IO(了解)     IO多路复用(帮助TCP实现并发)

    1、阻塞式IO(BIO)

    特点:简单、常用、效率低

    ● 当程序调用某些接口时,如果期望的动作无法触发,那么进程会进入阻塞态(等待状态,让出CPU的调度),当期望动作可以被触发了,那么会被唤醒,然后处理事务。

    ● 重点理解相对于进程而言的影响;

    ● 阻塞I/O模式是最普遍使用的I/O模式,大部分程序使用的都是阻塞模式的I/O 。

    ● 前面学习的很多读写函数在调用过程中会发生阻塞。

    1. 阻塞I/O 模式是最普遍使用的I/O 模式,大部分程序使用的都是阻塞模式的I/O 。
    2. 缺省情况下(及系统默认状态),套接字建立后所处于的模式就是阻塞I/O 模式。
    3. 学习的读写函数在调用过程中会发生阻塞相关函数如下:
    4. •读操作中的read、recv、recvfrom
    5.      读阻塞--》需要读缓冲区中有数据可读,读阻塞解除
    6. •写操作中的writesend
    7.      写阻塞--》阻塞情况比较少,主要发生在写入的缓冲区的大小小于要写入的数据量的情况下,写操作不进行任何拷贝工作,将发生阻塞,一旦缓冲区有足够的空间,内核将唤醒进程,将数据从用户缓冲区拷贝到相应的发送数据缓冲区。 
    8. 注意:sendto没有写阻塞
    9. 1)无sendto函数的原因:
    10. sendto不是阻塞函数,本身udp通信不是面向连接的,udp无发送缓冲区,即sendto没有发送缓冲区,send是有发送缓存区的,即sendto不是阻塞函数。
    11. 2)UDP不用等待确认,没有实际的发送缓冲区,所以UDP协议中不存在缓冲区满的情况,在UDP套接字上进行写操作永远不会阻塞。
    12. •其他操作:accept、connect

    udp与tcp缓存区  仅作为了解

    UDP通信没有发送缓存区, 它不保证数据的可靠性。因此,UDP通信是将数据尽快发送出去,不关心数据是否到达目标主机. 但是UDP有接受缓存区, 因为数据发送过快, 如果接收缓存区内数据已满, 则继续发送数据, 可能会出现丢包

    丢包出现原因:  接收缓存区满       网络拥堵, 传输错误    

    相比之下,TCP是一种面向连接的传输协议,它需要保证数据的可靠性和顺序性。TCP有发送缓存区和接收缓存区, 如果发送频率过快, 且内容小于发送缓存区的大小 , 可能会导致多个数据的粘包。如果发送的数据大于发送缓存区, 可能会导致拆包

    UDP不会造成粘包和拆包,  TCP不会造成丢包

    UDP是基于数据报文发送的,每次发送的数据包,在UDP的头部都会有固定的长度, 所以应用层能很好的将UDP的每个数据包分隔开, 不会造成粘包。

    TCP是基于字节流的, 每次发送的数据报,在TCP的头部没有固定的长度限制,也就是没有边界,那么很容易在传输数据时,把多个数据包当作一个数据报去发送,成为了粘包,或者传输数据时, 要发送的数据大于发送缓存区的大小,或者要发送的数据大于最大报文长度, 就会拆包;

    TCP不会丢包,因为TCP一旦丢包,将会重新发送数据包。(超时/错误重传)

    TCP: 

    UDP: 

    2、非阻塞式IO(NIO)

         特点:可以处理多路IO;需要轮询,浪费CPU资源

    1. •当我们将一个套接字设置为非阻塞模式,我们相当于告诉了系统内核:“当我请求的I/O 操作不能够马上完成,你想让我的进程进行休眠等待的时候,不要这么做,请马上返回一个错误给我。”
    2. •(引导着让大家说出来)当一个应用程序使用了非阻塞模式的套接字,它需要使用一个循环来不停地测试是否一个文件描述符有数据可读(称做polling)。---轮询
    3. •应用程序不停的polling 内核来检查是否I/O操作已经就绪。这将是一个极浪费CPU 资源的操作。
    4. •这种模式使用中不普遍。

    如何设置非阻塞

    1)  通过函数自带参数设置

    2)  通过设置文件描述符属性fcntl (file control)

    1. #include <unistd.h>
    2. #include <fcntl.h>
    3. int fcntl(int fd, int cmd, ...);
    4. 功能:
    5.    获取/改变文件属性(linux中一切皆文件)
    6. 文件描述符:stdin 0、stdout 1、stderr 2
    7. 参数:fd:文件描述符
    8.          cmd: 操作功能选项 (可以定义个变量,通过vi -t F_GETFL 来找寻功能赋值 )
    9.           F_GETFL:获取文件描述符的原有的状态信息 
    10.            //不需要第三个参数,返回值为获取到的属性
    11.           F_SETFL:设置文件描述符的状态信息 - 需要填充第三个参数
    12.          //需要填充第三个参数  O_RDONLY, O_RDWR ,O_WRONLY ,O_CREAT
    13.           O_NONBLOCK 非阻塞   O_APPEND追加
    14.           O_ASYNC 异步        O_SYNC  同步 
    15.           F_SETOWN:    可以用于实现异步通知机制。
    16.           //当文件描述符上发生特定事件时(例如输入数据到达),内核会向拥有该  文件描述符的进程发送 SIGIO 信号(异步),以便进程能够及时处理这些事件。
    17. 第三个参数:由第二个参数决定,set时候需要设置的值,get时候填0
    18. arg:文件描述符的属性     ----------同上参数,一般填0
    19. 返回值: 特殊选择:根据功能选择返回 (int 类型)   
    20.             其他:  成功0   失败: -1;
    21. 设置流程:
    22. int flag;//文件状态的标志 
    23. flag = fcntl(fd, F_GETFL); //读 
    24. flag |= O_NONBLOCK;//改  O_NONBLOCK = 0x00004000
    25. fcntl(fd, F_SETFL, flag);//

    3、信号驱动IO(异步IO模型  非重点)

    特点:异步通知模式,需要底层驱动的支持

    1. 操作系统中的同步与异步
    2. 在操作系统中,特别是在Linux中,同步和异步是描述I/O操作方式的两个概念。它们主要区分在于操作完成的通知方式和程序执行的流程。
    3. 同步(Synchronous):
    4. 同步I/O操作是指在执行I/O操作时,程序必须等待操作完成才能继续执行。在同步操作中,程序提交一个I/O请求后,操作系统会阻塞该程序,直到请求操作完成。此时,程序才能继续执行后续的代码。因此,同步操作会导致程序执行流程暂停,直至I/O操作完成。
    5. 同步I/O的例子:read(), write(), recv(), send() 等。
    6. 异步(Asynchronous):
    7. 异步I/O操作是指程序在发起I/O请求后,无需等待操作完成,可以继续执行其他任务。当异步I/O操作完成时,程序会通过某种方式(如回调函数、事件通知、信号等)得到通知。因此,异步操作使程序执行流程得以继续,而不必等待I/O操作完成。

    ●  通过信号方式,当内核检测到设备数据后,会主动给应用发送信号SIGIO。

    1. SIGIO
    2. 文件描述符准备就绪, 可以开始进行输入/输出操作.

    ● 应用程序收到信号后做异步处理即可。

    ● 应用程序需要把自己的进程号告诉内核,并打开异步通知机制。

    ● 标准模板

    1. //将APP进程号告诉驱动程序
    2. fcntl(fd, F_SETOWN, getpid());
    3. //使能异步通知
    4. int flag;
    5. flag = fcntl(fd, F_GETFL);
    6. flag |= O_ASYNC; //也可以用FASYNC标志
    7. fcntl(fd, F_SETFL, flag);
    8. signal(SIGIO, handler);

    signal信号处理相关函数

    头文件: #include 

            typedef void (*sighandler_t)(int);

            sighandler_t   signal(int signum, sighandler_t handler)

    功能:信号处理函数(注册信号)

    参数: int signum:要处理的信号(要修改的信号)

               sighandler_t handler: 函数指针: void(*handler)(int) (修改的功能:)

               handler:------void handler(int num) 自定义的信号处理函数指针

    返回值: 成功:设置之前的信号处理方式

    失败:   SIG_ERR

    用非阻塞方式监听鼠标的数据

    查看自己使用的鼠标:/dev/input    

    检查鼠标设备:sudo cat /dev/input/mouse0

     注意:执行的时候需要加sudo

    1. #include <stdio.h>
    2. #include <sys/types.h>
    3. #include <sys/stat.h>
    4. #include <fcntl.h>
    5. #include <signal.h>
    6. #include <unistd.h>
    7. int fd;
    8. #define N 64
    9. char buf[N] = {0};
    10. void handler(int sig)
    11. {
    12. int ret;
    13.     ret = read(fd, buf, N);
    14. if (ret < 0)
    15. {
    16. perror("READ ERR.");
    17. return;
    18. }
    19. else
    20. {
    21. printf("len= %d\n", ret);
    22. }
    23. }
    24. int main(int argc, char const *argv[])
    25. {
    26.     fd = open("/dev/input/mouse0", O_RDONLY);
    27. if (fd < 0)
    28. {
    29. perror("open err");
    30. return -1;
    31. }
    32. // 将APP进程号告诉驱动程序
    33. fcntl(fd, F_SETOWN, getpid());
    34. // 使能异步通知
    35. int flag;
    36.     flag = fcntl(fd, F_GETFL);
    37.     flag |= O_ASYNC; // 也可以用FASYNC标志
    38. fcntl(fd, F_SETFL, flag);
    39. signal(SIGIO, handler);
    40. while (1)
    41. {
    42. printf("-----------\n");
    43. sleep(1);
    44. }
    45. close(fd);
    46. return 0;
    47. }

    4.IO多路复用

    4.1、IO多路复用场景假设

    假设妈妈有三个孩子,分别不同的房间里睡觉,需要及时获知每个孩子是否醒了,如何做?

    1.  挨个房间跑

    4.2、IO多路复用机制 

     I/O多路复用  -  帮助TCP实现并发服务器

    1.  进程中若需要同时处理多路输入输出 ,在使用单进程和单线程的情况下, 可使用IO多路复用处理多个请求;

    2.  IO多路复用不需要创建新的进程和线程, 有效减少了系统的资源开销。

    场景就比如服务员给50个顾客点餐,分两步:

           顾客思考要吃什么(等待客户端数据发送)

           顾客想好了,开始点餐(接收客户端数据)

    要提高效率有几种方法? 

    1.   安排50个服务员   (类似于多进程/多线程实现服务器连接多个客户端,太占用资源)

    2.  哪个顾客想好了吃啥, 那个顾客来柜台点菜 (类似IO多路复用机制实现并发服务器)

    实现IO多路复用的方式:  select   poll   epoll

    基本流程是:

    1. 先构造一张有关文件描述符的表;  

    2. 清空表   

    3. 将你关心的文件描述符加入到这个表中;   

    4. 调用select函数。 

    5. 判断是哪一个或哪些文件描述符产生了事件(IO操作);   

    6. 做对应的逻辑处理;

    ● 使用I/O多路复用技术。其基本思想是:

    ○ 先构造一张有关描述符的表,然后调用一个函数。

    ○ 当这些文件描述符中的一个或多个已准备好进行I/O时函数才返回。

    ○ 函数返回时告诉进程哪个描述符已就绪,可以进行I/O操作。

    1. 基本流程:
    2. 1. 先构造一张有关文件描述符的表(集合、数组);
    3. 2. 将你关心的文件描述符加入到这个表中;
    4. 3. 然后循环调用一个函数。 select / poll          
    5. 4. 当这些文件描述符中的一个或多个已准备好进行I/O操作的时候
    6. 该函数才返回(阻塞)。                            
    7. 5. 判断是哪一个或哪些文件描述符产生了事件(IO操作);
    8. 6. 做对应的逻辑处理;

    4.3、select :用于监测是哪个或哪些文件描述符产生事件;

    1. #include<sys/select.h>   
    2. #include<sys/time.h>   
    3. #include<sys/types.h>   
    4. #include<unistd.h>
    5. int select(int nfds, fd_set *readfds, fd_set *writefds,
    6.                   fd_set *exceptfds, struct timeval *timeout);
    7.    功能:select用于监测是哪个或哪些文件描述符产生事件;
    8.    参数:
    9. nfds:监测的最大文件描述个数(文件描述符从0开始,这里是个数,记得+1)
    10. (这里是个数,使用的时候注意,与文件中最后一次打开的文件描述符所对应的值的关系是什么?)
    11.          readfds:  读事件集合; // 键盘鼠标的输入,客户端连接都是读事件
    12.          writefds: 写事件集合;  //NULL表示不关心
    13.          exceptfds:异常事件集合;  //NULL 表示不关心
    14.          timeout:   设为NULL,等待直到某个文件描述符发生变化;
    15.                        设为大于0的值,有描述符变化或超时时间到才返回。
    16.         超时时间检测:如果规定时间内未完成函数功能,返回一个超时的信息,我们可以根据该信息设定相应需求;
    17.    如果设置了超时检测时间:&tv
    18.       select返回值:
    19. <0 出错
    20. >0 表示有事件产生;
    21. ==0 表示超时时间已到;
    22. struct timeval
    23. {
    24. long    tv_sec; /* seconds */以秒为单位,指定等待时间
    25. long    tv_usec; /* microseconds */以毫秒为单位,指定等待时间
    26.  };
    27. void FD_CLR(int fdfd_set *set);//fd从表中清除
    28. int FD_ISSET(int fdfd_set *set);//判断fd是否在表中
    29. void FD_SET(int fdfd_set *set);//fd添加到表中
    30. void FD_ZERO(fd_set *set);//清空表1

    select特点:

    1. 一个进程最多只能监听1024个文件描述符 (32位)   [64位为 2048]

    2. select被唤醒之后要重新轮询(0-1023)一遍驱动,效率低(消耗CPU资源)

           3. select每次会清空未响应的文件描述符,每次都需要拷贝用户空间的表到内核空间,效率低,开销较大

       (0~3G是用户态,3G~4G是内核态,两个状态来回切换  拷贝是非常耗时,耗资源的)

    select机制(辅助理解): 

                  1. 头文件检测1024个文件描述符  0-1023

                  2. 在select中0~2存储标准输入、标准输出、标准出错    

            3. 监测的最大文件描述个数为fd+1(如果fd = 3,则最大为 4) :  //因为从0开始的    

                  4. select只对置1的文件描述符感兴趣                  假如事件产生,select检测时 , 产生的文件描述符会保持1,未产生事件的会置0; 

             5. select每次轮询都会清空表(置零的清空)   //需要在select前备份临时表

    练习1: 如何通过select实现 响应鼠标事件同时响应键盘事件

    1. #include <stdio.h>
    2. #include <unistd.h>
    3. #include <string.h>
    4. #include <sys/types.h>
    5. #include <sys/stat.h>
    6. #include <fcntl.h>
    7. #include <sys/select.h>
    8. //响应鼠标的时候, 打印鼠标事件 
    9. //输入键盘的时候, 打印键盘内容
    10. int main(int argc, char const *argv[])
    11. {
    12. //1.打开鼠标文件
    13. int fd = open("/dev/input/mouse0",O_RDONLY);
    14. if(fd < 0)
    15. {
    16. perror("open is err:");
    17. return -1;
    18. }
    19. //1.创建文件描述符的表
    20.     fd_set readfds,tempfds;
    21. //2.清空表
    22. FD_ZERO(&readfds);
    23. //3.添加关心的文件描述符
    24. FD_SET(0,&readfds);
    25. FD_SET(fd,&readfds);
    26. int maxfd = fd;
    27. char buf[128];
    28. while(1)
    29. {
    30.         tempfds = readfds;
    31. //4.select检测   阻塞
    32. select(maxfd+1,&tempfds,NULL,NULL,NULL);
    33. if(FD_ISSET(0,&tempfds))
    34. {
    35. //1.键盘
    36. fgets(buf,sizeof(buf),stdin);
    37. if(buf[strlen(buf)-1] == '\n')
    38.              buf[strlen(buf)-1] = '\0';
    39. printf("key: %s\n",buf);
    40. }
    41. if(FD_ISSET(fd,&tempfds))
    42. {
    43. //2.鼠标
    44. int ret = read(fd,buf,sizeof(buf));
    45.             buf[ret] = '\0';
    46. printf("mouse: %s\n",buf);
    47. }
    48. }
    49. close(fd);
    50. return 0;
    51. }

    练习:select实现客户端服务器全双工通信并发服务器的建立

    1. 在tcp的服务器端, 有两类文件描述符
    2. 监听的文件描述符
    3. 1.只需要有一个
    4. 2.不负责和客户端通信, 负责检测客户端的连接请求, 检测到之后调用accept就可以建立新的连接
    5. 通信的文件描述符
    6. 1.负责和建立连接的客户端通信
    7. 2.如果有N个客户端和服务器建立了新的连接, 通信的文件描述符就有N个,每个客户端和服务器都对应一个通信的文件描述符

    总结select实现IO多路复用特点

    1. 1. 一个进程最多只能监听1024个文件描述符 (千级别)
    2. 2select被唤醒之后需要重新轮询一遍驱动的poll函数,效率比较低(消耗CPU资源);
    3. 3select每次会清空表,每次都需要拷贝用户空间的表到内核空间,效率低(一个进程0~4G,0~3G是用户态,3G~4G是内核态,拷贝是非常耗时的);
    4. 4.跨平台

    (1)客户端              

    1. /*客户端创建代码 */
    2. #include <stdio.h>
    3. #include <sys/types.h> /* See NOTES */
    4. #include <sys/socket.h>
    5. #include <netinet/in.h>
    6. #include <netinet/ip.h> /* superset of previous */
    7. #include <arpa/inet.h>
    8. #include <unistd.h>
    9. #include <stdlib.h>
    10. #include <string.h>
    11. #include <sys/types.h>
    12. #include <sys/stat.h>
    13. #include <fcntl.h>
    14. #include <unistd.h>
    15. #include <string.h>
    16. #include <pthread.h>
    17. // #include "head.h"
    18. enum type_t
    19. {
    20.     login, //登录
    21.     chat,  //发送信息
    22.     quit,  //退出
    23. };
    24. typedef struct mag_t
    25. {
    26.     int type;       //功能
    27.     char name[32];  //ip
    28.     char text[128]; //内容
    29. } MSG_t;
    30. int main(int argc, char const *argv[])
    31. {
    32.     if (argc < 3)
    33.     {
    34.         printf("plase input ");
    35.         return -1;
    36.     }
    37.     //1.创建套接字,用于链接
    38.     int sockfd;
    39.     sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    40.     if (sockfd < 0)
    41.     {
    42.         perror("socket err");
    43.         return -1;
    44.     }
    45.     printf("sockfd:%d\n", sockfd);
    46.     //2.填充结构体
    47.     struct sockaddr_in saddr;
    48.     saddr.sin_family = AF_INET;//协议族
    49.     saddr.sin_port = htons(atoi(argv[2]));//端口
    50.     saddr.sin_addr.s_addr = inet_addr(argv[1]);//IP
    51.     MSG_t msg; //消息包
    52.     socklen_t len = sizeof(saddr); //结构体大小
    53.     int num=0;//交互次数
    54.     pid_t pid = fork();//创建父子进程
    55.     if (pid < 0)
    56.     {
    57.         perror("fork err");
    58.         return -1;
    59.     }
    60.     else if (pid == 0//子进程接收消息
    61.     {
    62.         while (1)
    63.         {
    64.             //接受信息
    65.             if (recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, &len) < 0)
    66.             {
    67.                 perror("recvfrom err");
    68.                 return -1;
    69.             }
    70.             printf("ip:%s 状态:%d 内容:%s\n", msg.name, msg.type, msg.text);
    71.         }
    72.     }
    73.     else //父进程发送消息
    74.     {
    75.         while (1)
    76.         {
    77.             strncpy(msg.name, "xiaoyang"8);//客户端昵称
    78.             //发送信息
    79.             memset(msg.text, 0, sizeof(msg.text)); //清空数组内容
    80.             printf("发送内容:");
    81.             fgets(msg.text, sizeof(msg.text), stdin); //从终端获取内容存放到数组中
    82.             if (strncmp(msg.text, "quit"4== 0)    //输入quit退出客户端
    83.             {
    84.                 msg.type = quit;//退出状态
    85.                 sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, len);
    86.                 exit(0);
    87.             }
    88.             if (msg.text[strlen(msg.text)] == '\0')
    89.             {
    90.                 msg.text[strlen(msg.text) - 1= '\0';
    91.             }
    92.             if (num == 0//第一次登入
    93.             {
    94.                 msg.type == login;//登录状态
    95.             }
    96.             else
    97.             {
    98.                 msg.type = chat;//交互状态
    99.             }
    100.             sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, len);//发送信号
    101.         }
    102.     }
    103.     close(sockfd);
    104.     return 0;
    105. }

    (2)  服务器

    1. /*服务器创建代码 */
    2. #include <stdio.h>
    3. #include <sys/types.h> /* See NOTES */
    4. #include <sys/socket.h>
    5. #include <netinet/in.h>
    6. #include <netinet/ip.h> /* superset of previous */
    7. #include <arpa/inet.h>
    8. #include <unistd.h>
    9. #include <stdlib.h>
    10. #include <string.h>
    11. enum type_t
    12. {
    13.     login,//登录
    14.     chat,//发送信息
    15.     quit,//退出
    16. };
    17. typedef struct mag_t
    18. {
    19.     int type;//功能
    20.     char name[32];//ip
    21.     char text[128];//内容
    22. } MSG_t;
    23. MSG_t msg;
    24. //链表节点结构体
    25. typedef struct node_t
    26. {
    27.     struct sockaddr_in addr;//ip地址
    28.     struct node_t *next;//链表下一个地址
    29. }list_t;
    30. int main(int argc, char const *argv[])
    31. {
    32.     if (argc < 2)
    33.     {
    34.         printf("plase input \n");
    35.         return -1;
    36.     }
    37.     //1.创建套接字,用于链接
    38.     int sockfd;
    39.     sockfd = socket(AF_INET,SOCK_DGRAM, 0);
    40.     if (sockfd < 0)
    41.     {
    42.         perror("socket err");
    43.         return -1;
    44.     }
    45.     printf("sockfd:%d\n", sockfd);
    46.     //2.绑定 ip+port 填充结构体
    47.     struct sockaddr_in saddr;
    48.     saddr.sin_family = AF_INET;            
    49.     saddr.sin_port = htons(atoi(argv[1])); 
    50.     saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    51.     socklen_t len = sizeof(saddr); //结构体大小
    52.     //bind绑定ip和端口
    53.     if (bind(sockfd, (struct sockaddr *)&saddr, len) < 0)
    54.     {
    55.         perror("bind err");
    56.         return -1;
    57.     }
    58.     printf("bind success\n");
    59.     // char buf[128= {0};
    60.     while (1)
    61.     {
    62.         //接收信息
    63.         if (recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, &len) < 0)
    64.         {
    65.             perror("recvfrom err");
    66.             return -1;
    67.         }
    68.         switch (msg.type)
    69.         {
    70.         case login:
    71.             Loginrecv();break;
    72.         case chat:
    73.             Chatrecv();break;
    74.         case quit:
    75.             Quitrecv();break;
    76.         }
    77.         //发送信息
    78.         printf("server:");
    79.         fgets(msg.text, sizeof(msg.text), stdin); //从终端获取内容存放到数组中
    80.         if (strncmp(msg.text, "quit"4== 0//输入quit退出客户端
    81.         {
    82.             break;
    83.         }
    84.         if (msg.text[strlen(msg.text)] == '\0')
    85.         {
    86.             msg.text[strlen(msg.text) - 1= '\0';
    87.         }
    88.         sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, len);
    89.     }
    90.     close(sockfd);
    91.     return 0;
    92. }
    93. void Chatrecv()//chat 型
    94. {
    95.         // printf("client ip:%s ,port:%d buf:%s\n", inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port),msg.text);
    96.         printf("ip:%s 状态:chat 内容:%s\n", msg.name,msg.text);
    97. }
    98. void Loginrecv()//login 型 首次链接
    99. {
    100. }
    101. void Quitrecv()//quit 退出
    102. {
    103.     //接收信息
    104.         if (recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, &len) < 0)
    105.         {
    106.             perror("recvfrom err");
    107.             return -1;
    108.         }
    109.         // printf("client ip:%s ,port:%d buf:%s\n", inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port),msg.text);
    110.         printf("ip:%s 状态:chat 内容:%s\n", msg.name,msg.text);
    111. }

  • 相关阅读:
    [tjctf 2023] crypto,pwn,rev部分
    关系型数据库之 MySQL 详解:从入门到精通【面试圣经】
    Asp .Net Core 系列:集成 Ocelot+Nacos+Swagger+Cors实现网关、服务注册、服务发现
    达芬奇调色:色彩理论入门
    每个设计师都应该有的五个性格,你具备几个?
    7数据结构与算法基础——软件设计师
    win环境安装Node.js的多种方式
    Kylin系统下离线安装依赖包
    TCP为什么需要三次握手和四次挥手?
    【ARM Coresight 系列文章 3.3 - ARM Coresight SWD 协议详细介绍】
  • 原文地址:https://blog.csdn.net/m0_74937538/article/details/134296564