select函数
select函数用于检测一组socket中是否有时间就绪,主要包含以下三类事件:
- 读事件就绪
- 写事件就绪
- 异常事件就绪
typedef struct{
long int __fds_bits[16];//可以看做128bit的数组
}fd_set;
void FD_SET(int fd, fd_set *set);
将一个fd添加到fd_set集合中,决定这个fd在__fds_bits数组的位置的实现使用的是位图法。
FD_SET宏在本质上是在一个有1024个连续bit(共计64字节)的内存的某个bit上设置一个标志。
如果要在fd_set中删除一个fd,即将对应的bit置0,则可以使用FD_CLR:void FD_CLR(int fd, fd_set *set);
;
如果要将fd_set中所有的fd都清掉,即将所有的bit都置0,则可以使用:void FD_ZERO(fd_set *set);
;
当select函数返回时,可以使用int FD_ISSET(int fd, fd_set *set);
来判断在某个fd中是否有我们关心的事件,本质上就是检测对应的bit是否置位。
使用select函数的服务器代码示例
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <vector>
#define INVALID_FD -1
int main(int argc, const char * argv[]) {
// insert code here...
//std::cout << "Hello, World!\n";
//创建一个监听socket
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(listenfd == INVALID_FD){
std::cout<<"create listen socket error"<<std::endl;
return -1;
}
//初始化服务器地址
struct sockaddr_in bindaddr;
bindaddr.sin_family = AF_INET;
bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);
bindaddr.sin_port = htons(3000);
if(bind(listenfd, (struct sockaddr*) &bindaddr, sizeof(bindaddr)) == -1){
std::cout<<"bind listen socket error"<<std::endl;
return -1;
}
//启动监听
if(listen(listenfd, SOMAXCONN) == -1){
std::cout<<"listen error"<<std::endl;
close(listenfd);
return -1;
}
//记录所有客户端的连接
std::vector<int> clientfds;
int maxfd;
while(true){
fd_set readset;
FD_ZERO(&readset);
//将监听socket加入到可检测的可读事件中
FD_SET(listenfd, &readset);
maxfd = listenfd;
//将客户端fd加入待检测的可读事件中
int clientfdslength = clientfds.size();
std::cout<<"the size of clientfds:"<<clientfdslength<<std::endl;
for(int i = 0; i < clientfdslength; i++){
if(clientfds[i] != INVALID_FD){
FD_SET(clientfds[i], &readset);
if(maxfd < clientfds[i])
maxfd = clientfds[i];
}
}
timeval tm;
tm.tv_sec = 1;
tm.tv_usec = 0;
//暂且只检测可读事件,不检测可写事件和异常事件
int ret = select(maxfd + 1, &readset, NULL, NULL, &tm);
if(ret == -1){
if(errno != EINTR)
break;
}else if(ret == 0){
continue;//select超时,下次继续
}else{
//监测到某个socket事件
if(FD_ISSET(listenfd, &readset)){
//监听socket的可读事件,表明有新的连接到来
struct sockaddr_in clientaddr;
socklen_t clientaddrlen = sizeof(clientaddr);
//接收客户端连接
int clientfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientaddrlen);
if(clientfd == INVALID_FD){
//接收连接出错
break;
}
//只接收连接,不调取recv收取任何数据
std::cout<<"accept a client connection, fd :"<<clientfd<<std::endl;
clientfds.push_back(clientfd);
}else{
//假设对端发来的数据长度不超过63个字符
char recvbuf[64];
int clientfdslength = clientfds.size();
for(int i = 0; i < clientfdslength; ++i){
if(clientfds[i] != INVALID_FD && FD_ISSET(clientfds[i], &readset)){
memset(recvbuf, 0, sizeof(recvbuf));
//非监听socket,接收数据
int length = recv(clientfds[i], recvbuf, 64, 0);
if(length <= 0){
//收取数据出错
std::cout<<"recv data error, clientfd:"<<clientfds[i]<<std::endl;
close(clientfds[i]);
//不直接删除该元素,将该位置元素标记为INVALID_FD
clientfds[i] = INVALID_FD;
continue;
}
std::cout<<"clientfd:"<<clientfds[i]<<", recv data:"<<recvbuf<<std::endl;
}
}
}
}
}
//关闭所有客户端socket
int clientfdslength = clientfds.size();
for(int i = 0; i < clientfdslength; i++){
if(clientfds[i] != INVALID_FD){
close(clientfds[i]);
}
}
/*
struct sockaddr_in clientaddr;
socklen_t clientaddrlen = sizeof(clientaddr);
//接收客户端连接
int clientfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientaddrlen);
if(clientfd != -1){
char recvBuf[32] = {0};
//从客户端接收数据
int ret = recv(clientfd, recvBuf, 32, 0);
if(ret > 0){
std::cout<<"recv data from client, data:"<<std::endl;
//将收到的数据原封不动的发给客户端
ret = send(clientfd, recvBuf, strlen(recvBuf), 0);
if(ret != strlen(recvBuf)){
std::cout<<"send data error"<<std::endl;
}else{
std::cout<<"send data to client success"<<std::endl;
}
}else{
std::cout<<"recv data error."<<std::endl;
}
//close(clientfd);
clientfds.push_back(clientfd);
}
}
*/
//关闭监听socket
close(listenfd);
return 0;
}
上述代码的流程图如下所示: