linux中网络编程I/O模型—poll(多路复用I/O)
- poll函数原型:
int poll(struct pollfd *fds, unsigned int nfds, int timeout);
struct pollfd{
int fd; //文件描述符
short events; //需要监听的事件
short reevents; //实际发生的事件
}
poll参数列表:
- struct pollfd * :监听的多个pollfd数组
- unsigned int:需要监听的文件描述符的数量
- int:超时时间
events和reevents值列表:
事件分类 | 事件代码 | 意义 |
---|---|---|
合法事件 | POLLIN | 有可读数据 |
合法事件 | POLLRDNORM | 有普通数据可读 |
合法事件 | POLLRDBAND | 有优先数据可读 |
合法事件 | POLLPRI | 有紧急数据可读 |
合法事件 | POLLOUT | 写数据不会导致阻塞 |
合法事件 | POLLWRNORM | 写普通数据不会导致阻塞 |
合法事件 | POLLWRBAND | 写优先数据不会导致阻塞 |
合法事件 | POLLMSGSIGPOLL | 消息可用 |
非法事件 | POLLER | 指定的文件描述符发生错误 |
非法事件 | POLLHUP | 指定的文件描述符挂起事件 |
非法事件 | POLLNVAL | 指定的文件描述符非法 |
-
poll函数的行为:
成功时,poll返回fds中revents不为0的文件描述符的数量;如果超时前没有任何事件发生,返回0。失败时,返回-1,并设置errno为下列值之一:
1)EBADF:一个或多个结构体中指定的文件描述无效
2)EFAULTfds:指针指向的地址空间超出进程的地址空间
3)EINTR:请求的事件之前产生一个信号,调用可以重新发起
3)EINVALnfds:参数超出PLIMIT_NOFILE值
4)ENOMEM:可用内存不足,无法完成请求 -
使用多路复用I/O实现server、client
server.cpp
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <poll.h>#define IPADDRESS "127.0.0.1"
#define PORT 6666
#define MAXLEN 1024
#define LISTENQ 5
#define OPEN_MAX 1000
#define INFTIM -1//创建监听socket,并bind和listen
int bind_and_listen()
{
int serverfd; //监听socket:serverfd;数据传输socket:acceptfdstruct sockaddr_in my_addr; //本机地址信息//初始化流式socketif ((serverfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
perror("socket");return -1;}printf("socket ok\n");my_addr.sin_family = AF_INET;my_addr.sin_port = htons(PORT); //将主机字节序的监听端口转换成网络字节序my_addr.sin_addr.s_addr = INADDR_ANY; //监听来自所有网络的消息bzero(&(my_addr.sin_zero), 0);//将需要监听的socket:serverfd与本地主机绑定if (bind(serverfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1){
perror("bind");return -2;}printf("bind ok\n");if (listen(serverfd, LISTENQ) == -1){
perror("listen");return -3;}printf("listen ok\n");return serverfd;
}//多路复用poll
void do_poll(int listenfd)
{
int connfd;struct sockaddr_in sock_client;socklen_t sock_len;struct pollfd poll_fds[OPEN_MAX];int maxi;int i;int nready;//将监听socket:serverfd添加到pollfd:poll_fds中poll_fds[0].fd = listenfd;//设置监听socket可读事件poll_fds[0].events = POLLIN;//初始化链接描述符for (int index = 1; index < OPEN_MAX; index++)poll_fds[index].fd = -1;maxi = 0;//循环处理while (1){
//获取需要处理的文件描述符的数量nready = poll(poll_fds, maxi + 1, INFTIM);if (nready == -1){
perror("poll error");exit(1);}//检查监听描述符是否存在可读消息if (poll_fds[0].revents & POLLIN){
sock_len = sizeof(sock_client);//接受新的链接if ((connfd = accept(listenfd, (struct sockaddr*)&sock_client, &sock_len)) == -1){
if (errno == EINTR)continue;else{
perror("accept");exit(1);}}fprintf(stdout, "accpet a new client : %s:%d\n", inet_ntoa(sock_client.sin_addr), sock_client.sin_port);//将新的链接文件描述符添加到pollfd:poll_fds中for (i = 1; i < OPEN_MAX; i++){
if (poll_fds[i].fd < 0){
poll_fds[i].fd = connfd;break;}}if (i == OPEN_MAX){
fprintf(stderr, "too many clients.\n");exit(1);}//将新的描述符添加到度描述符集合中poll_fds[i].events = POLLIN;//记录客户链接套接字数量maxi = (i > maxi ? i : maxi);if (--nready <= 0){
continue;}}//处理所有客户端发来的数据char buffer[MAXLEN];memset(buffer, 0, sizeof(buffer));int readlen = 0;for (i = 1; i < maxi; i++){
if (poll_fds[i].fd < 0)continue;//检测客户端描述符是否准备好if (poll_fds[i].revents & POLLIN){
//接收客户端发来的消息readlen = read(poll_fds[i].fd, buffer, MAXLEN);if (readlen == 0){
close(poll_fds[i].fd);poll_fds[i].fd = -1;continue;}//write(STDOUT_FILENO, buffer, readlen);//write(poll_fds[i].fd, buffer, readlen);}}}
}int main()
{
int listenfd = bind_and_listen();if (listenfd < 0)return 0;do_poll(listenfd);return 0;
}
client.cpp
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>#define MAXLEN 1024
#define PORT 6666
#define max(a, b) (a > b) ? a : bstatic void handle_connection(int sockfd);int main(int argc, char *argv[])
{
int connfd = 0;struct sockaddr_in client;if (argc < 2){
printf("Uasge : Clientent [Server IP address]\n");return -1;}client.sin_addr.s_addr = inet_addr(argv[1]);client.sin_family = AF_INET;client.sin_port = htons(PORT);connfd = socket(AF_INET, SOCK_STREAM, 0);if (connfd < 0){
perror("socket");return -2;}if (connect(connfd, (struct sockaddr*)(&client), sizeof(struct sockaddr)) < 0){
perror("connect");return -3;}//处理链接描述符handle_connection(connfd);return 0;
}static void handle_connection(int sockfd)
{
char sendline[MAXLEN], recvline[MAXLEN];struct pollfd poll_fds[2];int n;//添加链接描述符poll_fds[0].fd = sockfd;poll_fds[0].events = POLLIN;//添加标准输入描述符poll_fds[1].fd = STDIN_FILENO;poll_fds[1].events = POLLIN;while (1){
poll(poll_fds, 2, -1);if (poll_fds[0].revents & POLLIN){
n = read(sockfd, recvline, MAXLEN);if (n == 0){
fprintf(stderr, "client : server is closed.\n");close(sockfd);}write(STDOUT_FILENO, recvline, n);}//检测标准输入描述符是否准备好if (poll_fds[1].revents & POLLIN){
n = read(STDOUT_FILENO, sendline, MAXLEN);if (n == 0){
shutdown(sockfd, SHUT_WR);printf("111111111111111111111\n");continue;}write(sockfd, sendline, n);}}
}