Linux下的 四种IO模型、IO 多路复用实现 TCP 并发

四种 IO 模型

阻塞式 IO

最常用,最简单,最普遍的 IO,但效率低。
目前,有阻塞功能的函数如下:
读阻塞: read,recv,recvfrom
写阻塞: write,send
其他: accept,connect
TCP (有连接):有发送缓存区,有接收缓存区,所以 TCP编程 使用 sendto 会发生阻塞
UDP(无连接):没有发送缓存区,但有接收缓存区,所以 UDP编程 使用 sendto 不阻塞

UDP 通信没有发送缓存区 是因为 UDP 是一种无连接的传输协议,它不保证数据的可靠性和顺序性。因此,UDP 通信不需要维护连接状态和缓存数据,而是将数据尽快发送出去,不关心数据是否到达目标主机或者是否按照发送顺序到达。
相比之下,TCP 是一种面向连接的传输协议,它需要维护连接状态、保证数据的可靠性和顺序性。为了实现这些功能,TCP 通信需要维护缓存区,用于存储已发送但未确认的数据、已接收但未交付的数据以及已交付但未被应用程序读取的数据。这样可以确保数据在网络中的可靠传输和正确的顺序交付。

非阻塞式 IO

设置方式一:

可以处理多路IO,但是一直轮循,浪费大量 CPU 资源。
Linux下的 四种IO模型、IO 多路复用实现 TCP 并发_第1张图片

设置方式二:

#include  
#include 
int fcntl (int fd, int cmd, ... /* arg */);

功能:设置文件描述符的属性
参数: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 同步 
                                //  O_NOATIME 读取文件时不更新文件访问时间
    arg:文件描述符的属性   如果需要设置(SET)文件描述符的状态,则需要该参数
返回值: 
	特殊选择:根据功能选择返回 (int 类型)   
    其他:  成功:0   失败: -1
步骤:

1)获取文件描述符的原属性(以标准输入为例,标准输入原本具有阻塞的功能):
int flag = fcntl(0, F_GETFL); // 获取文件描述符原有信息后,保存在 flag 变量内
2)修改对应的位 为 nonblock(非阻塞,00004000)
flag |= O_NONBLOCK; ( flag = flag | O_NONBLOCK)
3)将修改好的属性写回去(将标准输入的 阻塞 改为 非阻塞)
fcntl (0, F_SETFL, flag); // 给文件描述符 添加的新属性
Linux下的 四种IO模型、IO 多路复用实现 TCP 并发_第2张图片

信号驱动 IO

异步通知模式,需要底层驱动的支持。
1> 通过信号的方式,当内核检测到设备数据后,会主动给应用发送信号SIGIO.
2> 应用程序收到信号后做异步处理即可,需要把自己的进程号告诉内核,并打开异步通知机制。

异步通知:异步通知是一种非阻塞的通知机制,发送方发送通知后不需要等待接收方的响应或确认。通知发送后,发送方可以继续执行其他操作,而无需等待接收方处理通知。接收方在自己的时间内处理通知。通常使用回调函数或事件驱动的方式实现。

// 1.设置将APP进程号提交给内核驱动
fcntl(fd, F_SETOWN, getpid());		// F_SETOWN 将进程号交给内核驱动  
                            		// getgid 获取进程号
// 2.设置异步通知
int flags;
flags = fcntl(fd, F_GETFL); 		// 获取原属性
flags |= O_ASYNC;       			// 设置异步   O_ASYNC 通知
fcntl(fd, F_SETFL, flags);  		// 修改属性设置

// 3.signal 捕捉 SIGIO 信号驱动(自定义信号驱动)
signal(SIGIO, handler);



F_SETOWN:  当文件描述符发生特定事件的时候, 
    内核会给该文件描述符的进程号发送SIGIO信号,使进程能够及时处理这些事件
#include 
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler); 

功能:信号处理函数(注册信号)
参数:int signum:要处理的信号(要修改的信号)
      sighandler_t handler: 函数指针: void(*handler)(int) 
      SIG_IGN:忽略该信号。
      SIG_DFL:采用系统默认方式处理信号。
      handler:------void handler(int num) 自定义的信号处理函数指针
返回值: 成功:设置之前的信号处理方式
		失败:SIG_ERR

练习:(mouse)

操作鼠标设备,当有输入的时候获取输入数据,没有输入时循环输出“Hola~”。

