/* According to POSIX.1-2001 */
#include
/* According to earlier standards */
#include
#include
#include
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
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的全部位
下面是关于fd_set的定义:
大致说明一下:fd_set实质是一个long int的数组,而每一个文件描述符对应一个比特位。根据代码可以看出,fd_set的大小取决于__FD_SETSIZE(Linux下默认1024)。注意 :1024表示的不是个数而是大小
怎么算的:
共有__fd_mask × (__FD_SETSIZE / __NFDBITS)× 8个比特位 =
__fd_mask ×(__FD_SETSIZE / (8 * (int) sizeof (__fd_mask)))× 8;
约掉就等于__FD_SETSIZE。
53 /* The fd_set member is required to be an array of longs. */
54 typedef long int __fd_mask;
55
56 /* Some versions of define this macros. */
57 #undef __NFDBITS
58 /* It's easier to assume 8-bit bytes than to get CHAR_BIT. */
59 #define __NFDBITS (8 * (int) sizeof (__fd_mask))
60 #define __FD_ELT(d) ((d) / __NFDBITS)
61 #define __FD_MASK(d) ((__fd_mask) 1 << ((d) % __NFDBITS))
62
63 /* fd_set for select and pselect. */
64 typedef struct
65 {
66 /* XPG4.2 requires this member name. Otherwise avoid the name
67 from the global namespace. */
68 #ifdef __USE_XOPEN
69 __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
70 # define __FDS_BITS(set) ((set)->fds_bits)
71 #else
72 __fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
73 # define __FDS_BITS(set) ((set)->__fds_bits)
74 #endif
75 } fd_set;
参数解释:
nfds:参数nfds是需要监视的最大的文件描述符值+1。
readfds、writefds、exceptfds:分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集 合及异常文件描述符的集合。默认情况下,最大为1024。
参数timeout取值:
NULL:则表示select()没有timeout,select将一直被阻塞,直到某个文件描述符上发生了事件。
0:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生。
特定的时间值:如果在指定的时间段里没有事件发生,select将超时返回。
关于timeval结构:
struct timeval
{
__time_t tv_sec; //秒
__suseconds_t tv_usec; //微秒
};
理解select模型的关键在于理解fd_set,上面我已经说过了。
为方便说明,现在我们假设取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd.
①fd_set set;
②FD_ZERO(&set),则set用位表示是0000 0000;
③若fd=5,执行FD_SET(fd,&set),后set变为0001,0000(第5位置为1) ;
④若再加入fd=2,fd=1,则set变为0001 0011
⑤执行select(6,&set,0,0,0)阻塞等待;
⑥若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000 0011。注意:没有事件发生的fd=5被清空。
所以用户需要继续监视某一个文件描述符,则需要重新添加文件描述符到集合当中去,然后再次调用select。
注意: 对于一个新打开的普通文件的描述符,在文件为空的情况下,select认为该文件描述符是可读的,不是异常事件。
特点
缺点
下面是和tcp协议一起封装的:
select.hpp
#pragma once
#include
#include
#include
#include
#include "tcpsvr.hpp"
class selectSvr
{
public:
selectSvr()
{
FD_ZERO(&_readfds);
_max_readfd = -1;
}
//添加
void Add(int fd)
{
//添加到集合当中
FD_SET(fd, &_readfds);
//更新一下最大的文件描述符
if (fd > _max_readfd) {
_max_readfd = fd;
}
}
//删除
void Delete(int fd)
{
//删除
FD_CLR(fd, &_readfds);
//更新
for (int i = _max_readfd; i >= 0; --i) {
if (!FD_ISSET(i, &_readfds)) {
continue;
}
_max_readfd = i;
break;
}
}
//监控
bool SelectWait(std::vector<TcpSvr>* vec, int usec = 100)
{
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = usec;
//监控
fd_set tmp = _readfds;
int ret = select(_max_readfd + 1, &tmp, NULL, NULL, &tv);
if(ret < 0) {
perror("select");
return false;
}
else if (ret == 0) {
//printf("select timeout\n");
return false;
}
//select返回ret大于0
for (int i = 0; i <= _max_readfd; ++i) {
if (FD_ISSET(i, &tmp)) {
TcpSvr ts;
ts.SetFd(i);
vec->push_back(ts);
}
}
return true;
}
private:
fd_set _readfds;
//当前想让select监听的最大文件描述符
int _max_readfd;
};
tcpsvr.hpp
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
class TcpSvr
{
public:
TcpSvr()
{
_sock = -1;
}
~TcpSvr()
{
_sock = -1;
}
void SetFd(int fd)
{
_sock = fd;
}
int GetFd()
{
return _sock;
}
bool CreateSock()
{
_sock = socket(AF_INET, SOCK_STREAM, 6);
if (_sock < 0) {
perror("create");
return false;
}
return true;
}
bool Bind(std::string& ip, uint16_t port)
{
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip.c_str());
ssize_t ret = bind(_sock, (struct sockaddr*)&addr, sizeof(addr));
if (ret < 0) {
perror("bind");
return false;
}
return true;
}
bool Listen(int Backlog)
{
ssize_t ret = listen(_sock, Backlog);
if (ret < 0) {
perror("listen");
return false;
}
return true;
}
bool Accept(TcpSvr& ts, struct sockaddr_in* addr = NULL)
{
struct sockaddr_in peeraddr;
socklen_t len = sizeof(struct sockaddr_in);
int ret = accept(_sock, (struct sockaddr*)&peeraddr, &len);
if (ret < 0) {
perror("accept");
return false;
}
ts._sock = ret;
if (addr != NULL){
memcpy(addr, &peeraddr, len);
}
return true;
}
bool Connect(std::string& ip, uint16_t port)
{
struct sockaddr_in destaddr;
destaddr.sin_family = AF_INET;
destaddr.sin_port = htons(port);
destaddr.sin_addr.s_addr = inet_addr(ip.c_str());
int ret = connect(_sock, (struct sockaddr*)&destaddr, sizeof(destaddr));
if (ret < 0) {
perror("connect");
return 0;
}
return true;
}
bool Send(std::string& buf)
{
int sendSize = send(_sock, buf.c_str(), buf.size(), 0);
if (sendSize < 0) {
perror("send");
return false;
}
return true;
}
bool Recv(std::string& buffer)
{
char buf[1024] = {0};
//0:阻塞接收
//MSG_PEEK:探测接收
int recvSize = recv(_sock, buf, sizeof(buf) - 1, 0);
if (recvSize < 0) {
perror("recv");
}
else if (recvSize == 0) {
//如果recvSize等于0,表示对端将连接关闭了
printf("connect error\n");
return false;
}
buffer.assign(buf, recvSize);
return true;
}
void Close()
{
close(_sock);
_sock = -1;
}
private:
int _sock;
};