1.poll机制
poll的实现和select非常相似,只是文件描述符fd集合的方式不同。
poll使用struct pollfd结构而不是select的fd_set结构,其他的都差不多。
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
功能:poll机制
参数:
fds:struct pollfd类型的数组
nfds:准备的文件描述符的个数
timeout:-1 阻塞,直到文件描述符准备好
返回值:
成功返回准备好的文件描述符的个数,失败返回-1
truct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
fd:文件描述符;
event:请求的事件(读、写、异常)
revents:对请求事件的反馈(读、写、异常)
POLLIN:读事件
POLLOUT:写事件
eg:
struct pollfd fds[3];
memset(fds,0,sizeof(fds));
fds[0].fd=fd1;
fds[0].events=POLLIN;
eg: tcpserver_poll.c
/*===============================================
* 文件名称:tcpserver_select.c
* 创 建 者:
* 创建日期:2022年08月19日
* 描 述:
================================================*/
/*===============================================
* 文件名称:clinet_unix_udp.c
* 创 建 者:
* 创建日期:2022年08月18日
* 描 述:
================================================*/
#include <stdio.h>
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/un.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
{
perror("socket");
return -1;
}
//端口复用
int on =1;
int k = setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
if(k<0)
{
perror("setsockopt");
return -1;
}
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(8888);
server.sin_addr.s_addr=inet_addr("0");
int len = sizeof(server);
int ret = bind(sockfd,(struct sockaddr *)&server,len);
if(ret<0)
{
perror("bind");
return -1;
}
ret =listen(sockfd , 5);
if(ret<0)
{
perror("listen");
return -1;
}
//1.创建结构体数组
struct pollfd fds[100];
//2.清空数组
memset(&fds,0,sizeof(fds));
//3.添加第一个元素
fds[0].fd=sockfd;
fds[0].event=POLLIN;
int m = 1;//结构体数组的长度
int n = 1;//结构体数组的下标
int i;
char buf[64] ={0};
while(1)
{
ret = poll(fds,m,-1);//阻塞,直到有文件描述符准备好
if(ret<0)
{
perror("select");
return -1;
}
for(i=0;i<m;i++)//检测谁准备好
{
if(fds[i].revents & POLLIN )//i准备好
{
int fd = fds[i].fd;
if(fd==sockfd) //处理sockfd,建立连接
{
int connfd = accept(i,NULL,NULL);
if(connfd<0)
{
perror("accept");
return -1;
}
printf("%d is link\n",connfd);
n=connfd-sockfd;
fds[n].fd=connfd;
fds[n].event=POLLIN;
if(m<connfd-2) //m代表下标的最大值
{
m=connfd-2;
}
}
else//connfd准备好
{
ret = recv(fd,buf,sizeof(buf),0);
if(ret<0)
{
perror("recv");
return -1;
}
else if(ret==0)
{
printf("%d is unlink\n",fd);
memset(&fds[i],0,sizeof(fds[i]));//删除队列
}
else
{
printf("%d :message =%s\n",i,buf);
memset(buf,0,sizeof(buf));
}
}
}
}
}
close(sockfd);
return 0;
}
2.epoll机制
1.epoll使用红黑树来管理所有的文件描述符集合,不受大小限制,更新集合时,不需 要重新拷贝整个集合,直接更新红黑树即可
2.利用call back来知道文件描述符是否就绪,只关心已经就绪的文件描述符,不需要遍 历所有的文件描述符。
1.epoll_create
#include <sys/epoll.h>
int epoll_create(int size);
功能:创建一棵树
参数:
size:树的节点
返回值:
成功返回准备好的文件描述符epfd,失败返回-1
2.epoll_ctl
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:控制epoll
参数:
epfd:epoll_create的返回值
op:
EPOLL_CTL_ADD:添加文件描述符
EPOLL_CTL_DEL:删除文件描述符
fd:准备好的文件描述符
event:指定的结构体(一个)
返回值:
成功返回0,失败返回-1;
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
events:
EPOLLIN:读事件
EPOLLOUT:写事件
eg:
struct epoll_event ev;
ev.events=;//事件
ev.data.fd;//
3.epoll_wait
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
功能:
等待文件描述符就绪
参数:
epfd:epoll_creat的返回值
events:指定的结构体数组;
struct epoll_event evs[size];//结构体数组
maxevents:[size]
time_out:超时检测,-1阻塞,等待文件描述符就绪
返回值:
成功,返回准备好的文件描述符,没有准备好的返回0;
失败返回-1;
/*===============================================
* 文件名称:tcpserver_select.c
* 创 建 者:
* 创建日期:2022年08月19日
* 描 述:
================================================*/
/*===============================================
* 文件名称:clinet_unix_udp.c
* 创 建 者:
* 创建日期:2022年08月18日
* 描 述:
================================================*/
#include <stdio.h>
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/un.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
int main(int argc, char *argv[])
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
{
perror("socket");
return -1;
}
//端口复用
int on =1;
int k = setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
if(k<0)
{
perror("setsockopt");
return -1;
}
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(8888);
server.sin_addr.s_addr=inet_addr("0");
int len = sizeof(server);
int ret = bind(sockfd,(struct sockaddr *)&server,len);
if(ret<0)
{
perror("bind");
return -1;
}
ret =listen(sockfd , 5);
if(ret<0)
{
perror("listen");
return -1;
}
//1.创建一课树
int epfd = epoll_create(1003);
if(epfd<0)
{
perror("epoll_create");
return -1;
}
//2.添加到树中
struct epoll_event ev,evs[100];//ev用于修改evs
ev.events = EPOLLIN; //设置为增加
ev.data.fd = sockfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&ev);//通过epfd把ev加到树中
int m,i;//m用于接收epoll_wait的返回值,准备就绪的文件描述符
//i用于循环
char buf[64] ={0};
while(1)
{
m = epoll_wait(epfd,evs,100,-1);
if(m==-1)
{
perror("epoll_wait");
return -1;
}
for(i=0;i<m;i++)
{
if(evs[i].events & EPOLLIN)//事件是可读的
{
int fd = evs[i].data.fd;
if(fd==sockfd)//处理sockfd,建立连接
{
int connfd = accept(fd,NULL,NULL);
printf("%d is link\n",connfd);
ev.events = EPOLLIN;
ev.data.fd = connfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);//把connfd添加到树中
}
else//处理connfd
{
ret = recv(fd,buf,sizeof(buf),0);
if(ret<0)
{
perror("recv");
}
else if(ret == 0)
{
printf("%d is unlink\n",fd);
epoll_ctl(epfd,EPOLL_CTL_DEL,fd,&ev);//从树中删除fd
close(fd);
}
else
{
printf("%d message=%s\n",fd,buf);
memset(buf,0,sizeof(buf));
}
}
}
}
}
close(sockfd);
return 0;
}
3.超时检测
方法1:设置套接字属性,设置超时值
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
int tcpserver_init(int port);
int recv_data(int connfd);
int main(int argc, const char *argv[])
{
int sockfd = tcpserver_init(6666);
struct sockaddr_in clientaddr;
int m = sizeof(clientaddr);
while(1)
{
printf("wait a client.............\n");
//connfd 进行数据的收发
int connfd = accept(sockfd, (struct sockaddr *)&clientaddr , &m );
if(connfd < 0)
{
perror("accept");
return -1;
}
printf("client ip: %s client port: %d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
struct timeval tv = {5,0};
setsockopt(connfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
recv_data(connfd);
}
close(sockfd);
return 0;
}
int tcpserver_init(int port)
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("sockfd");
return -1;
}
printf("sockfd = %d\n", sockfd);
//端口复用函数:解决端口号被系统占用的情况
int on = 1;
int k = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if(k == -1)
{
perror("setsockopt");
return -1;
}
struct sockaddr_in serveraddr;
memset(&serveraddr, 0 ,sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(port);
serveraddr.sin_addr.s_addr = inet_addr("0"); //自动路由,寻找IP地址
int len = sizeof(serveraddr);
int ret = bind(sockfd, (struct sockaddr *)&serveraddr, len);
if(ret == -1)
{
perror("bind");
return -1;
}
ret = listen(sockfd, 5);
if(ret == -1)
{
perror("listen");
return -1;
}
return sockfd;
}
int recv_data(int connfd)
{
//接收和发送数据
char buf[1024] = {0};
while(1)
{
int n = recv(connfd, buf, sizeof(buf), 0);
if(n < 0)
{
printf("%d\n", errno);
if(errno == 11)
{
printf("overtime\n");
continue;
}
perror("read");
return -1;
}
else if(n == 0) //客户端关闭了
{
close(connfd);
break;
}
printf("message:%s\n", buf);
memset(buf, 0 , sizeof(buf));
}
return 0;
}
方法2:设置闹钟
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <time.h>
#include <signal.h>
int tcpserver_init(int port);
int recv_data(int connfd);
void signal_handler(int sig)
{
printf("overtime\n");
//......
}
int main(int argc, const char *argv[])
{
signal(SIGALRM, signal_handler);
alarm(5);
int sockfd = tcpserver_init(6666);
struct sockaddr_in clientaddr;
int m = sizeof(clientaddr);
while(1)
{
printf("wait a client.............\n");
//connfd 进行数据的收发
int connfd = accept(sockfd, (struct sockaddr *)&clientaddr , &m );
if(connfd < 0)
{
perror("accept");
return -1;
}
printf("client ip: %s client port: %d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
recv_data(connfd);
}
close(sockfd);
return 0;
}
int tcpserver_init(int port)
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("sockfd");
return -1;
}
printf("sockfd = %d\n", sockfd);
//端口复用函数:解决端口号被系统占用的情况
int on = 1;
int k = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if(k == -1)
{
perror("setsockopt");
return -1;
}
struct sockaddr_in serveraddr;
memset(&serveraddr, 0 ,sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(port);
serveraddr.sin_addr.s_addr = inet_addr("0"); //自动路由,寻找IP地址
int len = sizeof(serveraddr);
int ret = bind(sockfd, (struct sockaddr *)&serveraddr, len);
if(ret == -1)
{
perror("bind");
return -1;
}
ret = listen(sockfd, 5);
if(ret == -1)
{
perror("listen");
return -1;
}
return sockfd;
}
int recv_data(int connfd)
{
//接收和发送数据
char buf[1024] = {0};
while(1)
{
int n = recv(connfd, buf, sizeof(buf), 0);
if(n < 0)
{
perror("read");
return -1;
}
else if(n == 0) //客户端关闭了
{
close(connfd);
break;
}
printf("message:%s\n", buf);
memset(buf, 0 , sizeof(buf));
}
return 0;
}
方法3:在select函数 或poll函数中设置超时值
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>
int main(int argc, const char *argv[])
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("sockfd");
return -1;
}
printf("sockfd = %d\n", sockfd);
//端口复用函数:解决端口号被系统占用的情况
int on = 1;
int k = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if(k == -1)
{
perror("setsockopt");
return -1;
}
struct sockaddr_in serveraddr;
memset(&serveraddr, 0 ,sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(8888);
serveraddr.sin_addr.s_addr = inet_addr("0"); //自动路由,寻找IP地址
int len = sizeof(serveraddr);
int ret = bind(sockfd, (struct sockaddr *)&serveraddr, len);
if(ret == -1)
{
perror("bind");
return -1;
}
ret = listen(sockfd, 5);
if(ret == -1)
{
perror("listen");
return -1;
}
//select机制
fd_set readfds,tmpfds;
FD_ZERO(&readfds);
FD_SET(sockfd, &readfds);
int maxfd = sockfd;
tmpfds = readfds;
char buf[64] = {0};
int i;
struct timeval tv = {5, 0};
while(1)
{
readfds = tmpfds;
ret = select(maxfd+1, &readfds, NULL, NULL, &tv);
if(ret == -1)
{
perror("select");
exit(-1);
}
else if(ret == 0) //超时检测
{
printf("overtime\n");
tv.tv_sec = 2;
continue;
}
for(i=sockfd; i<maxfd+1; i++)
{
if(FD_ISSET(i, &readfds))
{
if(i== sockfd)
{
printf("%d 已经准备好了\n", i);
//建立连接,用accept函数
int connfd = accept(i, NULL, NULL);
printf("%d is link\n", connfd);
FD_SET(connfd, &tmpfds);
if(maxfd < connfd)
{
maxfd = connfd;
}
}
else{
memset(buf, 0, sizeof(buf));
//处理connfd
ret = recv(i, buf, sizeof(buf), 0);
if(ret == -1)
{
close(i);
perror("recv");
return -1;
}
else if(ret == 0)
{
printf("%d is unlink\n", i);
FD_CLR(i, &tmpfds);
close(i);
break;
}
else{
printf("%d:message=%s\n", i,buf);
}
}
}
}
}
close(sockfd);
return 0;
}