套接字的默认状态是阻塞的。阻塞的套接字调用可分为以下四类:
1.输入操作:包括read,readv,recv,recvfrom和recvmsg共5个函数。
2.输出操作:包括write,writev,send,sendto和sendmsg共5个函数。
3.接收外来连接,即accept函数。调用accept函数时尚无新的连接到达,调用进程将进入睡眠。
4.发起外出连接,即connect函数。connect函数引起三路握手过程,要一直等到客户收到对于自己的SYN的ACK为止才返回。
非阻塞读和写:str_cli函数
两个缓冲区:to容纳从标准输入到服务器去的数据,fr容纳自服务器到标准输出的数据。
str_cli函数:
#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;
/* 把描述符设置为非阻塞 */
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;
friptr = froptr = fr;
stdineof = 0;
maxfdp1 = max(max(STDIN_FILENO,STDOUT_FILENO),sockfd) + 1;
while (1) {
FD_ZERO(&rset);
FD_ZERO(&wset);
/* 指定所关注的描述符 */
if (stdineof == 0 && toiptr < &to[MAXLINE])
FD_SET(STDIN_FILENO, &rset); //read from stdin
if (friptr < &fd[MAXLINE])
FD_SET(sockfd,&rset); //read from sockfd
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);
/* 从标准输入或者从套接字读入*/
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) {
fprintf(stderr, "EOF on stdin\n");
stdineof = 1;
if (tooptr = toiptr)
shutdown(sockfd, SHUT_WR); //send FIN
} else {
fprintf(stderr, "read %d bytes from stdin\n",n);
toiptr += n; //jusy read
FD_SET(sockfd,&wset); //try to write to socket
}
}
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) {
fprintf(stderr, "EOF on socket\n",);
if (stdineof)
return; //normal termination
else
err_quit("str_cli: server terminated prematurely");
} else {
fprintf(stderr,"read %d bytes from socket",n);
friptr += n;
FD_SET(STDOUT_FILENO,&wset); //try to write to stdout
}
}
/* 写到标准输出或者套接字 */
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 {
fprintf(stderr, "wrote %d bytes to stdout \n",nwritten);
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(socket,tooptr,n)) < 0) {
if (errno != EWOULDBLOCK)
err_sys("write error to socket");
} else {
fprintf(stderr, "wrote %d bytes to socket \n",nwritten);
tooptr += nwritten; //just written
if (tooptr == toiptr) {
toiptr = tooptr = to; //back to beginning of buffer
if (stdineof)
shutdown(sockfd, SHUT_WR); //send FIN
}
}
}
}
}
非阻塞式I/O例子的时间线:
str_cli的较简单版本
str_cli函数的另一个版本,该函数使用fork把当前进程划分成两个进程。子进程把来自服务器的文本行复制到标准输出,父进程把来自标准输入的文本行复制到服务器。
str_cli_fork.h
#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);
kill(getppid(),SIGTERM); //防止父进程一直运行
exit(0);
}
/* parent: stdin -> server */
while (fgets(sendline,MAXLINE,fp) != NULL)
writen(sockfd, sendline, strlen(sendline));
shutdown(sockfd,SHUT_WR); //EOF on stdin, sen FIN
pause();
return;
}
非阻塞connect
当在一个非阻塞的TCP套接字上调用connect时,connect将立即返回一个EINPROGRESS错误,不过已经发起的TCP三路握手继续进行。使用select检测这个连接或成功或失败的已建立条件。非阻塞的connect有三个用途。
1.把三路握手叠加在其它处理上。
2.可以使用这个技术同时建立多个连接。
3.使用select等待连接的建立,可以给select指定一个时间限制,使得我们能够缩短connect的超时。
非阻塞connect:时间获取客户程序
#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 timaval tval;
flags = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd,F_SETFL, flags | O_NONBLOCK); //把套接字设置为非阻塞
error = 0;
if ( (n = connect(sockfd,saptr,salen)) < 0) //发起非阻塞connect,期望错误是EINPROGRESS,表示连接建立已启动但是尚未完成
if (errno != EINPROGRESS)
return -1;
//在connect完成期间可以做其它事
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) { //调用select等待套接字可读或者可写
close(sockfd); //timeout
errno = ETIMEDOUT;
return -1; //超时则关闭套接字,防止三路握手继续下去,并返回TIMEOUT错误给调用者。
}
if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) {
len = sizeof(error);
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
return (-1);
} 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;
}
非阻塞accept
......................