认识select
- 一定要以某种形式,一次等待多个fd (仅仅是等待,没有拷贝)
- 哪一个fd或者那些个fd就绪,用户需要知道的
- select 要等的的多个fd,一定有少量或者全部都是准备好的,一般是少量
select调用失败时,错误码可能被设置为:
- EBADF 文件描述词为无效的或该文件已关闭
- EINTR 此调用被信号所中断
- EINVAL 参数n 为负值。
- ENOMEM 核心内存不足
关于fd_set
结构体
typedef struct
{
/* XPG4.2 requires this member name. Otherwise avoid the name
from the global namespace. */
#ifdef __USE_XOPEN
__fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->fds_bits)
#else
__fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->__fds_bits)
#endif
} fd_set;
其实这个结构就是一个整数数组, 更严格的说, 是一个 “位图”. 使用位图中对应的位来表示要监视的文件描述符
提供了一组操作fd_set的接口, 来比较方便的操作位图
void FD_CLR(int fd, fd_set *set); // 用来清除描述词组set中相关fd 的位
int FD_ISSET(int fd, fd_set *set); // 用来测试描述词组set中相关fd 的位是否为真
void FD_SET(int fd, fd_set *set); // 用来设置描述词组set中相关fd的位
void FD_ZERO(fd_set *set); // 用来清除描述词组set的全部位
select服务器
select服务器,使用时需要程序员自己维护一个第三方的数组,来进行已经获得的sock(文件描述符)进行管理。
// Sock.hpp
#pragma once
#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
class Sock
{
public:
Sock(int ret = -1)
:_sock(ret)
{}
void Socket()
{
_sock = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == _sock)
{
std::cout<< "socket error" << std::endl;
exit(-1);
}
}
void Bind(const uint16_t& port)
{
struct sockaddr_in local;
memset(&local, 0, sizeof(local));//强烈建议做清空
local.sin_family = AF_INET;//通信方式
local.sin_port = htons(port);//端口号
local.sin_addr.s_addr = INADDR_ANY;//ip地址
if(bind(_sock, (struct sockaddr*)&local, sizeof(local)) < 0)
{
std::cout<< "bind error" << std::endl;
exit(-1);
}
}
void Listen()
{
if(listen(_sock, 0) < 0)
{
std::cout<< "listen error" << std::endl;
exit(-1);
}
}
int Accept(std::string* clientIp, uint16_t* clientPort)
{
struct sockaddr_in temp;
socklen_t len = sizeof(temp);
int sock = accept(_sock, (struct sockaddr*)&temp, &len);
if(sock < 0)
{
std::cout<< "accept error" << std::endl;
exit(-1);
}
else
{
*clientIp = inet_ntoa(temp.sin_addr);
*clientPort = ntohs(temp.sin_port);
}
return sock;
}
// 输入: const &
// 输出: *
// 输入输出: &
int Connect(std::string& serverIp, uint16_t& serverPort)
{
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverPort);
server.sin_addr.s_addr = inet_addr(serverIp.c_str());
return connect(_sock, (struct sockaddr *)&server, sizeof(server));
}
int Fd()
{
return _sock;
}
void Close()
{
if (_sock != -1)
close(_sock);
}
~Sock()
{}
private:
int _sock;
};
//selectServer.hpp
#pragma once
#include <iostream>
#include <sys/select.h>
#include "Sock.hpp"
const static uint16_t gport = 8888;
typedef int type_t;
static const int defaultfd = -1;
class selectServer
{
static const int N = sizeof(fd_set) *8 ;
public:
selectServer(uint16_t port = gport)
:_port(port)
{}
void InitServer()
{
_listenSock.Socket();
_listenSock.Bind(_port);
_listenSock.Listen();
for(int i = 0; i < N; ++i)
{
fdarray[i] = defaultfd;
}
}
void Accepter()
{
std::cout<< "Accepter();" << std::endl;
std::string clientip;
uint16_t clientport;
int sock = _listenSock.Accept(&clientip,&clientport);
if(sock < 0)
{
return;
}
int pos = 1;
for(; pos < N; ++pos)
{
if(fdarray[pos] == defaultfd)
{
fdarray[pos] = sock;
break;
}
}
if(pos >= N)
{
close(sock);
std::cerr << "fd_set的位图已经超过了其大小"<<std::endl;
}
}
void HandlerEvent(fd_set &rfds)
{
for(int i = 0; i < N; ++i)
{
if(fdarray[i] == defaultfd)
continue;
if((fdarray[i] == _listenSock.Fd()) && (FD_ISSET(fdarray[i], &rfds)))//将监听套接字文件准备就绪
{
Accepter();
}
else if((fdarray[i] != _listenSock.Fd()) && (FD_ISSET(fdarray[i], &rfds)))
{
int fd = fdarray[i];
char buffer[1024];
ssize_t s = recv(fd, buffer, sizeof(buffer) - 1, 0);
if (s > 0)
{
buffer[s-1] = 0;
std::cout << "client# " << buffer << std::endl;
// 发送回去也要被select管理的,TODO
std::string echo = buffer;
echo += " [select server echo]";
send(fd, echo.c_str(), echo.size(), 0);
}
else
{
if (s == 0)
{
close(fdarray[i]);
fdarray[i] = defaultfd;
}
else
{
std::cerr<<errno<< ":"<< strerror(errno) << std::endl;
}
}
}
}
}
void Start()
{
// 1. 这里我们能够直接获取新的链接吗?
// 2. 最开始的时候,我们的服务器是没有太多的sock的,甚至只有一个sock!listensock
// 3. 在网络中, 新连接到来被当做 读事件就绪!
fdarray[0] = _listenSock.Fd();
while(true)
{
// 因为rfds是一个输入输出型参数,注定了每次都要对rfds进行重置,重置,必定要知道我历史上都有哪些fd?fdarray[]
// 因为服务器在运行中,sockfd的值一直在动态变化,所以maxfd也一定在变化, maxfd是不是也要进行动态更新, fdarray[]
fd_set rfds;
FD_ZERO(&rfds);
int maxfd = fdarray[0];
for (int i = 0; i < N; ++i)
{
if(fdarray[i] == defaultfd)
continue;
FD_SET(fdarray[i], &rfds);
if(maxfd < fdarray[i])
{
maxfd = fdarray[i];
}
}
int n = select(maxfd + 1,&rfds,nullptr,nullptr,nullptr);
switch(n)
{
case 0:
std::cout << "没有文件描述符,就绪" << std::endl;
break;
case -1:
std::cerr<<errno<< ":"<< strerror(errno) << std::endl;
break;
default:
std::cout << "有一个就绪事件发生了" << std::endl;
HandlerEvent(rfds);
DebugPrint();
break;
}
}
}
void DebugPrint()
{
std::cout << "fdarray[]: ";
for (int i = 0; i < N; i++)
{
if (fdarray[i] == defaultfd)
continue;
std::cout << fdarray[i] << " ";
}
std::cout << "\n";
sleep(1);
}
private:
uint16_t _port;
Sock _listenSock;
type_t fdarray[N];
};
//test.cc
#include <iostream>
#include <memory>
#include "selectServer.hpp"
int main()
{
std::unique_ptr<selectServer> svr(new selectServer());
svr->InitServer();
svr->Start();
return 0;
}
select缺点
- 每次调用select, 都需要手动设置fd集合, 从接口使用角度来说也非常不便.
- 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
- 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
- select支持的文件描述符数量太小.