select
函数原型
#include<sys/select.h>
select(int nfds, fd_set *readfds, fd_set * writefds, fd_set *exceptfds, struct timeval *timeout);
- nfds代表最大文件描述符值+1
- readfds、writefds、exceptfds分别代表可读、可写、异常事件集合
- timeout代表等待时间
timeout参数:
- NULL:select阻塞等待
- 0:仅检测文件描述符状态,然后立即返回
- 特定值,如果在特定时间内没有事件发生,就会超时返回
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);
struct timeval:
struct timeval
{
time_t tv_sec; //秒
suseconds_t tv_usec; //毫秒
};
函数返回值
- 执行成功,返回发生事件的文件描述符个数
- 如果返回0代表在描述词状态改变前已超过timeout时间,没有返回
- 发生错误时返回-1
错误值可能为:
- EBADF 文件描述词为无效的或该文件已关闭
- EINTR 此调用被信号所中断
- EINVAL 参数n 为负值
- ENOMEM 核心内存不足
select特点
- 可监控的文件描述符个数取决与sizeof(fd_set)的值
- 将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd
- 一是用于再select 返回后,array作为源数据和fd_set进行FD_ISSET判断
- 二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数
select缺点
- 每次调用select, 都需要手动设置fd集合, 从接口使用角度来说也非常不便
- 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
- 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
- select支持的文件描述符数量太小
使用select实现TCP服务器
封装TCP类
#pragma once
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <iostream>
#include <string>
//流程:
//创建套接字
//绑定地址信息
//客户端---连接接口
//监听
//获取新连接
//发送数据
//接收数据
//关闭套接字
class TcpSrv
{
public:
TcpSrv()
{
sockfd_ = -1;
}
~TcpSrv()
{
}
bool CreateSock()
{
sockfd_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sockfd_ < 0)
{
perror("socket");
return false;
}
return true;
}
bool Bind(const 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 <