IO复用使得程序能够同时监听多个文件描述符,可以大大提高程序的性能。
select 系统调用
select系统调用的用途是:在一段指定的时间内,监听用户感兴趣的文件描述符上的可读、可写和异常事件。
select API
#include <sys/select.h>
int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds,struct timeval* timeout);
/* * 成功时返回就绪文件描述符总数 * 如果没有任何文件描述符就绪,select返回0 * 失败返回-1 */
nfds
参数指定被监听的文件描述符的总数,一般设置为select监听的所有文件描述符最大值+1,因为文件描述符从0开始。readfds、writefds、exceptfds
参数分别指向可读、可写和异常等事件对应的文件描述符集合。
应用程序调用select时,通过这三个参数传入自己感兴趣的文件描述符,select调用返回时,内核将修改它们来通知应用程序哪些文件描述符 已就绪。
因为内核会对其进行修改,所以就需要进行保存。
这三个参数对应的fd_set其实就是一个整形数组,该数组的每个元素的每一位标记一个文件描述符。
-timeout
设置select的超时时间。
修改fd_set结构体
#include <sys/select.h>
FD_ZERO(fd_set* fdset); //清除fdset所有位
FD_SET(int fd, fd_set* fdset); //设置fdset的位fd
FD_CLR(int fd, fd_set* fdset); //清除fdset的位fd
int FD_ISSET(int fd, fd_ste* fdset); //测试fdset的位fd是否被设置
文件描述符就绪条件
select编程实例
select编程流程
/* * 1.创建socket-->bind-->listen * 2.创建一个链表或数组存储socketfd 并清空select的标志数组 * 3.先将servicefd放入链表 * 4.循环:① 先清空select标志数组 * ② 获取链表中最大文件描述符值,并将链表中存储的sockfd一一设置在select中的数组 * ③ 进入第五步:处理数组 * 5.处理数组:① 如果是发生事件的标记为 == servicefd ,则是有新的连接将连接放入链表 * ② 如果不是servicefd,则代表是客户端收发数据,如果收到断开请求,则从链表中删除socketfd并关闭该socket */
头文件 tcp_socket.h
#include<iostream>
#include<assert.h>
#include<string.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<unistd.h>
#define BUFF_SIZE 128
using namespace std;
class Socket
{
public:
Socket()
{
sockfd_ = socket(PF_INET, SOCK_STREAM, 0);
assert(sockfd_ >= 0);
}
int Get_socket()
{
return sockfd_;
}
~Socket()
{
close(sockfd_);
}
protected:
int sockfd_;
};
class Socket_Ser :public Socket
{
public:
Socket_Ser(const char* ip, int port = 6000, int backlog = 5)
{
struct sockaddr_in address;
memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_port = htons(port);
address.sin_addr.s_addr = inet_addr(ip);
int ret = bind(sockfd_, (struct sockaddr*)&address, sizeof(address));
assert(ret != -1);
ret = listen(sockfd_, backlog);
assert(ret != -1);
}
int Accept()
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
int connfd = accept(sockfd_, (struct sockaddr*)&client, &len);
return connfd;
}
int Recv(int fd, char* buffer, int size)
{
int ret = recv(fd, buffer, size - 1, 0);
if (ret == -1 || (strncmp(buffer, "end", 3) == 0))
{
return -1;
}
}
void Send(int fd, const char* buffer, int size)
{
send(fd, buffer, size, 0);
}
};
class Socket_Cli :public Socket
{
public:
Socket_Cli(const char* ip, int port = 6000)
{
struct sockaddr_in address;
memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_port = htons(port);
address.sin_addr.s_addr = inet_addr(ip);
int ret = connect(sockfd_, (struct sockaddr*)&address, sizeof(address));
assert(ret != -1);
}
int Send(char* buffer, int size)
{
send(sockfd_, buffer, size, 0);
}
};
头文件tcp_select.h
#include "tcp_socket.h"
#include <list>
class Select
{
public:
Select(const char* ip, int port = 6000, int backlog = 5) :ser(ip, port, backlog)
{
cli_list.push_front(ser.Get_socket());
FD_ZERO(&readfds_);
FD_ZERO(&exceptfds_);
FD_ZERO(&writefds_);
}
void Deal()
{
Using_Select();
Deal_connect();
}
void Delete_fd(int fd)
{
cli_list.remove(fd);
close(fd);
}
public:
void Join_Read(int connfd)
{
FD_SET(connfd, &readfds_);
}
void Join_Write(int connfd)
{
FD_SET(connfd, &writefds_);
}
void Join_Except(int connfd)
{
FD_SET(connfd, &exceptfds_);
}
public:
int Judge_Read(int connfd)
{
return FD_ISSET(connfd, &readfds_);
}
int Judge_Write(int connfd)
{
return FD_ISSET(connfd, &writefds_);
}
int Judge_Except(int connfd)
{
return FD_ISSET(connfd, &exceptfds_);
}
public:
void Zero()
{
FD_ZERO(&readfds_);
FD_ZERO(&exceptfds_);
FD_ZERO(&writefds_);
}
private:
int Accept()
{
int connfd = ser.Accept();
cli_list.push_front(connfd);
return connfd;
}
void Using_Select()
{
int serfd = Ret_Max();
int ret = select(serfd + 1, &readfds_, NULL, NULL, NULL);
if (ret < 0)
{
cout << "selcet failure" << endl;
}
}
int Ret_Max()
{
Zero();
auto it = cli_list.begin();
int max = *it;
for (; it != cli_list.end(); it++) //获取最大值,并重置select数组
{
Join_Read(*it);
if (*it > max)
{
max = *it;
}
}
return max;
}
void Deal_connect()
{
int sockfd = ser.Get_socket();
for (auto it = cli_list.begin(); it != cli_list.end(); )
{
if (Judge_Read(*it)) //当发生读事件
{
if (*it == sockfd) //服务器接受新连接
{
int connfd = Accept();
cout << "new client:" << connfd << " connet!" << endl;
}
else //客户端发送消息
{
char buffer[BUFF_SIZE] = {
0 };
int ret = ser.Recv(*it, buffer, BUFF_SIZE);
if (ret == -1)
{
int fd = *it++; //删除操作注意!
Delete_fd(fd);
continue;
}
cout << "recv form " << *it << ":" << buffer << endl;
ser.Send(*it, "ok", 2);
}
}
++it;
}
}
private:
Socket_Ser ser;
list<int> cli_list; //存储套接字
fd_set readfds_;
fd_set writefds_;
fd_set exceptfds_;
};
主文件tcp_select.cpp
#include "tcp_select.h"
#include <list>
int main(int argc, char* argv[])
{
if (argc <= 1)
{
cout << "error! please input again" << endl;
return 1;
}
Select sel(argv[1]);
while (1) //不断的去在select上注册事件
{
sel.Deal();
}
return 0;
}
参考文献
[1]游双.Linux高性能服务器编程.机械工业出版社,2043.5.