多路转接(使用poll实现)
文章目录
1. poll的优缺点
如果说select开辟了多路转接的道路,那么poll就是select的升级版。
1.1 poll的优点
不同于select使用三个位图来表示三个fd_set的方式,poll使用一个pollfd的指针实现。
1.pollfd结构包含了要监视的event和就绪的event,不再使用select"参数-值"传递的方式,接口使用比select更加方便。
2.poll没有最大的数量限制(但是数量过大后性能也会下降)。
1.2 poll的缺点
poll中监听文件的文件描述符数目增大时:
1.和select一样,poll返回后,需要轮询pollfd来获取就绪的描述符。
2.每次调用poll都需要把大量的pollfd结构从用户态拷贝到内核中。
3.同时连接的大量客户端在一时刻可能只有很少处于就绪状态,因此随着监视的描述符数量的增长,其效率也会直线下降。
2. poll函数原型
2.1 poll与pollfd内容
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
同时我们需要知道pollfd这个结构体结构:
struct pollfd {
int fd; /* file descriptor /
short events; / requested events /
short revents; / returned events */
};
2.2 参数与返回值
2.2.1 参数
在select中需要我们自己维护一个数组来存储需要关心的fd,在poll中通过将数组首元素地址与数组长度结合的方式来表示这个数组。
fds是指向数组首元素地址的指针,而nfds表示的就是数组的长度。最后一个参数timeout指的是超时等待时间,与select不同的是,它是以毫秒为单位的。
2.2.2 pollfd
数组中的每一个元素都是一个pollfd类型,因此,这个类型中一定包括文件描述符fd,以及关于该文件描述符的事件。
在select中,需要关心的事件与已经就绪的事件使用的是同一个fd_set位图结构,而在poll中,events表示的是OS需要关心该fd中的事件;revents表示的是OS要告诉用户哪些事件已经就绪了。
2.2.3 事件的添加
在select中是根据位置来进行事件的添加的,比如函数select的第三个位置不是空,就表示有文件描述符的读操作需要关心。poll的升级在于它可以将事件进行具体的划分,即增加多个事件种类。注意,这里的events是short类型,但是我们也可以将它看成一个位图结构,方便我们进行事件的添加。添加事件的本质就在于按位与或上一个表示某一个事件的宏。
下面给出这些宏的含义,以及它们的使用范围(在events中使用还是在revents中使用)
| 事件 | 含义 | events | revents |
|---|---|---|---|
| POLLIN | 数据(包括普通数据和优先数据) | 是 | 是 |
| POLLRDNORM | 普通数据可读 | 是 | 是 |
| POLLRDBAND | 优先级带数据可读(Linux不支持) | 是 | 是 |
| POLLPRI | 高优先级数据可读,比如TCP带外数据 | 是 | 是 |
| POLLOUT | 数据(包括普通数据和优先数据)可写 | 是 | 是 |
| POLLWRNORM | 普通数据可写 | 是 | 是 |
| POLLWRBAND | 优先级带数据可写 | 是 | 是 |
| POLLRDHUP | TCP连接被对方关闭,或者对方关闭了写操作,它由GNU引入 | 是 | 是 |
| POLLERR | 错误 | 否 | 是 |
| POLLHUP | 挂起。比如管道的写端被关闭了之后,读端描述符上将收到POLLHUP事件 | 否 | 是 |
| POLLNVAL | 文件描述符没有打开 | 否 | 是 |
2.2.4 返回值
返回值小于0,表示出错。
返回值等于0,表示poll函数等待超时。
返回值大于0,表示poll由于监听的文件描述符就绪而返回。
3.poll实现多路转接
3.1实现思路
1.实现思路上和select是完全一样的,首先定义一个pollfd类型的数组,并将其进行初始化,并将第一个数组的位置交给listen套接字。
2.使用poll函数等待,当有事件就绪的时候它的返回值会大于0,然后遍历整个数组,将不关心的位置以及没有就绪的排除掉。
3.剩下的就是就绪的文件描述符,当文件描述符时listen_sock的时候,说明有新的链接到来了,此时需要进行accept新的文件描述符,并在数组中为其找到对应的位置,如果没有那么说明已经满载了,此时直接执行四次挥手,即将文件描述符关闭。
如果有空位置,那么就将其填入进去,并设置好对应的监听选项。
4.如果是普通的套接字就绪了,那么就进行读操作,如果对端关闭或者读失败,则关闭对应的文件描述符。
3.1通信代码
#include <iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>
using namespace std;
namespace ns_Sock
{
class Sock
{
private:
public:
static int Socket()
{
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
cerr << "创建套接字失败" << endl;
exit(-2);
}
else
{
return sock;
}
}
static int Listen(int sock)
{
if(listen(sock,5)<0)
{
cerr<<"listen error"<<endl;
exit(-3);
}
}
static int Accept(int sock)
{
struct sockaddr_in peer;
socklen_t len=sizeof(peer);
int fd=accept(sock,(struct sockaddr*)&peer,&len);
if(fd>=0)
{
return fd;
}
else
{
exit(-5);
}
}
static void Bind(int sock,uint16_t port)
{
sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port=htons(port);
local.sin_addr.s_addr=INADDR_ANY;
if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
{
cerr<<"bind error"<<endl;
exit(-4);
}
}
static void Connect(int sock,string ip,uint16_t port)
{
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_family=AF_INET;
server.sin_port=htons(port);
server.sin_addr.s_addr=inet_addr(ip.c_str());
if(connect(sock,(struct sockaddr*)&server,sizeof(server))==0)
{
cout<<"connect success!"<<endl;
}
else
{
cout<<"connect fail"<<endl;
exit(-5);
}
}
};
}
3.2poll实现多路转接代码
#include "Sock.hpp"
#include <poll.h>
using namespace ns_Sock;
#define NUM 128
struct pollfd nfds[NUM];
void Usage(char *proc)
{
cout << proc << " port" << endl;
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
exit(-1);
}
uint16_t port = (uint16_t)atoi(argv[1]);
int listen_sock = Sock::Socket();
Sock::Bind(listen_sock, port);
Sock::Listen(listen_sock);
for (int i = 0; i < NUM; i++)
{
nfds[i].fd = -1;
}
nfds[0].fd = listen_sock; //将数组中第一个元素设为listen套接字
nfds[0].events = POLLIN;
nfds[0].revents = 0;
int timeout = 1000;
int count=0;
for (;;)
{
count++;
int n = poll(nfds, NUM, -1);
switch (n)
{
case 0:
cout << "timeout......" << endl;
break;
case -1:
cout << "error" << endl;
break;
default:
cout << "有数据就绪了,可以进行读取了" << endl;
for (int i = 0; i < NUM; i++)
{
if (nfds[i].fd == -1)
{
continue; //不关心的文件描述符
}
if (nfds[i].revents != 0)
{
//当有监听事件就绪时
cout << "有监听的事件就绪了,可以进行读取了" << endl;
if (nfds[i].fd == listen_sock)
{
cout << "有新的链接到来了" << endl;
int sock = Sock::Accept(listen_sock);
if (sock >= 0)
{
cout << "新链接创建成功" << endl;
int pos = 1;
for (pos = 1; pos < NUM; pos++)
{
if (nfds[pos].fd == -1)
{
break;
}
}
//找到了一个位置还没有被使用
if (pos < NUM)
{
cout << "新链接" << sock << "已经被填到了数组[" << pos << "]的位置" << endl;
nfds[pos].fd = sock;
nfds[pos].events = POLLIN;
}
else
{
cout << "服务器满载了" << endl;
close(sock);
}
}
}
else
{
cout << nfds[i].fd << "上面有数据读取" << endl;
char recvbuffer[1024] = {0};
ssize_t s = read(nfds[i].fd, recvbuffer, sizeof(recvbuffer) - 1);
if (s > 0)
{
recvbuffer[s] = '\0';
cout << "读取成功了" << endl;
cout << "client " << nfds[i].fd << "#" << recvbuffer << endl;
}
else if (s == 0)
{
cout << "客户端退出啦" << endl;
close(nfds[i].fd);
cout << "客户端" << nfds[i].fd << "退出了"
<< "已经将其数组位置的fd置为-1" << endl;
nfds[i].fd = -1;
}
else
{
cout << "出现读写失败" << endl;
close(nfds[i].fd);
cout << "读出错" << nfds[i].fd << "断开连接,已经将其数组位置的fd置为-1" << endl;
nfds[i].fd = -1;
}
}
}
//有数据就绪了
}
break;
}
}
}

浙公网安备 33010602011771号