🔥博客主页: 我要成为C++领域大神
🎥系列专栏:【C++核心编程】 【计算机网络】 【Linux编程】 【操作系统】
❤️感谢大家点赞👍收藏⭐评论✍️本博客致力于知识分享,与更多的人进行学习交流
poll模型
关于select模型可以查看我这期文章【高性能服务器】select模型
实现原理
poll
不再用 BitsMap 来存储所关注的文件描述符,取而代之用动态数组,以链表形式来组织,突破了 select 的文件描述符个数限制,当然还会受到系统文件描述符限制。但是 poll 和 select 并没有太大的本质区别,都是使用「线性结构」存储进程关注的 Socket 集合,因此都需要遍历文件描述符集合来找到可读或可写的 Socket,时间复杂度为 O(n),而且也需要在用户态与内核态之间拷贝文件描述符集合,这种方式随着并发数上来,性能的损耗会呈指数级增长。
核心接口
struct pollfd listen_array[10000];
自定义长度
struct pollfd node
结构体
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
node.fd=sockfd; //存储监听的socket,取消监听设置-1
node.events=POLLIN |POLLOUT | POLLERR; //设置要监听的socket事件
node.revents=POLLIN //当socket就绪后,传出就绪事件,后续使用revents判断socket就绪
int poll(struct pollfd *fds, nfds_t nfds, int timeout)
timeout
-1阻塞 >0定时阻塞 0非阻塞
代码实现
PollSock.h
#ifndef _POLLSOCK_H_
#define _POLLSOCK_H_
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <poll.h>
int SOCKET(int domain, int type, int protocol);
int BIND(int sockfd, struct sockaddr* addr, socklen_t addrlen);
ssize_t RECV(int sockfd, void* buf, size_t len, int flags);
ssize_t SEND(int sockfd, void* buf, size_t len, int flags);
int CONNECT(int sockfd, struct sockaddr* addr, socklen_t addrlen);
int ACCEPT(int sockfd, struct sockaddr* addr, socklen_t* addrlen);
int LISTEN(int sockfd, int backlog);
char* FGETS(char* s, int size, FILE* stream);
int POLL(struct pollfd *fds, nfds_t nfds, int timeout);
int socket_init();
int return_response(int clientfd, const char* clientip);
// 全局变量声明
char recv_buf[1024];
char time_buf[64];
int serverFd, clientFd;
struct sockaddr_in clientAddr;
struct pollfd client_array[10000];
int ready;
socklen_t addrlen;
char clientip[16];
time_t tp;
ssize_t recvlen;
int toupper_flag;
#define SHUTDOWN 1
#endif
PollSock.c
#include "PollSock.h"
int SOCKET(int domain, int type, int protocol) {
int reval = socket(domain, type, protocol);
if (reval == -1) {
perror("socket call failed");
exit(0);
}
return reval;
}
int BIND(int sockfd, struct sockaddr* addr, socklen_t addrlen) {
int reval = bind(sockfd, addr, addrlen);
if (reval == -1) {
perror("bind call failed");
exit(0);
}
return reval;
}
ssize_t RECV(int sockfd, void* buf, size_t len, int flags) {
ssize_t reval;
reval = recv(sockfd, buf, len, flags);
return reval;
}
ssize_t SEND(int sockfd, void* buf, size_t len, int flags) {
ssize_t reval;
reval = send(sockfd, buf, len, flags);
if (reval == -1)
perror("send call failed");
return reval;
}
int CONNECT(int sockfd, struct sockaddr* addr, socklen_t addrlen) {
int reval = connect(sockfd, addr, addrlen);
if (reval == -1) {
perror("connect call failed");
exit(0);
}
return reval;
}
int ACCEPT(int sockfd, struct sockaddr* addr, socklen_t* addrlen) {
int reval = accept(sockfd, addr, addrlen);
if (reval == -1) {
perror("accept call failed");
exit(0);
}
return reval;
}
int LISTEN(int sockfd, int backlog) {
int reval = listen(sockfd, backlog);
if (reval == -1) {
perror("listen call failed");
exit(0);
}
return reval;
}
char* FGETS(char* s, int size, FILE* stream) {
char* str;
if ((str = fgets(s, size, stream)) != NULL) {
return str;
} else {
perror("fgets call failed");
exit(0);
}
}
int socket_init() {
struct sockaddr_in sockAddr;
sockAddr.sin_family = AF_INET;
sockAddr.sin_addr.s_addr = htonl(INADDR_ANY);
sockAddr.sin_port = htons(8080);
int sock_fd = SOCKET(AF_INET, SOCK_STREAM, 0);
BIND(sock_fd, (struct sockaddr*)&sockAddr, sizeof(sockAddr));
LISTEN(sock_fd, 5);
return sock_fd;
}
int return_response(int clientfd, const char* clientip) {
char response[1024];
bzero(response, sizeof(response));
sprintf(response, "Hi [%s],This is TCP Server Working...\n", clientip);
SEND(clientfd, response, sizeof(response), 0);
}
int POLL(struct pollfd *fds, nfds_t nfds, int timeout){
int reval=poll(fds,nfds,timeout);
if(reval==-1)
{
perror("poll call failed");
exit(0);
}
return reval;
}
PollServer.c
#include "PollSock.h"
int main() {
bzero(recv_buf, sizeof(recv_buf));
bzero(time_buf, sizeof(time_buf));
bzero(clientip,sizeof(clientip));
serverFd = socket_init();
int i;
for (i = 0; i < 10000; ++i) {
client_array[i].fd = -1;//初始化poll数组
client_array[i].events=POLLIN;//设置所有socket默认为监听读
}
client_array[0].fd=serverFd;
printf("Test poll Server is Running...\n");
while (SHUTDOWN) {
ready = POLL(client_array, 10000, -1); //阻塞监听
while (ready) { // 辨别就绪
if (client_array[0].revents==POLLIN) {
addrlen = sizeof(clientAddr);
clientFd =ACCEPT(serverFd, (struct sockaddr*)&clientAddr, &addrlen);
inet_ntop(AF_INET, &clientAddr.sin_addr.s_addr, clientip, 16);
printf("Listen Server Socket Successfully Call Accept, Client IP [%s], PORT[%d]\n",clientip, ntohs(clientAddr.sin_port));
return_response(clientFd, clientip);
for (i = 1; i < 100000; ++i)
if (client_array[i].fd == -1) {
client_array[i].fd = clientFd;
break;
}
} else {
// 仅处理一次客户端请求,单进程不允许客户端持续占用
for (i = 1; i < 10000; ++i)
{if (client_array[i].fd != -1)
if (client_array[i].revents==POLLIN)
{
if ((recvlen = RECV(client_array[i].fd, recv_buf, sizeof(recv_buf), 0)) >0)
{ // 处理客户端业务
printf("Client Say:%s\n", recv_buf);
if (strcmp(recv_buf, "localtime") == 0) {
tp = time(NULL); // 获取时间种子
ctime_r(&tp, time_buf);
time_buf[strcspn(time_buf, "\n")] = '\0';
printf("[%s]Response SysTime Successfully!\n", clientip);
SEND(client_array[i].fd, time_buf, strlen(time_buf) + 1, 0);
bzero(time_buf, sizeof(time_buf));
} else {
toupper_flag = 0;
while (recvlen > toupper_flag) {
recv_buf[toupper_flag] = toupper(recv_buf[toupper_flag]);
++toupper_flag;
}
printf("[%s]Response Toupper Successfully!\n", clientip);
SEND(client_array[i].fd, recv_buf, recvlen, 0);
bzero(recv_buf, sizeof(recv_buf));
}
} else if (recvlen == 0) {
close(client_array[i].fd);
client_array[i].fd = -1;
printf("Client is Exiting, Delete Listen Item.\n");
}
break;
}
}
}
ready--;
}
}
printf("Server is Over\n");
close(serverFd);
return 0;
}
Client.c
#include "MySock.c"
//客户端源码编写,连接服务器成功,服务器反馈信息
#define _IP "39.102.213.44"
#define _PORT 8080
int main()
{
struct sockaddr_in ServerAddr;
bzero(&ServerAddr,sizeof(ServerAddr));
ServerAddr.sin_family=AF_INET;
ServerAddr.sin_port=htons(_PORT);
inet_pton(AF_INET,_IP,&ServerAddr.sin_addr.s_addr);
int Myfd=SOCKET(AF_INET,SOCK_STREAM,0);
//看需求决定是否要绑定
char Response[1024];//存放服务端反馈信息
ssize_t recvlen;
bzero(Response,sizeof(Response));
char sendbuf[1024];
if((CONNECT(Myfd,(struct sockaddr *)&ServerAddr,sizeof(ServerAddr)))==0)
{
while(1)
{
if((recvlen=RECV(Myfd,Response,sizeof(Response),0))>0)
{
printf("%s\n",Response);
}
printf("Please Type Some text:");//读取标准输入发送给服务端
FGETS(sendbuf,sizeof(sendbuf),stdin);
sendbuf[strcspn(sendbuf,"\n")]='\0';
SEND(Myfd,sendbuf,sizeof(sendbuf),0);
}
}
close(Myfd);
printf("Client is Over\n");
return 0;
}
运行结果
这里的pollfd结构体数组虽然设置的大小是10000,但是当打开的客户端超过1023个(还有一个是服务端socket)后,服务端就会崩溃。因为系统默认一个进程可以打开的文件描述符是1024个。通过ulimit -a
可以查看
通过cat /proc/sys/fs/file-max
命令可以查看系统可以使用的描述符最大值
可以通过ulimit -n
临时修改一个进程默认打开文件描述符最大值(注意不能超过系统限制),仅在当前终端下生效,新的终端失效
要想在所有情况都生效,可以修改系统配置。sudo vi /etc/security/limits.conf
查看系统限制配置,修改软限制和硬限制。
保存退出,重启生效
poll模型优缺点
优点:
1.poll可以监听的事件数量更丰富
2.poll可以对不同的socket进行不同的监听设置,设置方式更灵活
3.进行了传入传出分离设置,events传入监听的事件,revents传出就绪事件
缺点:
1.兼容性差,Windows平台不支持,Linux少数不支持
2.轮询问题, 随着轮询数量的增长,IO处理性能呈线性下降
3.poll只返回就绪的数量,没有反馈就绪的socket,需要用户自行遍历查找,开销较大
4.select多轮使用会出现大量重复的拷贝开销和挂载监听开销
5.仅支持毫秒级别的定时阻塞监