首先看一个例子,以下图所示:服务器
当咱们客户端发送ABCD再close套接字的时候,服务器端的接收通道也被关闭了,将没法接收ABCD的数据。若是想要仅仅关闭发送通道,保留接收通道,能够使用shutdown。socket
1、close与shutdown 的区别:
一、close终止了数据传送的两个方向
二、shutdown 能够有选择的终止某个方向的数据传送或者数据传送的两个方向、函数
2、shutdown 若是howto=1(SHUT_WR),就能够保证对等方接收到一个EOF字符,而无论其余进程是否已经打开了套接字(无论引用计数是否为1都激发TCP的正常终止链接)。而close不能保证(详见下面三中的例子,conn引用计数减为0才关闭),直到套接字引用计数减
位0时才发送。也就是说直到全部的进程都关闭了套接字。spa
3、
int shutdown(int sockfd,int howto)
howto=SHUT_RD (0)关闭链接的读的一半,再也不接收数据
howto=SHUT_WR (1)关闭链接的写的一半,
howto=SHUT_RDWR(2)
例子:
int conn;
pid_t pid=fork();
if(pid==-1)
ERR_EXIT("fork");
if(pid==0) //子进程
{
close(sock);指针
.... //通讯
close(conn);//子进程使用完conn,close conn 引用计数减为0,这时才会向双方发送FIN段。
}else if(pid>0) //父进程
{
close(conn);//父进程不会向客户端发送FIN.要考虑到引用计数。close(conn) 父进程用不到conn,将conn引用计数减一 。 shutdown(conn,SHUT_WR) 的话不理会引用计数,直接向对方发送FIN段
}code
下面程序对比说明close与shutdown的区别:blog
客户端程序:接口
/* 1、close与shutdown 的区别: 一、close终止了数据传送的两个方向 二、shutdown 能够有选择的终止某个方向的数据传送或者数据传送的两个方向、 2、shutdown 若是howto=1,就能够保证对等方接收到一个EOF字符,而无论其余进程是否已经打开了 套接字(无论引用计数是否为1都激发TCP的正常终止链接)。而close不能保证,直到套接字引用计数减 位0时才发送。也就是说直到全部的进程都关闭了套接字。 3、 int shutdown(int sockfd,int howto) howto=SHUT_RD 关闭链接的读的一半,再也不接收数据 howto=SHUT_WR 关闭链接的写的一半, howto=SHUT_RDWR 例子: int conn; pid_t pid=fork(); if(pid==-1) ERR_EXIT("fork"); if(pid==0) { close(sock); close(conn);//这时才会向双方发送FIN段。 }else if(pid>0) { close(conn);//不会向客户端发送FIN } */ #include<unistd.h> #include<sys/types.h> #include<sys/socket.h> #include<string.h> #include<stdlib.h> #include<stdio.h> #include<errno.h> #include<netinet/in.h> #include<arpa/inet.h> #include<signal.h> #include <sys/time.h>
#define ERR_EXIT(m)\
do\ {\ perror(m);\ exit(EXIT_FAILURE);\ }while(0) ssize_t readn(int fd,void *buf,size_t count) { size_t nleft=count; ssize_t nread; char *bufp=(char*)buf; while(nleft>0) { if((nread=read(fd,bufp,nleft))<0) { if(errno==EINTR) continue; else
return -1; } else if(nread==0) return (count-nleft); bufp+=nread; nleft-=nread; } return count; } ssize_t writen(int fd, const void *buf, size_t count) { size_t nleft=count; ssize_t nwritten; char *bufp=(char*)buf; while(nleft>0) { if((nwritten=write(fd,bufp,nleft))<=0) { if(errno==EINTR) continue; return -1; }else if(nwritten==0) continue; bufp+=nwritten; nleft-=nwritten; } return count; } ssize_t recv_peek(int sockfd,void *buf,size_t len) { while(1) { int ret=recv(sockfd,buf,len,MSG_PEEK);//从sockfd读取内容到buf,但不去清空sockfd,偷窥
if(ret==-1&&errno==EINTR) continue; return ret; } } //偷窥方案实现readline避免一次读取一个字符
ssize_t readline(int sockfd,void * buf,size_t maxline) { int ret; int nread; size_t nleft=maxline; char *bufp=(char*)buf; while(1) { ret=recv_peek(sockfd,bufp,nleft);//不清除sockfd,只是窥看
if(ret<0) return ret; else if(ret==0) return ret; nread=ret; int i; for(i=0;i<nread;i++) { if(bufp[i]=='\n') { ret=readn(sockfd,bufp,i+1);//读出sockfd中的一行而且清空
if(ret!=i+1) exit(EXIT_FAILURE); return ret; } } if(nread>nleft) exit(EXIT_FAILURE); nleft-=nread; ret=readn(sockfd,bufp,nread); if(ret!=nread) exit(EXIT_FAILURE); bufp+=nread;//移动指针继续窥看
} return -1; } void echo_cli(int sock) { /* char sendbuf[1024]={0}; char recvbuf[1024]={0}; while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL)//默认有换行符 { writen(sock,sendbuf,strlen(sendbuf)); int ret=readline(sock,recvbuf,1024); if(ret==-1) ERR_EXIT("readline"); else if(ret==0) { printf("service closed\n"); break; } fputs(recvbuf,stdout); memset(sendbuf,0,sizeof(sendbuf)); memset(recvbuf,0,sizeof(recvbuf)); } */
char sendbuf[1024]={0}; char recvbuf[1024]={0}; fd_set rset; FD_ZERO(&rset);//初始化
int nready;//准备好的个数
int maxfd; int fd=fileno(stdin);//防止STDIN_FILLENO被重定向
if(fd>sock) maxfd=fd; else maxfd=sock; int stdineof=0;//标准输入是否被终止了(Ctrl+D)
while(1) { if(stdineof==0) FD_SET(fd,&rset);//循环中,shutdown以后继续循环的时候,stdin就已经要被清除不能再放在select监听
FD_SET(sock,&rset); nready=select(maxfd+1,&rset,NULL,NULL,NULL); if(nready==-1) ERR_EXIT("select error"); if(nready==0) continue; if(FD_ISSET(sock,&rset)) { int ret=readline(sock,recvbuf,sizeof(recvbuf)); if(ret==-1) ERR_EXIT("readline error"); else if(ret==0) { ERR_EXIT("serve closed"); break; } fputs(recvbuf,stdout); memset(recvbuf,0,sizeof(recvbuf)); } if(FD_ISSET(fd,&rset)) { if(fgets(sendbuf,sizeof(sendbuf),stdin)==NULL)//输入两行再按下ctrl+D后,服务器收到消息4秒以后才能回射 //但套接字被close了
{ /* close(sock);//实验,一旦收到EOF,关闭套接字。既不能接收也不能发送。并且服务器端也会崩溃??? (服务器端有无SIGPIPE信号处理的话) sleep(5); exit(EXIT_FAILURE); */ shutdown(sock,SHUT_WR);//shutdown关闭能够产生回射,能够继续读数据和对面的关闭通知
stdineof=1; } else { writen(sock,sendbuf,strlen(sendbuf)); memset(sendbuf,0,sizeof(sendbuf)); } } } } void handle_sigpipe(int sig) { printf("recive a signal=%d\n",sig); } int main(void) { signal(SIGPIPE,handle_sigpipe);//捕捉第二次write的SIGPIPE信号,默认终止进程
int sock; if((sock=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0) ERR_EXIT("socket error"); struct sockaddr_in servaddr;//本地协议地址赋给一个套接字
memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family=AF_INET; servaddr.sin_port=htons(5188); servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");//服务器段地址 //inet_aton("127.0.0.1",&servaddr.sin_addr);
if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0) ERR_EXIT("connect"); //利用getsockname获取客户端自己地址和端口,即为对方accept中的对方套接口
struct sockaddr_in localaddr; socklen_t addrlen=sizeof(localaddr); if(getsockname(sock,(struct sockaddr *)&localaddr,&addrlen)<0) ERR_EXIT("getsockname error"); printf("local IP=%s, local port=%d\n",inet_ntoa(localaddr.sin_addr),ntohs(localaddr.sin_port)); //使用getpeername获取对方地址
echo_cli(sock);//选择一个与服务器通讯
return 0; }
服务器端程序:队列
/* 服务器进程要处理SIGPIPE信号,避免被该信号终止进程。利用shutdown函数咱们还可以使得 服务器可以将接收到的内容回射回去 */ #include<unistd.h> #include<sys/types.h> #include<sys/socket.h> #include<string.h> #include<stdlib.h> #include<stdio.h> #include<errno.h> #include<netinet/in.h> #include<arpa/inet.h> #include<signal.h> #include<sys/wait.h>
#define ERR_EXIT(m)\
do\ {\ perror(m);\ exit(EXIT_FAILURE);\ }while(0) ssize_t readn(int fd,void *buf,size_t count) { size_t nleft=count; ssize_t nread; char *bufp=(char*)buf; while(nleft>0) { if((nread=read(fd,bufp,nleft))<0) { if(errno==EINTR) continue; else
return -1; } else if(nread==0) return (count-nleft); bufp+=nread; nleft-=nread; } return count; } ssize_t writen(int fd, const void *buf, size_t count) { size_t nleft=count; ssize_t nwritten; char *bufp=(char*)buf; while(nleft>0) { if((nwritten=write(fd,bufp,nleft))<=0) { if(errno==EINTR) continue; return -1; }else if(nwritten==0) continue; bufp+=nwritten; nleft-=nwritten; } return count; } ssize_t recv_peek(int sockfd,void *buf,size_t len) { while(1) { int ret=recv(sockfd,buf,len,MSG_PEEK);//从sockfd读取内容到buf,但不去清空sockfd,偷窥
if(ret==-1&&errno==EINTR) continue; return ret; } } //偷窥方案实现readline避免一次读取一个字符
ssize_t readline(int sockfd,void * buf,size_t maxline) { int ret; int nread; size_t nleft=maxline; char *bufp=(char*)buf; while(1) { ret=recv_peek(sockfd,bufp,nleft);//不清除sockfd,只是窥看
if(ret<0) return ret; else if(ret==0) return ret; nread=ret; int i; for(i=0;i<nread;i++) { if(bufp[i]=='\n') { ret=readn(sockfd,bufp,i+1);//读出sockfd中的一行而且清空
if(ret!=i+1) exit(EXIT_FAILURE); return ret; } } if(nread>nleft) exit(EXIT_FAILURE); nleft-=nread; ret=readn(sockfd,bufp,nread); if(ret!=nread) exit(EXIT_FAILURE); bufp+=nread;//移动指针继续窥看
} return -1; } void handle_sigchld(int sig) { while(waitpid(-1,NULL, WNOHANG)>0) ; } void handle_sigpipe(int sig) { printf("recevie a sig=%d\n",sig);//打印,不退出服务器进程
} int main(void) { signal(SIGCHLD,handle_sigchld); //signal(SIGPIPE,SIG_IGN);//忽略sigpipe信号
signal(SIGPIPE,handle_sigpipe); int listenfd; if((listenfd=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0) ERR_EXIT("socket error"); //if((listenfd=socket(PF_INET,SOCK_STREAM,0))<0) //本地协议地址赋给一个套接字
struct sockaddr_in servaddr; memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family=AF_INET; servaddr.sin_port=htons(5188); servaddr.sin_addr.s_addr=htonl(INADDR_ANY);//表示本机地址 //开启地址重复使用,关闭服务器再打开不用等待TIME_WAIT
int on=1; if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0) ERR_EXIT("setsockopt error"); //绑定本地套接字
if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0) ERR_EXIT("bind error"); if(listen(listenfd,SOMAXCONN)<0)//设置监听套接字(被动套接字)
ERR_EXIT("listen error"); struct sockaddr_in peeraddr;//对方套接字地址
socklen_t peerlen=sizeof(peeraddr); /* pid_t pid; while(1){ if((conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen))<0) ERR_EXIT("accept error"); //链接好以后就构成链接,端口是客户端的。peeraddr是对端 printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port)); pid=fork(); if(pid==-1) ERR_EXIT("fork"); if(pid==0){ close(listenfd); echo_srv(conn); //某个客户端关闭,结束该子进程,不然子进程也去接受链接 //虽然结束了exit退出,可是内核还保留了其信息,父进程并未为其收尸。 exit(EXIT_SUCCESS); }else close(conn); } */
int client[FD_SETSIZE];//select最大文件描述符,用来保存已链接文件描述符。
int i=0; for(i=0;i<FD_SETSIZE;i++) { client[i]=-1; } int conn;//已链接套接字(主动套接字)
int nready; int maxi=0;//最大不空闲位置
int maxfd=listenfd; fd_set rset,allset; FD_ZERO(&rset); FD_ZERO(&allset); FD_SET(listenfd,&allset); while(1) { rset=allset; nready=select(maxfd+1,&rset,NULL,NULL,NULL);//若是是监听套接口(服务器),已完成链接队列不为空时,accept再也不阻塞;
if(nready==-1) { if(errno==EINTR) continue; ERR_EXIT("select error"); } if(nready==0) continue; if(FD_ISSET(listenfd,&rset))//监听口有事件,已完成队列不为空
{ conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen); if(conn==-1) ERR_EXIT("accept error"); for(i=0;i<FD_SETSIZE;i++) { if(client[i]<0) { client[i]=conn; if(i>maxi) maxi=i;//更新最大不空闲位置
break; } } if(i==FD_SETSIZE) { fprintf(stderr,"too many clents\n"); exit(EXIT_FAILURE); } printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port)); FD_SET(conn,&allset);//将已链接套接字描述符放入allset,用于监测已链接套接口是否有客户端数据到来
if(conn>maxfd) maxfd=conn;//更新maxfd
if(--nready<=0) continue;//若是事件已经处理完,就继续循环监听,再也不执行如下代码
} for(i=0;i<=maxi;i++)//小于等于
{ conn=client[i]; if(conn==-1) continue; if(FD_ISSET(conn,&rset))//已经链接套接字是否有事件,不用while(1)循环处理客户端发送,有select监听。
{ int ret; char recvbuf[1024]; memset(&recvbuf,0,sizeof(recvbuf)); ret=readline(conn,recvbuf,1024); if(ret==-1) ERR_EXIT("readline"); else if(ret==0) { printf("client close\n"); FD_CLR(conn,&allset);//客户端清理,select就不用去监听
client[i]=-1; close(conn);//前面程序BUG,对方关闭以后,咱们服务器也要关闭套接口。让客户端接收到通知
} fputs(recvbuf,stdout); sleep(4);//服务器睡眠4秒。再回射数据,不立刻回射过去。服务器收到客户端的数据等待4秒才回射。 //若是客户端用close关闭套接字,则不能回射回去。 //因为对方关闭,返回RST字段,再次写的时候遇到RST段产生SIGPIPE信号会终止服务器,因此服务器须要加一个信号处理程序处理SIGPIPE信号。
writen(conn,recvbuf,strlen(recvbuf));//write :aaa bbb ,RST,写aaa接收bbb,再bbb,有了SIGPIPE
if(--nready==0) break; } } } return 0; }