博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
用 epoll 写一个socket双工通信
阅读量:4973 次
发布时间:2019-06-12

本文共 4432 字,大约阅读时间需要 14 分钟。

用 epoll 写一个socket双工通信

NIO 说明

常用的方式有 select、poll、epoll。

select是使用的一种类似位操作的方式,获取到了事件响应就将对应位置为1。缺点就是首先受限于这个标识符长度,因此能监听的数量有限,其次就是每次遍历都需要遍历每一位。

poll 改进了select,不是使用位操作,解决了数量有限的问题,但是依旧遍历需要遍历。

epoll 是使用类似链表的数据结构解决数量有限的问题的,然后是利用回调函数,避免了遍历。

epoll 使用

1117030-20181108103143961-1365346118.png

主要用到的几个函数:

int epoll_create(int size);int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

分别说明:

epoll_create 创建一个 epoll 套接字,之后所有的要监听的套接字都会add到这个套接字上,参数size只需要是一个大于0的整数即可。

epoll_ctl 的op参数标志执行一些操作 EPOLL_CTL_ADD | EPOLL_CTL_MOD | EPOLL_CTL_DEL,分别是增删改套接字,epdf参数就是epoll_create 创建的套接字,然后 fd是对应的文件描述符,event是事件描述。(有一点我不明白的是,event 结构体中其实已经包含了 fd参数,不知道为什么这个地方还需要重新传入)

epoll_wait 监听事件。events 参数用于暂时存放那些响应的事件,timeout代表接受到了消息之后多久返回,-1代表无限等待。

写一个socket通信程序

在计算机网络课程上可能我们第一次接触的程序就是一个简单的socket C/S 程序,但是那个程序只能是 客户端请求->服务端响应的方式,这里我们写一个服务端也能主动发送消息的程序。

主要区别就是在于使用epoll同时监听stdin和socket连接套接字。

server

#include 
#include
#include
// for sockaddr_in#include
// for socket#include
// for socket#include
// for printf#include
// for exit#include
// for bzero#include
// inet_ntoa#define LISTENQ 20#define MAXLINE 10000#define SERV_PORT 6666int main(int argc, char **argv){ int listenfd, connfd; struct sockaddr_in cliaddr, servaddr; listenfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); bind(listenfd, (struct sockaddr*) &servaddr, sizeof(servaddr)); listen(listenfd, LISTENQ); int addrSize = sizeof(cliaddr); connfd = accept(listenfd, (struct sockaddr*) &cliaddr, &addrSize); printf("connected from:%s, port:%d \n\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port)); close(listenfd); /* close listening socket */ int epollFd; epollFd = epoll_create(256); struct epoll_event ev,events[20]; ev.data.fd=connfd; ev.events=EPOLLIN|EPOLLET; epoll_ctl(epollFd,EPOLL_CTL_ADD,connfd,&ev); // stdin = 0 ev.data.fd=0; ev.events=EPOLLIN; epoll_ctl(epollFd,EPOLL_CTL_ADD,0,&ev); char sendline[MAXLINE], recvline[MAXLINE]; while(1){ int nfds=epoll_wait(epollFd,events,20,0); for (int i = 0; i < nfds; ++i){ if(events[i].data.fd==connfd){ int n = read(connfd, recvline, MAXLINE); // TODO: add n == 0 fputs(recvline, stdout); }else if(events[i].data.fd==0){ fgets(sendline, MAXLINE, stdin); send(connfd, sendline, strlen(sendline), 0); } } } close(connfd); close(epollFd); return 0;}

client

#include 
#include
#include
// for sockaddr_in#include
// for socket#include
// for socket#include
// for printf#include
// for exit#include
// for bzero#include
// inet_ntoa#include
#define SERV_PORT 6666#define MAXLINE 10000struct sockaddr_in servaddr, servaddr1;intmain(int argc, char **argv){ int connfd; if (argc != 2){ printf("usage: tcpcli
"); exit(0); } connfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERV_PORT); inet_pton(AF_INET, argv[1], &servaddr.sin_addr); connect(connfd, (struct sockaddr*) &servaddr, sizeof(servaddr)); int epollFd; epollFd = epoll_create(256); struct epoll_event ev,events[20]; ev.data.fd=connfd; ev.events=EPOLLIN|EPOLLET; epoll_ctl(epollFd,EPOLL_CTL_ADD,connfd,&ev); // stdin = 0 ev.data.fd=0; ev.events=EPOLLIN; epoll_ctl(epollFd,EPOLL_CTL_ADD,0,&ev); char sendline[MAXLINE], recvline[MAXLINE]; while(1){ int nfds=epoll_wait(epollFd,events,20,0); for (int i = 0; i < nfds; ++i){ if(events[i].data.fd==connfd){ int n = read(connfd, recvline, MAXLINE); fputs(recvline, stdout); }else if(events[i].data.fd==0){ fgets(sendline, MAXLINE, stdin); send(connfd, sendline, strlen(sendline), 0); } } } close(connfd); close(epollFd); return 0;}

执行

gcc tcpserv.c -o tcpservgcc tcpcli.c -o tcpcli./tcpserv...# 另开一个窗口./tcpcli 127.0.0.1

就可以在两边都进行输入了。

PS:

关于epoll事件定义

EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);

EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

实际使用中只用到了EPOLLIN...... 以后再学习其他的吧。

转载于:https://www.cnblogs.com/JenningsMao/p/9927641.html

你可能感兴趣的文章