binglinuxc(多路转接)
Posted 月屯
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了binglinuxc(多路转接)相关的知识,希望对你有一定的参考价值。
目录标题
多路转接IO
接口
select
select使用方式
- select共有三个事件集合:读事件集合,写事件集合,异常事件集合
- 当需要关注某个文件描述符的某个事件,则将某个文件描述符添加到对应的事件集合当中
例如:关注0号文件描述符的读事件,则将0号文件描述符添加到读事件集合当中readfds - 如果不关注某种事件,则给select传递参数的时候,传递NULL
select的返回值:
- 返回值为就绪的文件描述符的个数
- 就绪的文件描述符存储在事件集合当中返回给调用者
注意:select会将未就绪的文件描述符从事件集合当中去除掉,因此,再次监控的时候需要重新添加
结论:
1.事件集合在内核当中是以数组定义的,但是使用方式是位图
2.位图的大小取决于内核宏__FD_SETSIZE
3.目前的位图的大小为1024比特位,所以select只能监控0~1023号文件描述符
select使用事件集合方式
代码实例
#include <stdio.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
int listen_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(listen_sockfd < 0)
perror("socket");
return 0;
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(29090);
addr.sin_addr.s_addr = inet_addr("0.0.0.0");
int ret = bind(listen_sockfd, (struct sockaddr*)&addr, sizeof(addr));
if(ret < 0)
perror("bind");
return 0;
listen(listen_sockfd, 5);
/*
* 测试0号文件描述符的可读事件
* 0的可读事件怎么触发?标准输入当中输入内容, 回车就能触发
*
* 1.初始化一个可读事件集合, 将0号描述符添加进去
* 2.调用select进行监控
* 3.处理0号文件描述符的可读事件-read读
* 读完之后打印
* */
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(0, &readfds);
FD_SET(listen_sockfd, &readfds);
while(1)
fd_set tmp = readfds;
int ret = select(listen_sockfd + 1, &tmp, NULL, NULL, NULL);
printf("ret : %d\\n", ret);
if(FD_ISSET(0, &tmp))
char buf[1024] = 0;
read(0, buf, sizeof(buf) - 1);
printf("buf: %s", buf);
else
printf("0 is not in readfds\\n");
if(FD_ISSET(listen_sockfd, &tmp))
//TODO accept
printf("listen_sockfd ready\\n");
accept(listen_sockfd, NULL, NULL);
else
printf("listen_sockfd not in readfds\\n");
return 0;
单线程tcp
select封装
#include <stdio.h>
#include <unistd.h>
#include <sys/select.h>
#include <vector>
class SelectSvr
public:
SelectSvr()
/*
* 1. 清空事件集合 + 初始化 max_fd_
* */
FD_ZERO(&readfds_);
max_fd_ = -1;
~SelectSvr()
void AddFd(int fd)
/*
* 1.添加
* 2.更新最大文件描述符
* */
FD_SET(fd, &readfds_);
if(fd > max_fd_)
max_fd_ = fd;
void DeleteFd(int fd)
/*
* 1.移除文件描述符
* 2.更新
* */
FD_CLR(fd, &readfds_);
// 0 2 5 8
// 2
// 0 5 8 == >8
//
//
// 0 2 5 8
// 8
// 0 2 5 => 5
for(int i = max_fd_; i >= 0; i--)
if(FD_ISSET(i, &readfds_))
max_fd_ = i;
break;
int Select(std::vector<int>* vec)
int ret = -1;
while(1)
fd_set tmp = readfds_;
ret = select(max_fd_ + 1, &tmp, NULL, NULL, NULL);
if(ret < 0)
return ret;
else if(ret == 0)
continue;
/*
* 监控成功
* */
for(int i = 0; i <= max_fd_; i++)
if(FD_ISSET(i, &tmp))
vec->push_back(i);
break;
return ret;
private:
fd_set readfds_;
int max_fd_;
;
服务端单线程
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "my_select.hpp"
/*
* 1.tcp的初始化工作
* 2.select监控
* 3.依照监控进行处理
* listen_sock
* new_sockfd
* */
int main()
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if(listen_sock < 0)
perror("socket");
return 0;
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(39090);
addr.sin_addr.s_addr = inet_addr("0.0.0.0");
int ret = bind(listen_sock, (struct sockaddr*)&addr, sizeof(addr));
if(ret < 0)
perror("bind");
return 0;
listen(listen_sock, 5);
SelectSvr ss;
ss.AddFd(listen_sock);
while(1)
std::vector<int> vec;
int ret = ss.Select(&vec);
if(ret < 0)
continue;
for(size_t i = 0; i < vec.size(); i++)
if(listen_sock == vec[i])
//侦听套接字
struct sockaddr_in peer_addr;
socklen_t peer_addr_len = sizeof(peer_addr);
int new_sockfd = accept(listen_sock, (struct sockaddr*)&peer_addr, &peer_addr_len);
if(new_sockfd < 0)
continue;
ss.AddFd(new_sockfd);
printf("recv new link, ip : %s, port : %d\\n", inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port));
else
// 新连接套接字有数据到来了
char buf[1024] = 0;
ssize_t recv_size = recv(vec[i], buf, sizeof(buf) - 1, 0);
if(recv_size < 0)
continue;
else if(recv_size == 0)
ss.DeleteFd(vec[i]);
close(vec[i]);
else
printf("[%d sockfd] %s\\n", vec[i], buf);
return 0;
优缺点
优点:
1.According to POSIX.1-2001*/,,遵循posix标准,可以跨平台使用,可以在win平台使用,也可以在linux平台使用
2.select的超时时间可以精确到微妙
缺点:
1.select监控文件描述符的时候,采用轮询遍历的方式,随着监控的文件描述符越多,监控(轮询)效率越低
2.select监控文件描述符的个数是由上限的,.上限取决于内核当中的宏_FD_SETSIZE,这个宏的值为1024
3.select在返回就绪文件描述符的时候,会将未就绪的文件描述符从事件集合当中移除掉,导致二次监控的时候,程序员需要再次手动添加
4.在返回就绪文件描述符的时候,是返回了一个事件集合),并不是将就绪的文件描述符数值直接返回给调用者,需要调用者使用FD_ISSET函数进行判断那些文件描述符就绪了
poll
1.历史地位:跨平台不如select,性能不如后面学到的epoll,比较尴尬的地位
⒉.作用:IO多路转接能够同时等待多个文件描述符的就结状态,换句话说,可以帮助我们同时监控多个文件描述符
3.接口:
int poll(struct pollfd *fds,nfds_t nfds, int timeout);
fds:事件结构数组,存放监控的文件描述符,关心的事件,真实产生的事件nfds:描述fds数组当中有多少有效元素
timeout :超时事件单位s >0:表示带有超时时间的等待 ==0:非阻塞,需要搭配循环来使用<0∶阻塞监控
返回值:> 0:表示多少文件描述符就绪;==0:监控超时;<0:监控出错
例如:无效的文件描述符
#include <stdio.h>
#include <unistd.h>
#include <poll.h>
int main()
/*
* 1.准备事件结构数组
* 2.poll监控
* 3.处理监控结果
* */
struct pollfd arr[10];
arr[0].fd = 0;
arr[0].events = POLLIN | POLLOUT;
int ret = poll(arr, 1, -1);
if(arr[0].revents == POLLIN)
char buf[1024] = 0;
read(arr[0].fd, buf, sizeof(buf) - 1);
printf("buf is: %s ", buf);
return 0;
优缺点:
优点:
1.提出了事件结构的方式,在给poll函数传递参数的时候,不需要分别添加到“事件集合”当中。
⒉.事件结构数组的大小可以根据程序员自己进行定义,并没有上限的要求。
3.不用在监控到就绪之后,重新添加文件描述符
缺点:
1.不支持跨平台
2.内核也是对事件结构数组监控的时候采用轮询遍历的方式
epoll
历史定位:迄今为止,linux平台性能最好的IO多路转接模型。没有之一
接口:
创建epoll句柄:
int epoll_create(int size);
size:目前没有含义了,但是需要大于0,兼容旧内核
返回值:返回的epoll操作句柄;
int epoll_ ctl(int epfd,int op, int fd, strult epoll_event *event);
epfd : epoll操作句柄
op :
EPOLL_CTL_ADD
添加一个文件描述符对应的事件结构到epoll当中
EPOLL_CTL_MOD
修改一个文件描述符的事件结构
EPOLL_CTL_DEL
从epoll当中删除–个文件描述符对应的事件结构
fd:待处理(添加,修改,删除)的文件描述
event:文件描述符对应的事件结构
int epoll_wait(int epfd,struct epoll_event *events, int maxevents,int timeout);
epfd : epoll的操作句柄
events :事件结构数组(集合),从epoll当中获取就绪的事件结构
maxevents :最多━次获取多少个事件结构
timeout :> 0:带有超时事件== 0:非阻塞<0:阻塞
返回值:就绪的文件描述符个数
单线程代码
tcp封装
#pragma once
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <iostream>
#include <string>
#include <cstring>
class TcpSvr
public:
TcpSvr(int tcp_port = 28989)
tcp_port_ = 28989;
~TcpSvr()
TcpSvr(int sockfd, int tcp_port = 28989)
sockfd_ = sockfd;
int InitSvr()
sockfd_ = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd_ < 0)
perror("socket");
return -1;
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(tcp_port_);
addr.sin_addr.s_addr = inet_addr("0.0.0.0");
int ret = bind(sockfd_, (struct sockaddr*)&addr, sizeof(addr));
if(ret < 0)
perror("bind");
return -2;
ret = listen(sockfd_, 5);
if(ret < 0)
perror("listen");
return -3;
return 0;
int Accept()
struct sockaddr_in p_addr;
socklen_t p_addr_len = sizeof(p_addr);
int new_sockfd = accept(sockfd_, (struct sockaddr*)&p_addr, &p_addr_len);
if(new_sockfd < 0)
perror("accept");
printf("hava a new link, %s : %d\\n", inet_ntoa(p_addr.sin_addr), ntohs(p_addr.sin_port));
return new_sockfd;
int Send(std::string& buf)
return send(sockfd_, buf.c_str(), buf.size(), 0);
int Recv(std::string* buf)
char b[1024] = 0;
ssize_t recv_size = recv(sockfd_, b, sizeof(b) - 1, 0);
std::cout << "sockfd: "<<sockfd_ << std::endl;
if(recv_size < 0)
perror("recv");
else if(recv_size == 0)
close(sockfd_);
else
buf->assign(b, strlen(b));
return recv_size;
int GetSockfd()
return sockfd_;
private:
int sockfd_;
int tcp_port_;
;
epoll
#include <sys/epoll.h>
#include "tcp_svr.hpp"
int main()
/*
* 1.初始化tcp的内容
* 2.监控侦听套接字
*
* 3.处理就绪事件
* 3.1 接收新连接, 将新连接的套接字添加到epoll
* 3.2 处理新连接套接字当中的数据(接收数据)
*
*
* */
TcpSvr ts;
if(ts.InitSvr() < 0)
return 0;
int e_fd = epoll_create(5);
if(e_fd < 0)
perror以上是关于binglinuxc(多路转接)的主要内容,如果未能解决你的问题,请参考以下文章