linux下IO模及其特点及select

ftp实现

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

功能要求:

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

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

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

linux下IO模及其特点

场景假设

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

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

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

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

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

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

1、阻塞式IO(BIO)

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

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

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

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

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

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

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

如何设置非阻塞

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

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

复制代码
`#include <unistd.h>
#include <fcntl.h>
`int` `fcntl(int` fd`,` `int` cmd`,` `...);`
功能:
   获取`/`改变文件属性`(`linux中一切皆文件`)`
文件描述符:stdin 0、stdout 1`、`stderr 2
	参数:fd:文件描述符
         cmd: 操作功能选项 (可以定义个变量,通过vi -t F_GETFL 来找寻功能赋值 )
          F_GETFL:获取文件描述符的原有的状态信息 
           //不需要第三个参数,返回值为获取到的属性
          F_SETFL:设置文件描述符的状态信息 - 需要填充第三个参数
         //需要填充第三个参数  O_RDONLY, O_RDWR ,O_WRONLY ,O_CREAT
          O_NONBLOCK 非阻塞   O_APPEND追加
          O_ASYNC 异步        O_SYNC  同步 
          F_SETOWN:    可以用于实现异步通知机制。
          //当文件描述符上发生特定事件时(例如输入数据到达),内核会向拥有该  文件描述符的进程发送 SIGIO 信号(异步),以便进程能够及时处理这些事件。
第三个参数:由第二个参数决定,set时候需要设置的值,get时候填0
arg:文件描述符的属性     ----------同上参数,一般填0
        

返回值: 特殊选择:根据功能选择返回 (int 类型)   
            其他:  成功0   失败: -1;
设置流程:
    int flag;//文件状态的标志 
    flag = fcntl(fd, F_GETFL); //读 
    flag |= O_NONBLOCK;//改  O_NONBLOCK = 0x00004000
    fcntl(fd, F_SETFL, flag);//写
`

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

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

复制代码
`操作系统中的同步与异步
在操作系统中,特别是在Linux中,同步和异步是描述I`/`O操作方式的两个概念。它们主要区分在于操作完成的通知方式和程序执行的流程。

同步(Synchronous):

同步I`/`O操作是指在执行I`/`O操作时,程序必须等待操作完成才能继续执行。在同步操作中,程序提交一个I`/`O请求后,操作系统会阻塞该程序,直到请求操作完成。此时,程序才能继续执行后续的代码。因此,同步操作会导致程序执行流程暂停,直至I`/`O操作完成。
同步I`/`O的例子:`read(),` `write(),` `recv(),` `send()` 等。

异步(Asynchronous):
异步I`/`O操作是指程序在发起I`/`O请求后,无需等待操作完成,可以继续执行其他任务。当异步I`/`O操作完成时,程序会通过某种方式(如回调函数、事件通知、信号等)得到通知。因此,异步操作使程序执行流程得以继续,而不必等待I`/`O操作完成。

`

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

复制代码
SIGIO`
文件描述符准备就绪`,` 可以开始进行输入`/`输出操作`.`
`

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

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

标准模板

复制代码
//将APP进程号告诉驱动程序`
`fcntl(`fd`,` F_SETOWN`,` `getpid());`

`//使能异步通知`
`int` flag`;`
flag `=` `fcntl(`fd`,` F_GETFL`);`
flag `|=` O_ASYNC`;`  `//也可以用FASYNC标志`
`fcntl(`fd`,` F_SETFL`,` flag`);`

`signal(`SIGIO`,` handler`);`
`

signal信号处理相关函数

头文件: #include <signal.h>

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

复制代码
`#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
`int` fd`;`
#define N 64
`char` buf`[`N`]` `=` `{0};`
`void` `handler(int` sig`)`
`{`
    `int` ret`;`
    ret `=` `read(`fd`,` buf`,` N`);`
    `if` `(`ret `<` `0)`
    `{`
        `perror(`"READ ERR."`);`
        `return;`
    `}`
    `else`
    `{`
        `printf(`"len= %d\n"`,` ret`);`
    `}`
