多路复用——poll
在之前我们说过TCP的模型,用户可以通过连接服务器从而通过服务器去加载服务端对应功能的文件来达到通信,但是我们知道,一个用户加载文件需要一个进程,在Linux中,一个进程就要占用4G的虚拟内存,如果在并发量高的情况下,很明显服务器是不堪重负的。下面来讲解决方案即多路复用技术。
多路复用,举个例子就好比把多条狭窄的乡间小路修成一条双向16车道的快速路,即在这条大路上去进行网络通信。多路复用技术的核心就在于在单个网络信道上传输多个网络信号,能能大幅提高通信效率,并且能够很大程度上降低服务器的负担。
下面我们用TCP作为通信模型来看多路复用的第二种 I/O 机制,即poll:
服务端如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <poll.h>
int add_fds(struct pollfd *fds, int nfds, int fd, short events)
{
for (int i = 0; i < nfds; i++)
{
if(0==fds[i].fd)
{
fds[i].fd=fd;
fds[i].events=events;
return 0;
}
}
return -1;
}
//tcp server 只有server端能用多路复用
int main(int argc, char *argv[])
{
//创建socket
int svr_fd = socket(AF_INET, SOCK_STREAM, 0);
if (svr_fd < 0)
{
perror("socket");
return -1;
}
//准备通信地址
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8888);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
socklen_t addrlen=sizeof(addr);
//绑定地址
if (bind(svr_fd, (struct sockaddr*)&addr, addrlen))
{
perror("bind");
return -1;
}
//监听连接
if (listen(svr_fd, 10))
{
perror("listen");
return -1;
}
//创建pollfd数组并初始化
struct pollfd *fds=calloc(sizeof(struct pollfd), 10);
//设置[0]位置要监听的描述符和事件
fds[0].fd=svr_fd;
fds[0].events=POLLIN;
//定义超时时间
int timeout=10000;
char buf[1024];
size_t buf_size=sizeof(buf);
while (1)
{
int ret=poll(fds, 10, timeout);
if (ret == 0) continue; //超时
if (ret < 0)
{
perror("poll");
return -1;
}
//判断[0]位置是否有读事件产生
if (fds[0].revents & POLLIN)
{
//接受连接
int cli_fd=accept(svr_fd, (struct sockaddr*)&addr, &addrlen);
if(cli_fd>0)
{
//把客户端的socket描述符添加到pollfd数组的空位置,并设置事件
if(add_fds(fds, 10, cli_fd, POLLIN))
{
printf("客户端数量已满\n");
}
}
}
else
{
//遍历pollfd数组,判断其它位置是否有读事件产生
for (int i = 1; i < 10; i++)
{
if (!fds[i].revents & POLLIN) continue;
int ret=recv(fds[i].fd, buf, buf_size, 0);
if (ret <= 0||0==strcmp(buf, "quit"))
{
printf("客户端 %d 退出\n", fds[i].fd);
fds[i].fd=0;
fds[i].events=0;
continue;
}
printf("recv:%s bits:%d\n", buf, ret);
strcat(buf, ":return");
ret=send(fds[i].fd, buf, strlen(buf)+1, 0);
if (ret <= 0)
{
printf("客户端 %d 退出\n", fds[i].fd);
fds[i].fd=0;
fds[i].events=0;
continue;
}
}
}
}
return 0;
}
客户端如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>
typedef struct sockaddr *SP;
int main(int argc,const char* argv[])
{
//创建socket
int cli_fd=socket(AF_INET,SOCK_STREAM,0);
if(cli_fd<0)
{
perror("socket");
return -1;
}
//准备通信地址
struct sockaddr_in addr={};
addr.sin_family=AF_INET;
addr.sin_port=htons(8888);
addr.sin_addr.s_addr=inet_addr("127.0.0.1");
socklen_t addrlen=sizeof(addr);
//连接服务器
if(connect(cli_fd,(SP)&addr,addrlen))
{
perror("connect");
return -1;
}
char buf[4096];
size_t buf_size=sizeof(buf);
while(1)
{
//发送请求
printf(">>>>>");
scanf("%s",buf);
int ret=send(cli_fd,buf,strlen(buf)+1,0);
//ret=write(cli_fd,buf,strlen(buf)+1);
if(ret<=0)
{
printf("服务器正在升级,请稍后重试\n");
break;
}
if(0==strcmp("quit",buf))
{
printf("通信结束\n");
break;
}
//接收请求
//int ret=read(cli_fd,buf,buf_size);
ret=recv(cli_fd,buf,buf_size,0);
if(ret<=0)
{
printf("服务器正在维护,请稍候重试\n");
break;
}
printf("read:%s bits:%d\n",buf,ret);
}
return 0;
}
over