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:acceptfd
struct sockaddr_in my_addr; //本机地址信息
//初始化流式socket
if ((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 : b
static 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);
}
}
}