`}`
`int` `main(int` argc`,` `char` `const` `*`argv`[])`
`{`

    fd `=` `open(`"/dev/input/mouse0"`,` O_RDONLY`);`
    `if` `(`fd `<` `0)`
    `{`
        `perror(`"open err"`);`
        `return` `-1;`
    `}`
    `// 将APP进程号告诉驱动程序`
    `fcntl(`fd`,` F_SETOWN`,` `getpid());`
    `// 使能异步通知`
    `int` flag`;`
    flag `=` `fcntl(`fd`,` F_GETFL`);`
    flag `|=` O_ASYNC`;` `// 也可以用FASYNC标志`
    `fcntl(`fd`,` F_SETFL`,` flag`);`

    `signal(`SIGIO`,` handler`);`

    `while` `(1)`
    `{`
        `printf(`"-----------\n"`);`
        `sleep(1);`
    `}`
    `close(`fd`);`

    `return` `0;`
`}`
`

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.` 将你关心的文件描述符加入到这个表中`;`          
`3.` 然后循环调用一个函数。 select `/` poll          
`4.` 当这些文件描述符中的一个或多个已准备好进行I`/`O操作的时候
该函数才返回`(`阻塞`)`。                            
`5.` 判断是哪一个或哪些文件描述符产生了事件`(`IO操作`);`
`6.` 做对应的逻辑处理`;`
`

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

复制代码
`#include<sys/select.h>   
#include<sys/time.h>   
#include<sys/types.h>   
#include<unistd.h>
`int` `select(int` nfds`,` fd_set `*`readfds`,` fd_set `*`writefds`,`
                  fd_set `*`exceptfds`,` `struct` `timeval` `*`timeout`);`
   功能:select用于监测是哪个或哪些文件描述符产生事件`;` 
   参数:
    nfds:监测的最大文件描述个数(文件描述符从0开始,这里是个数,记得+1)
(这里是个数,使用的时候注意,与文件中最后一次打开的文件描述符所对应的值的关系是什么?)
         readfds:  读事件集合; // 键盘鼠标的输入,客户端连接都是读事件
         writefds: 写事件集合;  //NULL表示不关心
         exceptfds:异常事件集合;  //NULL 表示不关心
         timeout:   设为NULL,等待直到某个文件描述符发生变化;
                       设为大于0的值,有描述符变化或超时时间到才返回。
        超时时间检测:如果规定时间内未完成函数功能,返回一个超时的信息,我们可以根据该信息设定相应需求;
   如果设置了超时检测时间:`&`tv
      select返回值:
         `<0` 出错
        `>0` 表示有事件产生`;`
        `==0` 表示超时时间已到`;`

 `struct` `timeval` 
 `{`
      `long`    tv_sec`;`      `/* seconds */`以秒为单位,指定等待时间
      `long`    tv_usec`;`    `/* microseconds */`以毫秒为单位,指定等待时间
` };`
 `void` `FD_CLR(int` fd`,` fd_set `*`set`);//将fd从表中清除`
 `int`  `FD_ISSET(int` fd`,` fd_set `*`set`);//判断fd是否在表中`
 `void` `FD_SET(int` fd`,` fd_set `*`set`);//将fd添加到表中`
 `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实现 响应鼠标事件同时响应键盘事件
复制代码
`#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>

`//响应鼠标的时候, 打印鼠标事件 `
`//输入键盘的时候, 打印键盘内容`