#include 
#include 
#include 
#include 
#include 
#include 

int fd;

void handler(int arg){
    char buf[256] = {};
    int len = read(fd, buf, sizeof(buf)-1);
    buf[len] = '\0';
    printf("Mouse: %s\n", buf);
    sleep(2);
}

int main(int argc, char const *argv[])
{
    fd = open("/dev/input/mouse0", O_RDONLY);
    if (fd < 0){
        perror("Failed to open");
        return -1;
    }

    fcntl(fd, F_SETOWN, getpid());      // 将文件描述符的进程号告知底层驱动

    int flag = fcntl(fd, F_GETFL);      // 设置文件描述符的异步属性
    flag |= O_ASYNC;
    fcntl(fd, F_SETFL, flag);

    while (1){
        printf("Hola~\n");
        signal(SIGIO, handler);			// signal 捕捉 SIGIO 信号驱动
        sleep(2);
    }
    
    close(fd);
    return 0;
}

运行结果如下:
Linux下的 四种IO模型、IO 多路复用实现 TCP 并发_第3张图片

IO 多路复用(可用于 实现 TCP 并发)

有三种方式: select、poll、epoll。

Select

Linux下的 四种IO模型、IO 多路复用实现 TCP 并发_第4张图片

特点:

1、一个进程最多能监听 1024 个文件描述符( 0 - 1023),个数可通过内核源码修改
2、select 被唤醒之后要重新轮循一遍驱动(0 到 x, x ∈ \isin (0, 1023]),效率低 且 消耗 CPU资源
3、select 每次都会清空表,每次都需要拷贝用户空间的表到内核空间,效率低 且 消耗 CPU资源
(0~3G 的用户态 和 3G~4G 的内核态,两个状态来回切换 、拷贝 是非常耗时、耗资源的)

规则:

1、头文件检测 1024 个文件描述符(0-1023)
2、在 select 中 0~2 存储标准输入、标准输出、标准出错
3、监测的最大文件描述个数为 fd+1(如果 fd = 3,则最大为 4)
4、产生事件的文件描述符被置1;没有产生事件的描述符被置0(轮循)
5、select 每次轮循都会清空表(将置零的描述符清空) // 需要在 select 前备份临时表
Linux下的 四种IO模型、IO 多路复用实现 TCP 并发_第5张图片

基本流程:

1)先构造一张有关文件描述符的表(集合、数组); fd_set readfds, tempfds;
2)清空表; FD_ZERO(&readfds);
3)将关心的文件描述符添加到这个表中; FD_SET(fd, &readfds); int maxfd = last_fd;
4)备份表; tempfds = readfds;
5)调用 select 函数,当这些文件描述符已准备好进行 I/O 操作的时候,函数才返回(阻塞);
select(maxfd+1, &tempfds, NULL, NULL, NULL);
6)判断是哪一个或哪些文件描述符产生了事件(IO操作),做对应的逻辑处理;
if (FD_ISSET(fd, &tempfds)){…}

/* According to POSIX.1-2001, POSIX.1-2008 */
#include 

/* According to earlier standards */
#include 
#include 
#include 

int select(int nfds, fd_set *readfds, fd_set *writefds,\
 				fd_set *exceptfds, struct timeval *timeout);

功能:监测哪些文件描述符产生事件,阻塞等待产生.
参数:	nfds:    	监测的最大文件描述个数(文件描述符从0开始,这里是个数,记得+1)
        readfds:  	读事件集合; 		// 键盘鼠标的输入,客户端连接都是读事件
        writefds: 	写事件集合;  	// NULL表示不关心
        exceptfds:	异常事件集合;  	// NULL表示不关心
        timeout:   超时检测 1       // 如果不做超时检测:传 NULL
超时时间检测: 设定好时间,当程序执行到该语句时,如果规定时间内未完成函数功能, 
            返回一个超时的信息,可以根据该信息设定相应需求;
返回值:  < 0 出错;            > 0 表示有事件产生;
如果设置了超时检测时间:&tv
         < 0 出错;            > 0 表示有事件产生;      	== 0 表示超时,时间已到;  

