一、功能介绍
在上一篇文章,编写了一个 Linux下基于TCP协议的群聊系统设计(多线程+select)
案例,演示了select函数的使用方法。这篇文章接着介绍剩下的poll、epoll函数。并且也是使用群聊系统
的案例编写例子,方便理解实际用法。
下面先介绍这3种函数的原型和相关头文件
虽然select在上篇文章里已经介绍过,这里为了方便比较这3个函数,再把select详细介绍粘贴过来。
1.1 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);
函数功能: 监听指定数量的文件描述符的状态。
函数参数:
int nfds : 监听最大的文件描述符+1的值
fd_set *readfds :监听读事件的文件描述符集合,不想监听读事件这里可以填NULL
fd_set *writefds :监听写事件的文件描述符集合,不想监听事件这里可以填NULL
fd_set *exceptfds :监听其他事件的文件描述符集合,不想监听事件这里可以填NULL
struct timeval *timeout : 指定等待的时间。 如果填NULL表示永久等待,直到任意一个文件描述符产生事件再返回。
如果填正常时间,如果在等待的时间内没有事件产生,也会返回。
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
返回值: 表示产生事件文件描述符数量。 ==0表示没有事件产生。 >0表示事件数量 <0表示错误。
void FD_CLR(int fd, fd_set *set); //清除某个集合里的指定文件描述符
int FD_ISSET(int fd, fd_set *set); //判断指定集合里的指定文件描述符是否产生了某个事件。 为真就表示产生了事件
void FD_SET(int fd, fd_set *set); //将指定的文件描述符添加到指定的集合
void FD_ZERO(fd_set *set); //清空整个集合。
1.2 poll函数
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
函数功能: 监听多个文件描述符的事件。
函数参数:
struct pollfd *fds :监听的事件,可以填结构体数组。
nfds_t nfds :监听的数量。
int timeout :等待的时间.ms单位. >0表示正常等待时间。 ==0表示不等待 <0永久等待.
返回值:
产生事件数量
struct pollfd {
int fd; /* file descriptor 监听的文件描述符*/
short events; /* requested events 监听的事件 POLLIN表示可读事件*/
short revents; /* returned events 产生的事件--作为判断条件*/
};
1.3 epoll函数
#include <sys/epoll.h>
int epoll_create(int size);
函数功能: 创建epoll专用文件描述符的缓冲区。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
函数功能: 添加、删除、修改 监听文件描述符。
函数参数:
int epfd epoll专用的文件描述符
int op 操作命令。EPOLL_CTL_ADD EPOLL_CTL_MOD EPOLL_CTL_DEL
int fd 要操作文件描述符
struct epoll_event *event 存放监听文件描述符信息的结构体
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
函数功能: 等待事件发生。
函数参数:
int epfd epoll专用的文件描述符
struct epoll_event *events : 存放产生事件的文件描述结构体。
int maxevents :最大监听的数量.
int timeout :等待事件ms单位. <0 >0 ==0
返回值: 产生事件的数量。
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 EPOLLIN 输入事件 */
epoll_data_t data; /* User data variable */
};
二、epoll、poll函数使用案例
在上篇文章里已经贴出了详细的群聊系统的源码,并且有运行效果图、思路讲解。
这里就只贴出客户端的代码,把原来select监听的客户端的代码分别改成了epoll、poll两种。
2.1 poll函数使用案例: 应用在TCP客户端
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <sys/select.h>
#include <sys/time.h>
#include <poll.h>
//消息结构体
struct MSG_DATA
{
char type; //消息类型. 0表示有聊天的消息数据 1表示好友上线 2表示好友下线
char name[50]; //好友名称
int number; //在线人数的数量
unsigned char buff[100]; //发送的聊天数据消息
};
struct MSG_DATA msg_data;
//文件接收端
int main(int argc,char **argv)
{
if(argc!=4)
{
printf("./app <IP地址> <端口号> <名称>\n");
return 0;
}
int sockfd;
//忽略 SIGPIPE 信号--方式服务器向无效的套接字写数据导致进程退出
signal(SIGPIPE,SIG_IGN);
/*1. 创建socket套接字*/
sockfd=socket(AF_INET,SOCK_STREAM,0);
/*2. 连接服务器*/
struct sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_port=htons(atoi(argv[2])); // 端口号0~65535
addr.sin_addr.s_addr=inet_addr(argv[1]); //IP地址
if(connect(sockfd,(const struct sockaddr *)&addr,sizeof(struct sockaddr_in))!=0)
{
printf("客户端:服务器连接失败.\n");
return 0;
}
/*3. 发送消息表示上线*/
msg_data.type=1;
strcpy(msg_data.name,argv[3]);
write(sockfd,&msg_data,sizeof(struct MSG_DATA));
int cnt;
struct pollfd fds[2];
fds[0].fd=sockfd;
fds[0].events=POLLIN;
fds[1].fd=0;
fds[1].events=POLLIN;
while(1)
{
//监听事件
cnt=poll(fds,2,-1);
if(cnt)
{
if(fds[0].events&fds[0].revents) //判断收到服务器的消息
{
cnt=read(sockfd,&msg_data,sizeof(struct MSG_DATA));
if(cnt<=0) //判断服务器是否断开了连接
{
printf("服务器已经退出.\n");
break;
}
else if(cnt>0)
{
if(msg_data.type==0)
{
printf("%s:%s 在线人数:%d\n",msg_data.name,msg_data.buff,msg_data.number);
}
else if(msg_data.type==1)
{
printf("%s 好友上线. 在线人数:%d\n",msg_data.name,msg_data.number);
}
else if(msg_data.type==2)
{
printf("%s 好友下线. 在线人数:%d\n",msg_data.name,msg_data.number);
}
}
}
if(fds[1].events&fds[1].revents) //判断键盘上有输入
{
gets(msg_data.buff); //读取键盘上的消息
msg_data.type=0; //表示正常消息
strcpy(msg_data.name,argv[3]); //名称
write(sockfd,&msg_data,sizeof(struct MSG_DATA));
}
}
}
close(sockfd);
return 0;
}
2.2 epoll函数使用案例: 应用再TCP客户端
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <sys/select.h>
#include <sys/time.h>
#include <poll.h>
#include <sys/epoll.h>
//消息结构体
struct MSG_DATA
{
char type; //消息类型. 0表示有聊天的消息数据 1表示好友上线 2表示好友下线
char name[50]; //好友名称
int number; //在线人数的数量
unsigned char buff[100]; //发送的聊天数据消息
};
struct MSG_DATA msg_data;
#define MAX_EVENTS 10
struct epoll_event ev, events[MAX_EVENTS];
int epollfd;
int nfds;
//文件接收端
int main(int argc,char **argv)
{
if(argc!=4)
{
printf("./app <IP地址> <端口号> <名称>\n");
return 0;
}
int sockfd;
//忽略 SIGPIPE 信号--方式服务器向无效的套接字写数据导致进程退出
signal(SIGPIPE,SIG_IGN);
/*1. 创建socket套接字*/
sockfd=socket(AF_INET,SOCK_STREAM,0);
/*2. 连接服务器*/
struct sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_port=htons(atoi(argv[2])); // 端口号0~65535
addr.sin_addr.s_addr=inet_addr(argv[1]); //IP地址
if(connect(sockfd,(const struct sockaddr *)&addr,sizeof(struct sockaddr_in))!=0)
{
printf("客户端:服务器连接失败.\n");
return 0;
}
/*3. 发送消息表示上线*/
msg_data.type=1;
strcpy(msg_data.name,argv[3]);
write(sockfd,&msg_data,sizeof(struct MSG_DATA));
int cnt;
int i;
//创建专用文件描述符
epollfd = epoll_create(10);
//添加要监听的文件描述符
ev.events = EPOLLIN;
ev.data.fd = sockfd;
epoll_ctl(epollfd, EPOLL_CTL_ADD, sockfd, &ev);
ev.events = EPOLLIN;
ev.data.fd = 0; //标准输入文件描述符
epoll_ctl(epollfd, EPOLL_CTL_ADD, 0, &ev);
while(1)
{
//监听事件
nfds=epoll_wait(epollfd,events,MAX_EVENTS,-1);
if(nfds)
{
for(i=0;i<nfds;i++)
{
if(events[i].data.fd==sockfd) //判断收到服务器的消息
{
cnt=read(sockfd,&msg_data,sizeof(struct MSG_DATA));
if(cnt<=0) //判断服务器是否断开了连接
{
printf("服务器已经退出.\n");
goto SERVER_ERROR;
}
else if(cnt>0)
{
if(msg_data.type==0)
{
printf("%s:%s 在线人数:%d\n",msg_data.name,msg_data.buff,msg_data.number);
}
else if(msg_data.type==1)
{
printf("%s 好友上线. 在线人数:%d\n",msg_data.name,msg_data.number);
}
else if(msg_data.type==2)
{
printf("%s 好友下线. 在线人数:%d\n",msg_data.name,msg_data.number);
}
}
}
else if(events[i].data.fd==0) //表示键盘上有数据输入
{
gets(msg_data.buff); //读取键盘上的消息
msg_data.type=0; //表示正常消息
strcpy(msg_data.name,argv[3]); //名称
write(sockfd,&msg_data,sizeof(struct MSG_DATA));
}
}
}
}
SERVER_ERROR:
close(sockfd);
return 0;
}