一.select函数详解
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *excptset, const strcut timeval *timeout)
返回:准有就绪的文件描述数量,若超时则返回0,否者返回-1
针对select中最后一个时间参数情况如下
struct timeval{
long tv_sec; //seconds
long tv_usec; //microseconds
};
1.timeout = NULL
表示将会一直等待下去,并且堵塞。也有可能系统捕获到了异常信号,强制返回-1.
2.time->tv_sec != 0 && timeout->tv_usec != 0
timeout作为计时器,进行倒计时,如果超时了那么,select函数就会返回0。也有可能系统捕获到了异常信号,强制返回-1.
3.time->tv_sec == 0 && timeout->tv_usec == 0
将会以轮训的方式等待下,还没有准备就绪的描述符。
针对select返回值详细分析
1.负值
系统捕捉到信号,进入相应的信号处理函数。
2.0
等待超时
3.正值
表示有多少个描述符号准备就绪
fd_set的宏操作函数
FD_ZERO(fd_set *fdset) //clear all bit in fdset
FD_SET(int fd, fd_set *fdset) //turn on the bit for fd on fdset
FD_ISSET(int fd, fd_set *fdset) // is on the bit for fd on fdset ?
FD_CLR(int fd, fd_set *fd_set) //turn offthe bit for fd on fdset
在sys/select函数中规定,FD_SETSIZE的值表示描述符总数,一般值默认为1024。但是在不同64位中的值可能不同。
二.select函数的应用
针对客服端一方,我们设计了一个select在批量输入中的一个模型。但是该明显存在一个大量的陷阱,如下分析所示
#include "unp"
void doClient_echo(FILE *fp, int sockfd)
{
fd_set rset;
int maxfdp;
FD_ZERO(&rset);
char recvbuf[MAXLINE], sendbuf[MAXLINE];
for(;;){
FD_SET(fileno(fp), &rset);
FD_SET(sockfd, &rset);
maxfdp = fileno(fp)+1;
Select(maxfdp, &rset, NULL, NULL, NULL);
if(FD_ISSET(fileno(fp), &rset)){
if(Fgets(fp, sendbuf, MAXLINE) != EOF)
return;
Write(sockfd, sendbuf, strlen(sendbuf));
}
if(FD_ISSET(sockfd, &rset)){
int n;
if((n = Readline(sockfd, recvbuf, MAXLINE)) == 0)
std::cout << "do_client: server terminaled\n";
Fputs(recvbuf, stdout);
}
}
}
在cilent的发送速度和server相同的情况下,当在client通过循环发送8个请求给server后,当打算发送第九个请求的时候Fgets函数读取到了EOF,
这个时候返回空指针,这个时候函数返回。但是client的接受端并没有完全接受到来之server处理完的信息函数就返回了,这样就会造成数据的丢失。
因为虽然我们完成了从套接字的读入,但是请求可能还在前往去服务器的路上,或者是请求对应的处理消息还在从服务器到客服端的路上。
针对上诉的描述问题我们给出了如下的解决方案。
#include "unp"
#define MAXLINE 4096
void doClient_echo(FILE *fp, int sockfd)
{
int maxfdp;
fd_set rset;
FD_ZERO(&rset);
int stdineof = 0;
char sendbuf[MAXLINE],recvbuf[MAXLINE];
for(;;){
if(stdineof == 0)
FD_SET(fileno(fp), &rset);
FD_SET(sockfd, &rset);
maxfdp = fileno(fp) + 1;
Select(maxfdp, &rset, NULL, NULL, NULL);
if(FD_ISSET(sockfd, &rset)){
int n;
if((n = Readline(sockfd, recvbuf, MAXLINE)) == 0){
if(stdineof == 1)
return;
else{
std::cout << "Server terminated prematurely\n";
}
}
Write(STDOUT_FILENO, recvbuf, n);
}
if(stdineof == 0 && FD_ISSET(fileno(fp), &rset)){
int n;
if((n = Readline(fileno(fp), sendbuf, MAXLINE)) == EOF){
stdineof = 1;
Shutdown(sockfd, SHUT_WR);
FD_CLR(fileno(fp), &rset);
continue;
}
Write(sockfd, sendbuf, n);
}
}
}
根据要求只有当请求发送完毕后,然后client接收了处理的全部要求,然后才能返回函数。但是根据TCP|IP四次握手的原则,所有的请求发送完毕后,通过调用shutdown函数半关闭套接字写入端口,最后一个信息就是有cilent发送一个FIN单字节,促使server被动关闭套接字,进入tcp|ip四次挥手过程。(该过程不在详述,请参考待定)
1.stdineof = 0,表示fp文件指针还可以继续读取,请求。当fp文件读取完成后,Readline函数返回EOF表示请求读取完毕,这个时候通过调用shutdown函数半关闭套接字写入端口,然后将该文件fp对应的文件描述符号,从rset描述符集合中除去。
2.目前等待一直从套接字描述符号中读取对应的请求的处理,并且答应出来,当读写出来的数据为“处理n”的时候表示是对“请求n”的处理结果,在sockfd读取结果为0的时候,表示收到的连接为ACK,如果stdineof = 1,那么该ACK很有可能就是由自己发送的FIN结束请求的应答,以此来表示结束函数。