结构体如下:                     
struct timeval {
   long    tv_sec;         // 以秒为单位,指定等待时间
   long    tv_usec;        // 以毫秒为单位,指定等待时间
};

       
void FD_CLR (int fd, fd_set *set);  		// 将 set 集合中的 fd 清除掉 
int  FD_ISSET (int fd, fd_set *set); 		// 判断 fd 是否在set集合中产生事件
void FD_SET (int fd, fd_set *set); 			// 将fd加入到集合中
void FD_ZERO (fd_set *set); 				// 清空集合

Linux下的 四种IO模型、IO 多路复用实现 TCP 并发_第6张图片
在这里插入图片描述

练习:(mouse & keyboard)

输入鼠标响应鼠标事件, 输入键盘响应键盘事件。

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, char const *argv[])
{
    int fd = open("/dev/input/mouse0", O_RDONLY);
    if (fd < 0){
        perror("Failed to open");
        return -1;
    }

    fd_set readfds, tempfds;		// 1. 先构造一张有关文件描述符的表
    FD_ZERO(&readfds);				// 2. 清空表
    FD_SET(0, &readfds);			// 3. 将关心的文件描述符添加到表中
    FD_SET(fd, &readfds);
    int maxfd = fd;

    char buf[256] = {};
    while (1){
        tempfds = readfds;			// 4. 备份表
        select(maxfd+1, &tempfds, NULL, NULL, NULL);	// 5. 调用 select 函数

        if (FD_ISSET(0, &tempfds)){						// 6. 产生事件,进行相应处理
            fgets(buf, sizeof(buf), stdin);
            if (buf[strlen(buf)-1] == '\n')
                buf[strlen(buf)-1] = '\0';
            printf("Keyboard: %s\n", buf);
        }
        if (FD_ISSET(fd, &tempfds)){
            int len = read(fd, buf, sizeof(buf));
            buf[len] = '\0';
            printf("Mouse: %s\n", buf);
        }
    }
    
    return 0;
}

运行结果如下:
Linux下的 四种IO模型、IO 多路复用实现 TCP 并发_第7张图片

练习:(keyboard & sockfd)

同时检测 键盘输入 和 sockfd 事件 —— TCP实现同时连接多个客户端。

// ser_sele_ks.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include  
#include     
#include 
#include 

int main(int argc, char const *argv[])
{
    if (argc != 2){
        printf("Please input %s . \n", argv[0]);
        return -1;
    }

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0){
        perror("Failed to create a socket");
        return -1;
    }

    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    saddr.sin_port = htons(atoi(argv[1]));

    if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){
        perror("Failed to bind");
        return -1;
    }

    if(listen(sockfd, 6) < 0){
        perror("Failed to listen");
        return -1;
    }

    fd_set readfd, tempfd;
    FD_ZERO(&readfd);
    FD_SET(0, &readfd);
    FD_SET(sockfd, &readfd);
    int maxfd = sockfd;

    char buf[256] = {};
    int length = sizeof(caddr);
    int accfd;
    while (1){
        tempfd = readfd;
        select(maxfd+1, &tempfd, NULL, NULL, NULL);

        if (FD_ISSET(0, &tempfd)){
            fgets(buf, sizeof(buf), stdin);
            if (buf[strlen(buf)-1] == '\n')
                buf[strlen(buf)-1] == '\0';
            // printf("%s\n", buf);

            for (int i = 4; i <= maxfd; i++)
                if (FD_ISSET(i, &readfd))
                	send(i, buf, sizeof(buf), 0);
        }
        if (FD_ISSET(sockfd, &tempfd)){
            accfd = accept(sockfd, (struct sockaddr *)&caddr, &length);
            if (accfd < 0){
                perror("Failed to accept");
                return -1;
            }
            printf("Client IPv4: %s\t\tport:%d\n", 
                   inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));

            FD_SET(accfd, &readfd);
            if (maxfd < accfd)
                maxfd = accfd;
        }
        for (int i = 4; i <= maxfd; i++){
            if (FD_ISSET(i, &tempfd)){
                int receiver = recv(accfd, buf, sizeof(buf), 0);
                if (receiver < 0){
                    perror("Failed to receive (server)");
                    return -1;
                } else if (receiver == 0){
                    printf("Client[%d] exited. \n", i);
                    close(i);
                    FD_CLR(i, &readfd);
                    if(i == maxfd)
                        maxfd--;
                } else {
                    printf("No_%d: %s", i, buf);		// 自己打印
                    
                    for(int j = 4; j <= maxfd; j++){	// 转发给除自己以外其他端
                        if (j == i)
                            continue;
                        if (FD_ISSET(j, &readfd))
                            send(j, buf, sizeof(buf), 0);
                    }
                } 
            } 
        }
    }
    close(sockfd);

    return 0;
}

