以下知识点来均来自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,若出错则为-1
i、针对最后一个结构参数说明
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 */
}
}
}
}