close终止了数据传送的两个方向,而shutdown可以有选择的终止某个方向的数据传送或者终止数据传送的两个方向,例如shutdown how=1就可以保证对等方接收到一个EOF字符,而不管其他进程是否已经打开了套接字。而close不能保证,知道套接字引用计数减到0时才发送,也就是说直到所有的进程都关闭了套接字。
做实验,快速的客户端发送两串数据,如”AAA”和“BBB”,按下CTRL+D后会发现客户端出现“Bad file descriptor”,并且导致了服务器的崩溃。这是为什么呢?由于服务器回射有十秒钟的延迟(在服务器端设置十秒钟延迟),当输入CTRL+D后,客户端检测到输入空值,会立即close(sockfd),然后马上重新去监听,select函数此时监听不到信息,返回-1,,出现错误。服务器端虽然已经收到了两行数据,已经将其输出,过十秒钟调用write函数,由于对方已经关闭,TCP协议段响应一个RST段回来,即连接重置段。而且,因为向客户端发送数据时,由于对方已经关闭,此时会产生一个SIGPIPE信号,会导致服务器的终止,因此服务器端必须捕捉SIGPIPE信号,所以需要改进。采用shutdown来关闭客户端的写(意味着仍然可以读),但是此时管道仍然是全双工的,对方仍然能够接收到数据,采用shutdown后,客户端仍然回到select,服务器在检测到输入为零时,会执行如下的代码,关闭这个套接字,然后客户端监听到服务器的关闭后,会关闭本地的连接,因此为了安全起见,客户端一般需要采用shutdown而不是close来关闭
if(ret == 0)
{
printf("client close\n");
FD_CLR(connfd, &allset);//套接字已经关闭,从集合中清除
client[i]=-1;//设置为空闲
close(connfd);
}
if(pid==0)
{
close(listenfd);
…
close(connfd);/这时才会向对方发送FIN段(因为此时connfd引用计数减为0)/
}
else if(pid>0)
{
/*shutdown(connfd,SHUT_WR);//不理会引用计数,直接向对方发送FIN段
close(connfd);//并不会向客户端发送FIN段,仅仅是将套接字引用计数减一
}
void echo_cli1(int sockfd)
{
fd_set rset;
FD_ZERO(&rset);
int nready;
int maxfd;
int fd_stdin = fileno(stdin);
if (fd_stdin > sockfd)maxfd = fd_stdin;
else maxfd = sockfd;
char sendbuf[1024];
char recvbuf[1024];
int stdeof=0;
while (1)
{
if(!stdeof)FD_SET(fd_stdin, &rset);
FD_SET(sockfd, &rset);
nready = select(maxfd + 1, &rset, NULL, NULL, NULL);
if (nready == -1)ERR_EXIT("select");
if (nready == 0)continue;
if (FD_ISSET(sockfd, &rset))
{
int ret = readline(sockfd, recvbuf, sizeof(recvbuf));
if (ret == -1)ERR_EXIT("read");
if (ret == 0)
{
printf("server close\n");
break;
}
fputs(recvbuf, stdout);
}
if (FD_ISSET(fd_stdin, &rset))
{
if (fgets(sendbuf, sizeof(sendbuf), stdin) == NULL)
{
/*close(sockfd);
sleep(5);
exit(EXIT_FAILURE);
*/
stdeof=1;
shutdown(sockfd,SHUT_WR);
}
else
{
writen(sockfd, sendbuf, strlen(sendbuf));
}
}
memset(sendbuf, 0, sizeof(sendbuf));
memset(recvbuf, 0, sizeof(recvbuf));
}
//close(sockfd);
}