Linux下的 四种IO模型、IO 多路复用实现 TCP 并发_第8张图片

// cli_sele_ks.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include  
#include     
#include 
#include 

int main(int argc, char const *argv[])
{
    if (argc != 3){
        printf("Please input %s  . \n", argv[0]);
        return -1;
    }

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0){
        perror("Failed to create a socket");
        return -1;
    }

    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = inet_addr(argv[1]);
    saddr.sin_port = htons(atoi(argv[2]));

    if (connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){
        perror("Failed to connect");
        return -1;
    }

    fd_set readfd, tempfd;
    FD_ZERO(&readfd);
    FD_SET(0, &readfd);
    FD_SET(sockfd, &readfd);
    int maxfd = sockfd;

    char buf[256] = {};
    while (1){

        tempfd = readfd;
        select(maxfd+1, &tempfd, NULL, NULL, NULL);

        if (FD_ISSET(0, &tempfd)){
            fgets(buf, sizeof(buf), stdin);
            if (buf[strlen(buf)-1] == '\n')
                buf[strlen(buf)-1] == '\0';
            send(sockfd, buf, sizeof(buf), 0);
        }  
        if (FD_ISSET(sockfd, &tempfd)){
            int receiver = recv(sockfd, buf, sizeof(buf), 0);
            if (receiver < 0){
                perror("Failed to receive (client)");
                return -1;
            } else {
                printf("Server: %s", buf);
            }
        }     
    }
    close(sockfd);
    return 0;
}

实现效果如下:
Linux下的 四种IO模型、IO 多路复用实现 TCP 并发_第9张图片

Poll

特点:

1、取消 select 对文件描述符个数的限制;
(根据 poll 函数 的第一个参数来定,如果监听的事件为 n 个,则结构体数组元素个数为 n)
2、poll 被唤醒之后需要重新轮循一遍驱动,效率比较低(消耗CPU);
3、poll 不需重新构造文件描述符表(也不需清空表),只需要从用户空间向内核空间拷贝一次数据
(效率相对比较高)。

基本流程:

1)先创建 结构体数组; struct pollfd fds[N];
2)添加文件描述符以及触发方式; fds[i].fd = 文件描述符; fds[i].events = 宏事件;
3)保存数组内最后一个有效元素的下标; int last = …;
4)调用 poll 函数; poll(fds, last+1, -1);
5)判断表内的文件描述符是否被触发; if(fds[i].revents == 宏事件){…}
6)不同的文件描述符触发不同事件。 if(fds[i].fd == 文件描述符1){…} else if {…} …

#include
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

功能: 监视并等待多个文件描述符的属性变化
参数:
	1.  struct pollfd *fds:   关心的文件描述符数组,大小自己定义
        若想检测的文件描述符较多,则建立结构体数组 struct pollfd fds[N]; 
        struct pollfd{
	            int fd;	 			// 文件描述符
	            short events;		// 等待的事件触发条件----POLLIN(读事件),POLLOUT(写)
	            short revents;		// 实际发生的事件(未产生事件: 0 ))
            }
	2.  nfds:    最大文件描述符个数
	3.  timeout: 超时检测 (毫秒级):1000 == 1s      如果-1,阻塞     如果0,不阻塞
 
返回值:  < 0 出错		> 0 表示有事件产生;
    如果设置了超时检测:&tv,
    	 < 0 出错		> 0 表示有事件产生;	      == 0 表示超时时间已到;

Linux下的 四种IO模型、IO 多路复用实现 TCP 并发_第10张图片
Linux下的 四种IO模型、IO 多路复用实现 TCP 并发_第11张图片
在这里插入图片描述

poll_server.c
#include 
#include 
#include 
#include  
#include  
#include     
#include 
#include 
#include 
#include 

