1.poll 模型
![](https://i-blog.csdnimg.cn/direct/e5658c99e9c6499a8014d3f547abb4e9.png)
poll 函数原型
函数原型:int poll(struct pollfd *fds, nfds_t nfds, int timeout);
功能描述:监视fds中的描述符是否激活
参数描述:参数 fds:是一个struct pollfd 结构体数组,该数组中存放了多个想要监视的描述符该结构体结构如下struct pollfd {int fd; /* 想要监视的描述符 */short events; /* 确定fd描述,到底以何种形式进行监视 */POLLIN:监视fd描述是否可读POLLOUT:监视fd描述符是否可写 short revents; /* returned events */一般会随着结构体初始化为0,同步为0当 revents == 0的时候,表示fd这个描述符没有激活当 revents == POLLIN的时候,表示fd这个描述符可读激活了当 revents == POLLOUT的时候,表示fd这个描述可写激活了};参数 nfds:fds这个数组的长度参数 timeout:设置的最大监视时长注意: 单位是毫秒0表示不阻塞-1表示一直阻塞 实例struct pollfd arr[5] = {0}arr[0].fd = 0arr[0].events = POLLINpoll(arr) 表示监视 标准输入流是否可读
poll_server模型 完整代码
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <wait.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/un.h>
#include <poll.h>typedef struct sockaddr_in addr_in_t;
typedef struct sockaddr addr_t;
typedef struct sockaddr_un addr_un_t;void insert_fd(struct pollfd* arr,int* len,struct pollfd fd){arr[*len] = fd;(*len)++;
}void remove_fd(struct pollfd* arr,int* len,int fd){int i = 0;for(i=0;i<*len;i++){if(fd == arr[i].fd){break;}}for(int j=i;j<*len-1;j++){arr[j] = arr[j+1];}(*len)--;
}int main(int argc, const char *argv[])
{if(argc != 2){printf("请输入端口号\n");return 1;}// 准备一个用来存放所有连接上来的客户端描述符的数组int client_arr[64] = {0};int client_arr_len = 0;// ./server 8888int port = atoi(argv[1]);// 将字符串 8888 转换成int类型port// 创建服务器套接字int server = socket(AF_INET,SOCK_STREAM,0);// 准备网络地址结构体:struct sockaddr_inaddr_in_t addr = {0};addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = inet_addr("0.0.0.0");// 为套接字绑定ip 和 portif(bind(server,(addr_t*)&addr,sizeof(addr)) == -1){perror("bind");return 1;}// 监听listen(server,10);struct pollfd arr[20] = {0};int len = 0;// 0 和 server 放到 arr 里面去struct pollfd poll_stdin = {.fd=0,.events=POLLIN,.revents=0};struct pollfd poll_server = {server,POLLIN,0};insert_fd(arr,&len,poll_stdin);insert_fd(arr,&len,poll_server);while(1){poll(arr,len,-1);for(int i=0;i<len;i++){if(arr[i].revents == 0){continue;} // 该描述符没激活,直接看下一个描述符int fd = arr[i].fd;if(fd == server){printf("有新客户端连接\n");int client = accept(server,0,0);struct pollfd poll_client = {client,POLLIN,0};insert_fd(arr,&len,poll_client);}else if(fd == 0){char buf[64] = "";scanf("%63s",buf);while(getchar()!=10);printf("键盘输入数据:%s\n",buf);}else{// 剩下的都是客户端描述符了char buf[64] = "";int res = read(fd,buf,64);if(res == 0){remove_fd(arr,&len,fd);close(fd);}else{printf("%d#客户端发来消息:%s\n",fd,buf);}}}}return 0;
}
2.epoll模型
![](https://i-blog.csdnimg.cn/direct/70462b909b35486f99e5673ca426d786.png)
epoll函数原型
函数原型:int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
功能描述:监视 epfd 里面的描述符是否被激活
参数描述:参数 epfd:epoll_wait的监视列表epfd其实是一个文件描述符,该描述符所描述的文件,才是epoll_wait的监视列表所以我们要做的就是,将所有想要被epoll监视的描述符,都放到 epfd 文件中去参数 events:用来存放所有激活了的描述符的数组,是一个结构体数组,结构如下struct epoll_event {uint32_t events; /* 在poll_wait函数里面,这个数据表示激活的描述符是以何种形式激活的 */epoll_data_t data; /* 该数据里面存放了具体的激活的描述符是谁 */};typedef union epoll_data {void *ptr;int fd; /* 具体的激活的描述符 */uint32_t u32;uint64_t u64;} epoll_data_t;其实epoll_wait 针对当前数组只做一件事将激活的描述符到底是谁,以及该描述符是如何激活的,存放到 events这个数组里面去参数 maxevents:想要监视的描述符的数量参数 timout:设定poll_wait最大阻塞时长0:表示不阻塞-1:表示永久阻塞单位为毫秒返回值:返回激活的描述符的数量实例struct epoll_event arr[5]epoll_wait(epfd,arr) 假设此时 epfd 里面有一个 0 和 一个 server此时 epoll_wait 监视到 server激活了arr就会发生改变,改变如下arr[0].events == EPOLLINarr[0].data.fd == server
如何创建epfd
int epoll_create(int size);创建一个只能监视size个描述符的监视列表
int epoll_create1(int flags);flags一般传 EPOLL_CLOEXEC表示创建一个动态的监视列表,该监视列表的长度会随着想要监视的描述符数量的改变而改变
所以,一般来说,我们想要创建一个监视列表,直接写int epfd = epoll_create1(EPOLL_CLOEXEC);返回值:创建出来的监视列表的描述符
如何向epfd中存入想要监视的描述符
函数原型:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能描述:根据op(第2个参数)的不同,针对描述符做出不同的操作
参数描述:参数 epfd:监视列表自己的描述符参数 op:具体的操作EPOLL_CTL_ADD:将fd描述符,添加到epfd监视列表里面去EPOLL_CTL_DEL:将fd描述符,从epfd中移除EPOLL_CTL_MOD:更改fd描述的监视方式参数 event: 注意,这里是一个结构体地址,用来明确想要监视的描述符fd,以何种形式去监视struct epoll_event {uint32_t events; /* 在poll_ctl函数里面,用来确定描述符fd的监视方式 */EPOLLIN :监视描述符是否可读EPOLLOUT:监视描述符是否可写epoll_data_t data; };typedef union epoll_data {void *ptr;int fd; /* 要求和epoll_ctl函数里面第3个参数fd写的一模一样 */uint32_t u32;uint64_t u64;} epoll_data_t;
epoll_server 完整代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <sys/socket.h>typedef struct sockaddr_in addr_in_t;int main(int argc, const char *argv[]) {if (argc != 2) {printf("请输入端口号\n");return 1;}int port = atoi(argv[1]);int server = socket(AF_INET, SOCK_STREAM, 0);if (server == -1) {perror("socket");return 1;}addr_in_t addr = {0};addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = inet_addr("0.0.0.0"); // 或者使用其他有效的IP地址if (bind(server, (struct sockaddr*)&addr, sizeof(addr)) == -1) {perror("bind");return 1;}// 监听listen(server, 10);// 创建一个可动态扩容的epoll文件描述符int epfd = epoll_create1(EPOLL_CLOEXEC);if (epfd == -1) {perror("epoll_create1");return 1;}// 将server和stdin添加到epfd里面去struct epoll_event server_event, stdin_event;server_event.data.fd = server;server_event.events = EPOLLIN;stdin_event.data.fd = 0;stdin_event.events = EPOLLIN;if (epoll_ctl(epfd, EPOLL_CTL_ADD, server, &server_event) == -1) {perror("epoll_ctl for server");return 1;}if (epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &stdin_event) == -1) {perror("epoll_ctl for stdin");return 1;}int fd_count = 50; // 这个变量似乎没有被后续代码使用while (1) {struct epoll_event active_fds[50] = {0};int len = epoll_wait(epfd, active_fds, 50, -1);if (len == -1) {perror("epoll_wait");return 1;}// 经过epoll_wait说明有len个描述符激活了for (int i = 0; i < len; i++) {int sock = active_fds[i].data.fd;if (sock == server) {// 激活的是服务器printf("有新客户端连接\n");int client = accept(server, NULL, NULL);if (client == -1) {perror("accept");continue;}struct epoll_event client_event;client_event.data.fd = client;client_event.events = EPOLLIN;if (epoll_ctl(epfd, EPOLL_CTL_ADD, client, &client_event) == -1) {perror("epoll_ctl for client");close(client);continue;}} else if (sock == 0) {// 激活的是标准输入流char buf[64] = {0};scanf("%63s", buf);while (getchar() != '\n'); // 清除输入缓冲区printf("键盘输入了:%s\n", buf);} else {// 激活的不是上面两个,只能是客户端了char buf[64] = {0};int res = read(sock, buf, 64);if (res == 0) {// 客户端断开连接printf("客户端断开连接\n");epoll_ctl(epfd, EPOLL_CTL_DEL, sock, NULL);close(sock);} else {printf("客户端发来消息:%s\n", buf);}}}}return 0;
}
3.客户端测试代码
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <wait.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/un.h>typedef struct sockaddr_in addr_in_t;
typedef struct sockaddr addr_t;
typedef struct sockaddr_un addr_un_t;enum Type{TYPE_REGIST,TYPE_LOGIN
};typedef struct Pack{enum Type type;char name[20];char pswd[20];
}pack_t;int main(int argc, const char *argv[])
{if(argc!=2){printf("输入端口号\n");return 1;}int port =atoi(argv[1]);//创建服务器套接字int client=socket(AF_INET,SOCK_STREAM,0);addr_in_t addr ={0};addr.sin_family=AF_INET;addr.sin_port=htons(port);addr.sin_addr.s_addr=inet_addr("192.168.126.181");
//连接服务器if (connect(client,(addr_t*)&addr,sizeof(addr))==-1){perror("connect");return 1; }//while(1){char buf[64]="";printf("请输入");scanf("%63s",buf);while(getchar()!=10);write(client,buf,64);}return 0;
}