阻塞套接字意味着当发出一个不能立即完成的套接字调用时,其进程将投入睡眠,等待相应操作完成。
非阻塞套接字,如果输入操作不能被满足(对于tcp套接字即至少有一个字节的数据可读,对于udp套接字即有一个完整的数据报可读),相应调用将立即返回一个EWOULDBLOCK错误。
注意:selectc通常结合非阻塞式I/O一起使用
1、非阻塞读和写:str_cli函数的开发
如果套接字发送缓冲区已满,writen调用将会阻塞。在进程阻塞于writen调用期间,可能有来自套接字接收缓冲区的数据区的数据可供读取(偏偏由于进程被阻塞而读不了)。同理,如果从套接字中有一行输入文本可读,那么照样可能阻塞于后续的write调用。这个时候就显示出非阻塞读和写的好处,但非阻塞式I/O的加入str_cli函数的缓冲区管理显著地复杂化。于是可简化版本就是把应用程序任务划分到多个进程(使用fork)或多个线程。
首先介绍一下容纳从标准输入到套接字的数据的buff和从套接字到标准输入出的buff,如下所示:
相应代码此处贴出(实在是有点长,增加了复杂性)
/* include nonb1 */
#include "unp.h"
void
str_cli(FILE *fp, int sockfd)
{
int maxfdp1, val, stdineof;
ssize_t n, nwritten;
fd_set rset, wset;
char to[MAXLINE], fr[MAXLINE];
char *toiptr, *tooptr, *friptr, *froptr;
//以下是利用fcntl函数设置套接字为非阻塞的惯用手法
val = Fcntl(sockfd, F_GETFL, 0);
Fcntl(sockfd, F_SETFL, val | O_NONBLOCK);
val = Fcntl(STDIN_FILENO, F_GETFL, 0);
Fcntl(STDIN_FILENO, F_SETFL, val | O_NONBLOCK);
val = Fcntl(STDOUT_FILENO, F_GETFL, 0);
Fcntl(STDOUT_FILENO, F_SETFL, val | O_NONBLOCK);
toiptr = tooptr = to; /* initialize buffer pointers */
friptr = froptr = fr;
stdineof = 0;
maxfdp1 = max(max(STDIN_FILENO, STDOUT_FILENO), sockfd) + 1;
for (; ; ) {
FD_ZERO(&rset);
FD_ZERO(&wset);
if (stdineof == 0 && toiptr < &to[MAXLINE])
FD_SET(STDIN_FILENO, &rset); /* read from stdin */
if (friptr < &fr[MAXLINE])
FD_SET(sockfd, &rset); /* read from socket */
if (tooptr != toiptr)
FD_SET(sockfd, &wset); /* data to write to socket */
if (froptr != friptr)
FD_SET(STDOUT_FILENO, &wset); /* data to write to stdout */
Select(maxfdp1, &rset, &wset, NULL, NULL);
/* end nonb1 */
/* include nonb2 */
if (FD_ISSET(STDIN_FILENO, &rset)) {//检测是否有标准输入
if ((n = read(STDIN_FILENO, toiptr, &to[MAXLINE] - toiptr)) < 0) {
if (errno != EWOULDBLOCK)
err_sys("read error on stdin");
}
else if (n == 0) {//遇见EOF结束符
#ifdef VOL2
fprintf(stderr, "%s: EOF on stdin\n", gf_time());
#endif
stdineof = 1; /* all done with stdin */
if (tooptr == toiptr)//若为真,表明不再有东西发出
Shutdown(sockfd, SHUT_WR);/* send FIN */
}
else {
#ifdef VOL2
fprintf(stderr, "%s: read %d bytes from stdin\n", gf_time(), n);
#endif
toiptr += n; /* # just read */
FD_SET(sockfd, &wset); /* try and write to socket below */
}
}
if (FD_ISSET(sockfd, &rset)) {
if ((n = read(sockfd, friptr, &fr[MAXLINE] - friptr)) < 0) {
if (errno != EWOULDBLOCK)
err_sys("read error on socket");
}
else if (n == 0) {
#ifdef VOL2
fprintf(stderr, "%s: EOF on socket\n", gf_time());
#endif
if (stdineof)
return; /* normal termination */
else
err_quit("str_cli: server terminated prematurely");
}
else {
#ifdef VOL2
fprintf(stderr, "%s: read %d bytes from socket\n",
gf_time(), n);
#endif
friptr += n; /* # just read */
FD_SET(STDOUT_FILENO, &wset); /* try and write below */
}
}
/* end nonb2 */
/* include nonb3 */
if (FD_ISSET(STDOUT_FILENO, &wset) && ((n = friptr - froptr) > 0)) {
if ((nwritten = write(STDOUT_FILENO, froptr, n)) < 0) {
if (errno != EWOULDBLOCK)
err_sys("write error to stdout");
}
else {
#ifdef VOL2
fprintf(stderr, "%s: wrote %d bytes to stdout\n",
gf_time(), nwritten);
#endif
froptr += nwritten; /* # just written */
if (froptr == friptr)
froptr = friptr = fr; /* back to beginning of buffer */
}
}
if (FD_ISSET(sockfd, &wset) && ((n = toiptr - tooptr) > 0)) {
if ((nwritten = write(sockfd, tooptr, n)) < 0) {
if (errno != EWOULDBLOCK)
err_sys("write error to socket");
}
else {
#ifdef VOL2
fprintf(stderr, "%s: wrote %d bytes to socket\n",
gf_time(), nwritten);
#endif
tooptr += nwritten; /* # just written */
if (tooptr == toiptr) {
toiptr = tooptr = to; /* back to beginning of buffer */
if (stdineof)
Shutdown(sockfd, SHUT_WR); /* send FIN */
}
}
}
}
}
/* end nonb3 */
str_cli的较简单版本(把应用程序任务划分到多个进程(使用fork))
#include "unp.h"
void
str_cli(FILE *fp, int sockfd)
{
pid_t pid;
char sendline[MAXLINE], recvline[MAXLINE];
if ((pid = Fork()) == 0) { /* child: server -> stdout */
while (Readline(sockfd, recvline, MAXLINE) > 0)
Fputs(recvline, stdout);
/*当子进程读到EOF时,表明服务器进程终止,必须终止父进程从stdin到sockfd复制data*/
kill(getppid(), SIGTERM); /* in case parent still running */
exit(0);
}
/* parent: stdin -> server */
while (Fgets(sendline, MAXLINE, fp) != NULL)
Writen(sockfd, sendline, strlen(sendline));
//此处shutdown关闭写半部分,因为子进程还可能存在读,但不能用close(还出于存在引用计数问题考虑)
Shutdown(sockfd, SHUT_WR); /* EOF on stdin, send FIN */
pause();
return;
}
2、非阻塞connect
当在一个非阻塞的tcp套接字上调用connect时,connect将立即返回一个EINPROGESS错误,另如果已将发起的tcp三路握手继续进行。于是关天select和非阻塞connect的两个规则i、当连接成功建立时,描述符变为可写;ii、当连接建立遇到错误时,描述符变为既可读又可写。
#include "unp.h"
int
connect_nonb(int sockfd, const SA *saptr, socklen_t salen, int nsec)
{
int flags, n, error;
socklen_t len;
fd_set rset, wset;
struct timeval tval;
flags = Fcntl(sockfd, F_GETFL, 0);
Fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
error = 0;
if ((n = connect(sockfd, saptr, salen)) < 0)//期待错误EINPROGRESS,表示建立已连接但未完成
if (errno != EINPROGRESS) //此是non-block socket connect的特点
return(-1);
/* Do whatever we want while the connect is taking place. */
if (n == 0)
goto done; /* connect completed immediately */
FD_ZERO(&rset);
FD_SET(sockfd, &rset);
wset = rset;
tval.tv_sec = nsec;
tval.tv_usec = 0;
if ((n = Select(sockfd + 1, &rset, &wset, NULL,
nsec ? &tval : NULL)) == 0) {
close(sockfd); /* timeout */
errno = ETIMEDOUT;
return(-1);
}
if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) {//如果套接字变为可读或可写时,调用getsockopt取得套接字的待处理错误
len = sizeof(error);
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)//error值为0表示禁止option,非0表示启用option
return(-1); /* Solaris pending error */
}
else
err_quit("select error: sockfd not set");
done:
Fcntl(sockfd, F_SETFL, flags); /* restore file status flags */
if (error) {
close(sockfd); /* just in case */
errno = error;
return(-1);
}
return(0);
}
3、非阻塞accept
很矛盾哈,这是因为select告诉我们该套接字上已有连接就绪,那么随后的accept调用不应该阻塞,因为此时阻塞完全没必要。但有时有特定的条件时,还是需要非阻塞accept的,由于情况比较特殊,此处就不,贴代码了,详情见书上P363页。
以上知识点来均来自steven先生所著UNP卷一(version3),刚开始学习网络编程,如有不正确之处请大家多多指正。