int main(int argc, char const *argv[])
{
    if (argc != 2){
        printf("Please input %s . \n", argv[0]);
        return -1;
    }
    
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0){
        perror("Failed to create a socket");
        return -1;
    }
    printf("sockfd: %d\n", sockfd);

    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");

    if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){
        perror("Failed to bind");
        return -1;
    }

    if (listen(sockfd, 6) < 0){
        perror("Failed to listen");
        return -1;
    }

    struct pollfd fds[64] = {};		// int last = -1;
    fds[0].fd = 0;					// fds[++last].fd = 0;
    fds[0].events = POLLIN;			// fds[last].events = POLLIN; 
    fds[1].fd = sockfd;				// fds[++last].fd = sockfd;
    fds[1].events = POLLIN;    		// fds[last].events = POLLIN; 
    int last = 1;					// 此时 last == 1,此句省略

    char buf[256] = {};
    int accfd, receiver;
    struct sockaddr_in caddr;
    int length = sizeof(caddr);
    while (1){
        int getpoll = poll(fds, last+1, -1);
        if (getpoll < 0){
            perror("Failed to poll");
            return -1;
        }
        for (int i = 0; i < last+1; i++){
            if (fds[i].revents == POLLIN){
                if (fds[i].fd == 0){
                    fgets(buf, sizeof(buf), stdin);
                    if (buf[strlen(buf)-1] == '\n')
                        buf[strlen(buf)-1] = '\0';
                    printf("Keyboard: %s\n", buf);
                }
                else if (fds[i].fd == sockfd){
                    accfd = accept(sockfd, (struct sockaddr *)&caddr, &length);
                    if (accfd < 0){
                        perror("Failed to accept");
                        return -1;
                    }
                    printf("Client IPv4: %s\t\tport:%d\n", 
                           inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));

                    last++;
                    fds[last].fd = accfd;
                    fds[last].events = POLLIN;
                }
                else if (fds[i].fd == accfd){						// 可以写 else
                    receiver = recv(accfd, buf, sizeof(buf), 0);	// recv(fds[i].fd...
                    if (receiver < 0){
                        perror("Failed to receive");
                        return -1;
                    } else if (receiver == 0){
                        printf("Client[%d] exited. \n", i);
                        close(fds[i].fd);
                        fds[i] = fds[last];
                        i--;
                        last--;
                    }
                    else {
                        printf("Client[%d]: %s\n", i, buf);
                    }
                }
            }
        }
    }
    close(sockfd);
    return 0;
}

Linux下的 四种IO模型、IO 多路复用实现 TCP 并发_第12张图片

epoll

所支持的文件描述符上限是系统可以最大打开的文件的数目,1GB 的机器上,上限在10万左右。
每个产生事件的 fd 会主动调用 callback 回调函数,不需要轮循。
Linux下的 四种IO模型、IO 多路复用实现 TCP 并发_第13张图片

机制:

1、红黑树: 是特殊的二叉树(每个节点带有属性),Epoll 怎样能监听很多个呢?首先创建树的根节点,每个节点都是一个 fd,以结构体的形式存储(节点里面包含了 callback 函数)。
2、链表: 当某一个文件描述符产生事件后,会自动调用 callback函数,通过 callback 回调函数来找到链表对应的事件(读事件还是写事件),链表为事件链表。

基本流程:

1)创建红黑树(根节点); int epfd = epoll_create(999);
2)添加属性和文件描述符到树上;
struct epoll_event ev;
epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &ev);
4)阻塞等待事件的产生,一旦产生事件,则进行处理;
struct epoll_event eves[N];
int num = epoll_wait(epfd, struct epoll_event eves, N, -1);
5)对 链表 中相应的文件描述符 进行处理; if (eves[i].data.fd == 文件描述符){…}
Linux下的 四种IO模型、IO 多路复用实现 TCP 并发_第14张图片

函数
epoll_create
#include 
int epoll_create(int size); 

功能:创建红黑树根节点(创建 epoll实例)
参数:从LINUX 2.6.8开始,该参数被忽略,大于 0 即可
返回值: 成功:epoll文件描述符,
		失败:返回 -1。

Linux下的 四种IO模型、IO 多路复用实现 TCP 并发_第15张图片

epoll_ctl
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

