close与shutdown

  首先看一个例子,以下图所示:服务器

当咱们客户端发送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; }
相关文章
相关标签/搜索