以下知识点来均来自steven先生所著UNP卷一(version3),刚开始学习网络编程,如有不正确之处请大家多多指正。
1、I/O复用模型:通过调用select或poll函数,阻塞在这两个系统调用中的某一个之上,见下图模型
解释一下,阻塞I/O、非阻塞I/O、同步I/O、异步I/O
阻塞I/O:从调用recvfrom开始到它返回的整段时间内是被阻塞的
非阻塞I/O:调用recvfrom开始,只要无数据准备好,就返回一个EWOULDBLOCK错误。持续轮询内核,以查看某个操作是否就绪
同步I/O:导致请求进程阻塞,直至I/O操作完成
异步I/O:不导致请求进程阻塞
2、select函数
#include<sys/select.h> #include<sys/time.h> int select(int maxfdp1, fd_set* readset, fd_set *writeset, fd_set* exceptset, const struct timeval* timeout); //返回:若有则为跨所有描述符集的就绪描述符数目,若超时则为0,若出错则为-1i、针对最后一个结构参数说明
struct timeval { long tv_sec; long tv_usec; };
当timeout=NULL时,永远等待下去;当指定好时间,等待一段固定时间;当为0时,根本不等待。
调用之前,必要对timeout进行初始化。
ii、中间三个描述符集,数据类型为fd_set,每次调用之前初始化是必要的
void FD_ZERO(fd_set* fdset);//将描述符集清0 void FD_SET(int fd, fd_set * fdset);//在描述符集中开启指定套接字 void FD_CLR(int fd, fd_set* fdset);//在描述符集中关闭指定套接字 int FD_ISSET(int fd, fd_set* fdset);//检测fd_set中的就绪套接字
iii、maxfdp1:待测试的最大描述符加1
调用select函数,指定所关心的描述符的值,函数返回时,结果将指示哪些描述符已就绪。函数返回后,使用FD_ISSET宏来测试fd_set数据类型中的描述符。描述符集内任何与未就绪描述符对应的位返回时均清为0。故每次重新调用select函数时,都得把所有描述符集内所关心的位均置为1。
3、使用select的str_cli函数的实现(此处解决了缓冲区问题)
#include "unp.h" void str_cli(FILE *fp, int sockfd) { int maxfdp1, stdineof; fd_set rset; char buf[MAXLINE]; int n; stdineof = 0; FD_ZERO(&rset); for (; ; ) { //每次循环都要对rset进行初始化 if (stdineof == 0) FD_SET(fileno(fp), &rset);//turn on bit for fileno(fp) FD_SET(sockfd, &rset);//turn on bit for sockfd maxfdp1 = max(fileno(fp), sockfd) + 1; Select(maxfdp1, &rset, NULL, NULL, NULL);//阻塞于select调用或是等待标准输入可读或是等待套接字可读 if (FD_ISSET(sockfd, &rset)) { /* socket is readable */ if ((n = Read(sockfd, buf, MAXLINE)) == 0) { if (stdineof == 1) return; /* normal termination */ else err_quit("str_cli: server terminated prematurely"); } Write(fileno(stdout), buf, n); } if (FD_ISSET(fileno(fp), &rset)) { /* input is readable */ if ((n = Read(fileno(fp), buf, MAXLINE)) == 0) { stdineof = 1; Shutdown(sockfd, SHUT_WR); /* send FIN *///SHUT_WR:关闭写,但还能读,SHUT_RD:关闭读,还能写,SHUT_RDWR:关闭读,再关闭写 FD_CLR(fileno(fp), &rset);//复位 continue; } Writen(sockfd, buf, n); } } }
4、shutdown函数
#include<sys/socket.h> int shutdown(int sockfd, int howto);//返回:若成功则为0,若出错则为-1
shutdown函数避免了close函数的两个限制,其一,close把描述符的引用计数减1,仅在该计数变为0时才关闭套接字,而shutdown可以不管引用计数就激发tcp的正常连接终止序列;其二,close终止读和写两个方向的数据发送。tcp连接是全双工,有时需要单方向终止连接。
针对传递的给形参howto,可以有的实参为
SHUT_RD:关闭读连接——套接字中不再有数据可接收,而且套接字接收缓冲区中的现有数据都被丢弃;
SHUT_WR:关闭写连接(半关闭)——当前留在套接字发送缓冲区中的数据将被发送掉,后跟tcp的正常连接终止序列;
SHUT_RDWR:关闭读写连接,等效调用shutdown两次,第一次调用指定SHUT_RD,第二次调用SHUT_WR。
5、poll函数
#include<poll.h> int poll(struct pollfd* fdarray, unsigned long nfds, int timeout); //返回:若有就绪描述符则为其数目,若超时则为0.若出错则为-1 struct pollfd { int fd;//用来检测的描述符 short events;//有关描述符感兴趣事件 short revents;//发生在描述符上的感兴趣事件 };poll第一个参数特点:测试的条件由events成员指定,函数在相应的revents成员中返回该描述符的状态。
/* include fig01 */ #include "unp.h" #include <limits.h> /* for OPEN_MAX */ int main(int argc, char **argv) { int i, maxi, listenfd, connfd, sockfd; int nready; ssize_t n; char buf[MAXLINE]; socklen_t clilen; struct pollfd client[OPEN_MAX]; struct sockaddr_in cliaddr, servaddr; listenfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); Bind(listenfd, (SA *) &servaddr, sizeof(servaddr)); Listen(listenfd, LISTENQ); client[0].fd = listenfd;//client[0]固定用于监听套接字 client[0].events = POLLRDNORM;//普通数据可读 for (i = 1; i < OPEN_MAX; i++) client[i].fd = -1; /* -1 indicates available entry */ maxi = 0; /* max index into client[] array */ for ( ; ; ) { nready = Poll(client, maxi+1, INFTIM);//nfds=maxi+1 if (client[0].revents & POLLRDNORM) { /* new client connection */ clilen = sizeof(cliaddr); connfd = Accept(listenfd, (SA *) &cliaddr, &clilen); #ifdef NOTDEF printf("new client: %s\n", Sock_ntop((SA *) &cliaddr, clilen)); #endif for (i = 1; i < OPEN_MAX; i++) if (client[i].fd < 0) {//寻找第一个描述符成员为-1值的位置(可用项) client[i].fd = connfd; /* save descriptor */ break; } if (i == OPEN_MAX) err_quit("too many clients"); client[i].events = POLLRDNORM;//设置为普通数据可读 if (i > maxi) maxi = i; /* max index in client[] array */ if (--nready <= 0) continue; /* no more readable descriptors */ } for (i = 1; i <= maxi; i++) { /* check all clients for data */ if ( (sockfd = client[i].fd) < 0)//首先检测到相应客户就绪描述符 continue; if (client[i].revents & (POLLRDNORM | POLLERR)) { if ( (n = read(sockfd, buf, MAXLINE)) < 0) {//读入 if (errno == ECONNRESET) {//应对客户发送RST情况 /*4connection reset by client */ #ifdef NOTDEF printf("client[%d] aborted connection\n", i); #endif Close(sockfd); client[i].fd = -1; } else err_sys("read error"); } else if (n == 0) {//遇见EOF输入 /*4connection closed by client */ #ifdef NOTDEF printf("client[%d] closed connection\n", i); #endif Close(sockfd); client[i].fd = -1; } else Writen(sockfd, buf, n);//写出 if (--nready <= 0) break; /* no more readable descriptors */ } } } }