功能:控制 epoll属性
参数: 
	1. epfd:   	epoll_create函数 的返回句柄。				// 一个标识符
	2. op:		表示动作类型,有三个宏:			        
             EPOLL_CTL_ADD:注册新的 fd 到 epfd 中
			 EPOLL_CTL_MOD:修改已注册 fd 的监听事件
			 EPOLL_CTL_DEL:从 epfd 中删除一个 fd
    3. 要操作的文件描述符
    4. typedef union epoll_data {
               int fd;      		// 要添加的文件描述符
               uint32_t u32;  		// typedef unsigned int
               uint64_t u64;   		// typedef unsigned long int
        } epoll_data_t;

    	struct epoll_event {
               uint32_t events; 事件
               epoll_data_t data; //共用体(看上面)
		};
		 event事件:
              EPOLLIN:  	表示对应文件描述符可读
              EPOLLOUT:		可写
              EPOLLPRI:	有紧急数据可读;
              EPOLLERR:	错误;
              EPOLLHUP:	被挂断;
              EPOLLET:		触发方式,边缘触发;(默认使用边缘触发)
              ET模式:		表示状态的变化;
              NULL: 		删除一个文件描述符使用,无事件
              
返回值: 成功:0, 
		失败:-1

Linux下的 四种IO模型、IO 多路复用实现 TCP 并发_第16张图片
Linux下的 四种IO模型、IO 多路复用实现 TCP 并发_第17张图片

epoll_wait
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

功能:等待事件的产生,类似于select的用法
参数: 	epfd:		句柄;
		events:	用来保存从链表中拿取响应事件的集合;
		maxevents:  表示每次在链表中拿取响应事件的个数;
		timeout:	超时时间(毫秒),0 立即返回;-1 阻塞	
返回值: 成功:实际从链表中拿出的数目     
		失败:-1

Linux下的 四种IO模型、IO 多路复用实现 TCP 并发_第18张图片
在这里插入图片描述
在这里插入图片描述

epoll_server.c
#include 
#include 
#include 
#include  
#include  
#include     
#include 
#include 
#include 
#include 

int main(int argc, char const *argv[])
{
    if (argc != 2){
        printf("Please input %s . \n", argv[0]);
        return -1;
    }
    
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0){
        perror("Failed to create a socket");
        return -1;
    }
    printf("sockfd: %d\n", sockfd);

    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[1]));
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    int length = sizeof(caddr);

    if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){
        perror("Failed to bind");
        return -1;
    }

    if (listen(sockfd, 6) < 0){
        perror("Failed to listen");
        return -1;
    }

    int epfd = epoll_create(999);
    if (epfd < 0){
        perror("Failed to epcreate");
        return -1;
    }

    struct epoll_event ev, eves[16];

    ev.data.fd = 0;
    ev.events = EPOLLIN | EPOLLET;
    epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &ev);

    ev.data.fd = sockfd;
    ev.events = EPOLLIN | EPOLLET;
    epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);

    char buf[256] = {};
    while (1){ 
        int num = epoll_wait(epfd, eves, 16, -1);
        if (num < 0){
            perror("Failed to epwait");
            return -1;
        }
        for (int i = 0; i < num; i++){
            if (eves[i].data.fd == 0){
                fgets(buf, sizeof(buf), stdin);
                if(buf[strlen(buf)-1] == '\n')
                   buf[strlen(buf)-1] = '\0';
                printf("%s\n", buf);
            }
            else if (eves[i].data.fd == sockfd){
                int accfd = accept(sockfd, (struct sockaddr *)&caddr, &length);
                if(accfd < 0){
                    perror("accept is err:");
                    return -1;
                }
                printf("Client IPv4: %s\t\tport: %d\n", 
                       inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
                
                ev.data.fd = accfd;
                ev.events = EPOLLIN | EPOLLET;
                epoll_ctl(epfd, EPOLL_CTL_ADD, accfd, &ev);
            }
            else {
                int receiver = recv(eves[i].data.fd, buf, sizeof(buf), 0);
                if (receiver < 0){
                    perror("Failed to receive");
                    return -1;
                } else if (receiver == 0){
                    printf("Client[%d] exited. \n", eves[i].data.fd);
                    close(eves[i].data.fd);
                    epoll_ctl(epfd, EPOLL_CTL_DEL, eves[i].data.fd, NULL);
                } else {
                    printf("Client[%d]: %s\n", eves[i].data.fd, buf);
                }
            }
        }  
    }
    close(sockfd);
    return 0;
}

总结

Linux下的 四种IO模型、IO 多路复用实现 TCP 并发_第19张图片
Linux下的 四种IO模型、IO 多路复用实现 TCP 并发_第20张图片

你可能感兴趣的:(#,网络编程,linux,tcp/ip,c语言,服务器)