`int` `main(int` argc`,` `char` `const` `*`argv`[])` 
`{`
    `//1.打开鼠标文件`
    `int` fd `=` `open(`"/dev/input/mouse0"`,`O_RDONLY`);`
    `if(`fd `<` `0)`
    `{`
        `perror(`"open is err:"`);`
        `return` `-1;`
    `}`
    `//1.创建文件描述符的表`
    fd_set readfds`,`tempfds`;`
    `//2.清空表`
    `FD_ZERO(&`readfds`);`
    `//3.添加关心的文件描述符`
    `FD_SET(0,&`readfds`);`
    `FD_SET(`fd`,&`readfds`);`
    
    `int` maxfd `=` fd`;`

    `char` buf`[128];`
    `while(1)`
    `{`
        tempfds `=` readfds`;`
        `//4.select检测   阻塞`
        `select(`maxfd`+1,&`tempfds`,`NULL`,`NULL`,`NULL`);`

        `if(FD_ISSET(0,&`tempfds`))`
        `{`
          `//1.键盘`
          `fgets(`buf`,sizeof(`buf`),`stdin`);`
          `if(`buf`[strlen(`buf`)-1]` `==` '\n'`)`
             buf`[strlen(`buf`)-1]` `=` '\0'`;`
           `printf(`"key: %s\n"`,`buf`);`
        `}`
        `if(FD_ISSET(`fd`,&`tempfds`))`
         `{`
            `//2.鼠标`
            `int` ret `=` `read(`fd`,`buf`,sizeof(`buf`));`
            buf`[`ret`]` `=` '\0'`;`
            `printf(`"mouse: %s\n"`,`buf`);`
         `}`

    `}`
    `close(`fd`);`
    `return` `0;`
`}`


`
练习:select实现客户端服务器全双工通信并发服务器的建立
复制代码
`在tcp的服务器端`,` 有两类文件描述符

监听的文件描述符
`1.`只需要有一个
`2.`不负责和客户端通信`,` 负责检测客户端的连接请求`,` 检测到之后调用accept就可以建立新的连接

通信的文件描述符
`1.`负责和建立连接的客户端通信
`2.`如果有`N`个客户端和服务器建立了新的连接`,` 通信的文件描述符就有`N`个,每个客户端和服务器都对应一个通信的文件描述符
`
总结select实现IO多路复用特点
复制代码
`1. 一个进程最多只能监听1024个文件描述符 (千级别)
2. select被唤醒之后需要重新轮询一遍驱动的poll函数,效率比较低(消耗CPU资源);
3. select每次会清空表,每次都需要拷贝用户空间的表到内核空间,效率低(一个进程0~4G,0~3G是用户态,3G~4G是内核态,拷贝是非常耗时的);
4.跨平台
`
(1)客户端
复制代码
`/*客户端创建代码 */
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
// #include "head.h"
enum type_t
{
    login, //登录
    chat,  //发送信息
    quit,  //退出
};
typedef struct mag_t
{
    int type;       //功能
    char name[32];  //ip
    char text[128]; //内容
} MSG_t;
int main(int argc, char const *argv[])
{
    if (argc < 3)
    {
        printf("plase input <ip><port>");
        return -1;
    }
    //1.创建套接字,用于链接
    int sockfd;
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("socket err");
        return -1;
    }
    printf("sockfd:%d\n", sockfd);
    //2.填充结构体
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;//协议族
    saddr.sin_port = htons(atoi(argv[2]));//端口
    saddr.sin_addr.s_addr = inet_addr(argv[1]);//IP
    MSG_t msg; //消息包
    socklen_t len = sizeof(saddr); //结构体大小
    int num=0;//交互次数
    pid_t pid = fork();//创建父子进程
    if (pid < 0)
    {
        perror("fork err");
        return -1;
    }
    else if (pid == 0) //子进程接收消息
    {
        while (1)
        {
            //接受信息
            if (recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, &len) < 0)
            {
                perror("recvfrom err");
                return -1;
            }
            printf("ip:%s 状态:%d 内容:%s\n", msg.name, msg.type, msg.text);
        }
    }
    else //父进程发送消息
    {
        while (1)
        {
            strncpy(msg.name, "xiaoyang", 8);//客户端昵称
            //发送信息
            memset(msg.text, 0, sizeof(msg.text)); //清空数组内容
            printf("发送内容:");
            fgets(msg.text, sizeof(msg.text), stdin); //从终端获取内容存放到数组中
            if (strncmp(msg.text, "quit", 4) == 0)    //输入quit退出客户端
            {
                msg.type = quit;//退出状态
                sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, len);
                exit(0);
            }
            if (msg.text[strlen(msg.text)] == '\0')
            {
                msg.text[strlen(msg.text) - 1] = '\0';
            }
            if (num == 0) //第一次登入
            {
                msg.type == login;//登录状态
            }
            else
            {
                msg.type = chat;//交互状态
            }
            sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, len);//发送信号
        }
    }
    close(sockfd);
    return 0;
}

