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; }
相關文章
相關標籤/搜索