`
(2) 服务器
复制代码
`/*服务器创建代码 */
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
enum type_t
{
    login,//登录
    chat,//发送信息
    quit,//退出
};
typedef struct mag_t
{
    int type;//功能
    char name[32];//ip
    char text[128];//内容
} MSG_t;
MSG_t msg;
//链表节点结构体
typedef struct node_t
{
    struct sockaddr_in addr;//ip地址
    struct node_t *next;//链表下一个地址
}list_t;
int main(int argc, char const *argv[])
{
    if (argc < 2)
    {
        printf("plase input <ip><port>\n");
        return -1;
    }
    //1.创建套接字,用于链接
    int sockfd;
    sockfd = socket(AF_INET,SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("socket err");
        return -1;
    }
    printf("sockfd:%d\n", sockfd);
    //2.绑定 ip+port 填充结构体
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;            
    saddr.sin_port = htons(atoi(argv[1])); 
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    socklen_t len = sizeof(saddr); //结构体大小
    //bind绑定ip和端口
    if (bind(sockfd, (struct sockaddr *)&saddr, len) < 0)
    {
        perror("bind err");
        return -1;
    }
    printf("bind success\n");
    // char buf[128] = {0};
    while (1)
    {
        //接收信息
        if (recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, &len) < 0)
        {
            perror("recvfrom err");
            return -1;
        }
        switch (msg.type)
        {
        case login:
            Loginrecv();break;
        case chat:
            Chatrecv();break;
        case quit:
            Quitrecv();break;
        }
        
        //发送信息
        printf("server:");
        fgets(msg.text, sizeof(msg.text), stdin); //从终端获取内容存放到数组中
        if (strncmp(msg.text, "quit", 4) == 0) //输入quit退出客户端
        {
            break;
        }
        if (msg.text[strlen(msg.text)] == '\0')
        {
            msg.text[strlen(msg.text) - 1] = '\0';
        }
        sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, len);
    }
    close(sockfd);
    return 0;
}
void Chatrecv()//chat 型
{
        // printf("client ip:%s ,port:%d buf:%s\n", inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port),msg.text);
        printf("ip:%s 状态:chat 内容:%s\n", msg.name,msg.text);

}
void Loginrecv()//login 型 首次链接
{
    

}
void Quitrecv()//quit 退出
{
    //接收信息
        if (recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, &len) < 0)
        {
            perror("recvfrom err");
            return -1;
        }
        // printf("client ip:%s ,port:%d buf:%s\n", inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port),msg.text);
        printf("ip:%s 状态:chat 内容:%s\n", msg.name,msg.text);
}
`
相关推荐
一起逃去看海吧35 分钟前
dify-03
java·linux·开发语言
fengyehongWorld36 分钟前
Linux 根据端口进行的相关查询
linux
lihao lihao39 分钟前
linux匿名管道
linux·运维·服务器
うちは止水42 分钟前
weston出图调试
linux·wayland·weston
STDD43 分钟前
Farming Simulator 25(模拟农场 25) Linux 专服搭建完全指南
linux·运维·javascript
社交怪人1 小时前
【范围判断】信息学奥赛一本通C语言解法(题号2052)
c语言
STDD1 小时前
Glances:跨平台系统资源监控,浏览器实时查看服务器状态
运维·服务器
好好风格1 小时前
宝塔面板 HTTPS 端口证书不生效排查记录
linux·运维·nginx
Forget_85502 小时前
HCIA——计算机网络诞生与发展
服务器·网络·计算机网络