1. 程式人生 > >Unix網路-shutdown和close

Unix網路-shutdown和close

四次揮手狀態:

 

close終止了套接字傳送資料的方向。假如我們的客戶端和伺服器端進行通訊,我們在客戶端將socket套接字close,那麼我們無法再利用這個套接字向伺服器端傳送資訊,也無法再利用這個套接字從伺服器中接受資訊。但是shutdown不同,我們可以自己選擇shutdown之後套接字的功能。

在多程序伺服器中,如果父程序close conn套接字,那麼伺服器不會像客戶端傳送FIN訊號,因為套接字有一個引用計數。每建立一個子程序,這個引用計數就加1。shutdown不管在哪個程序中,只要shutdown就一定能傳送FIN訊號。

客戶端cpp

客戶端 client.cpp
——————————————————————————————————

#include 
<stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include<sys/select.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include<iostream> using namespace std; int main(){ int sockfd = socket(AF_INET, SOCK_STREAM, 0
); if (sockfd == -1) { perror("socket() err"); return -1; } struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(8888); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1
) { perror("socket() err"); return -1; } char sendbuf[1024]={0}; char recvbuf[1024]={0}; fd_set rset; FD_ZERO(&rset); int nready; int fd_stdin=fileno(stdin); while(1){ FD_SET(fd_stdin,&rset); FD_SET(sockfd,&rset); int maxfd; if(sockfd>fd_stdin) maxfd=sockfd; else maxfd=fd_stdin; nready=select(maxfd+1,&rset,NULL,NULL,NULL); if(nready==-1) perror("nready"); if(nready==0) continue; if(FD_ISSET(sockfd,&rset)){ int rc=read(sockfd,recvbuf,sizeof(recvbuf)); if(rc<0){ perror("read() error"); break; }else if(rc==0){ printf("connect is cloesd !\n"); break; } } printf("recv message:%s\n",recvbuf); memset(sendbuf,0,sizeof(sendbuf)); memset(recvbuf,0,sizeof(recvbuf)); if(FD_ISSET(fd_stdin,&rset)){ if(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL) write(sockfd,sendbuf,sizeof(sendbuf)); else { close(sockfd);\\我們在此處設定關閉客戶端套接字,ctrl+D }; } } return 0; }
View Code

伺服器端cpp

#include<stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include<iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include<sys/select.h>
#include<errno.h>
using namespace std;
int main()
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1)
    {
        perror("socket() err");
        return -1;
    }
    int on = 1;
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
    {
        perror("setsockopt() err");
        return -1;
    }
    char recvbuf[1024];
    int conn;
    int client[FD_SETSIZE];
    for(int i=0;i<FD_SETSIZE;i++){
        client[i]=-1;
    }
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8888);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    if (bind(sockfd, (struct sockaddr *) &addr, sizeof(addr)) == -1)
    {
        perror("bind() err");
        return -1;
    }
    if (listen(sockfd, SOMAXCONN) == -1)
    {
        perror("bind() err");
        return -1;
    }
    struct sockaddr_in peeraddr;
   // socklen_t peerlen = sizeof(peeraddr);
    /* conn = accept(sockfd, (struct sockaddr *) &peeraddr, &peerlen);
    if (conn == -1)
    {
        perror("accept() err");
        return -1;
    }
    printf("accept by :%s \n", inet_ntoa(peeraddr.sin_addr));*/
    //char recvbuf[1024] = { 0 };
   int nready;
   int maxfd=sockfd;
   fd_set rset;
   int maxi=0;
   fd_set allset;
   FD_ZERO(&rset);
   FD_ZERO(&allset);
   FD_SET(sockfd,&allset);
   while(1){
       rset=allset;
       nready=select(maxfd+1,&rset,NULL,NULL,NULL);
       if(FD_ISSET(sockfd,&rset)){
           socklen_t peerlen = sizeof(peeraddr);
           conn=accept(sockfd,(struct sockaddr *) &peeraddr, &peerlen);
           printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
       for(int i=0;i<FD_SETSIZE;i++){
           if(client[i]<0){
               client[i]=conn;
               if(i>maxi)
                   maxi=i;
               break;
           }
       }
       
              FD_SET(conn,&allset);
             if(conn>maxfd)
                 maxfd=conn;
              if(--nready<=0)
                  continue;
   }
       for(int i=0;i<FD_SETSIZE;i++){
           conn=client[i];
           if(conn==-1)
               continue;
           if(FD_ISSET(conn,&rset)){
               int rc = read(conn, recvbuf, sizeof(recvbuf));
if (rc == 0)
{   
    cout<<"client has closed"<<endl;
    FD_CLR(conn,&allset);
   client[i]=-1;
    close(conn);  

}
           else{ 
               sleep(5);\\在這裡設定5秒後才對客戶端進行迴應,這樣就可以模擬他們之間的管道。
               printf("recv message:%s\n", recvbuf);
            write(conn, recvbuf, rc);
            memset(recvbuf, 0, sizeof(recvbuf));
            
           
            if(--nready<=0)
               break;
       }
   
   }
}
   
}
    close(conn);
    close(sockfd);
    return 0;
}
View Code

接著啟動客戶端和服務端,我們連續快速傳送兩條資訊給服務端,最終的結果如下:

客戶端出現了bad file descriptor的錯誤,這是因為,我們將套接字關閉後,又在rset中設定了其監聽位導致的。然後接著看到,伺服器端也崩潰了,自動退出。這是因為,當伺服器準備回射第一條資訊時,客戶端的套接字已經關閉,此時,伺服器端會接收到一個RST,如果伺服器再次嘗試回射,那麼伺服器會產生一個SIGPIPE訊號,如果我們不會SIGPIPE訊號處理,那麼,根據訊號的預設處理規則SIGPIPE訊號的預設執行動作是terminate(終止、退出),所以server會退出。我們來證明一下,我們客戶端只發送一條資訊,那麼服務端只會受到一個RST,而不會退出。

 

可以看到,伺服器並沒有退出。

那麼接下來,我們對SIGPIPE訊號進行處理,這樣也不會導致服務端的退出。

server.cpp

#include<stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include<iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include<sys/select.h>
#include<errno.h>
#include<sys/signal.h>
using namespace std;

//訊號處理
void handler(int sig){
     cout<<sig<<endl;
}
int main()
{   //signal(SIGPIPE,SIG_IGN) //我們預設自動處理也行
    signal(SIGPIPE,handler); //用handler處理
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1)
    {
        perror("socket() err");
        return -1;
    }
    int on = 1;
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
    {
        perror("setsockopt() err");
        return -1;
    }
    char recvbuf[1024];
    int conn;
    int client[FD_SETSIZE];
    for(int i=0;i<FD_SETSIZE;i++){
        client[i]=-1;
    }
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8888);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    if (bind(sockfd, (struct sockaddr *) &addr, sizeof(addr)) == -1)
    {
        perror("bind() err");
        return -1;
    }
    if (listen(sockfd, SOMAXCONN) == -1)
    {
        perror("bind() err");
        return -1;
    }
    struct sockaddr_in peeraddr;
   // socklen_t peerlen = sizeof(peeraddr);
    /* conn = accept(sockfd, (struct sockaddr *) &peeraddr, &peerlen);
    if (conn == -1)
    {
        perror("accept() err");
        return -1;
    }
    printf("accept by :%s \n", inet_ntoa(peeraddr.sin_addr));*/
    //char recvbuf[1024] = { 0 };
   int nready;
   int maxfd=sockfd;
   fd_set rset;
   int maxi=0;
   fd_set allset;
   FD_ZERO(&rset);
   FD_ZERO(&allset);
   FD_SET(sockfd,&allset);
   while(1){
       rset=allset;
       nready=select(maxfd+1,&rset,NULL,NULL,NULL);
       if(FD_ISSET(sockfd,&rset)){
           socklen_t peerlen = sizeof(peeraddr);
           conn=accept(sockfd,(struct sockaddr *) &peeraddr, &peerlen);
           printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
       for(int i=0;i<FD_SETSIZE;i++){
           if(client[i]<0){
               client[i]=conn;
               if(i>maxi)
                   maxi=i;
               break;
           }
       }
       
              FD_SET(conn,&allset);
             if(conn>maxfd)
                 maxfd=conn;
              if(--nready<=0)
                  continue;
   }
       for(int i=0;i<FD_SETSIZE;i++){
           conn=client[i];
           if(conn==-1)
               continue;
           if(FD_ISSET(conn,&rset)){
               int rc = read(conn, recvbuf, sizeof(recvbuf));
if (rc == 0)
{   
    cout<<"client has closed"<<endl;
    FD_CLR(conn,&allset);
   client[i]=-1;
    close(conn);  

}
           else{ 
               sleep(5);
               printf("recv message:%s\n", recvbuf);
            write(conn, recvbuf, rc);
            memset(recvbuf, 0, sizeof(recvbuf));
            
           
            if(--nready<=0)
               break;
       }
   
   }
}
  
}
    close(conn);
    close(sockfd);
    return 0;
}
View Code

結果如下:

伺服器將不會自動結束

 

 

 

接下來我們使用shutdown對客戶端進行關閉。詳細的shutdown引數可以自己man 一下

client.cpp

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include<sys/select.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include<iostream>
using namespace std;
int main(){
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1)
    {
        perror("socket() err");
        return -1;
    }
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8888);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
    {
        perror("socket() err");
        return -1;
    }
    char sendbuf[1024]={0};
    char recvbuf[1024]={0};
    fd_set rset;
    FD_ZERO(&rset);
    int nready;
    int fd_stdin=fileno(stdin);
    while(1){
        FD_SET(fd_stdin,&rset);
        FD_SET(sockfd,&rset);
        int maxfd;
        if(sockfd>fd_stdin)
            maxfd=sockfd;
        else 
            maxfd=fd_stdin;
        nready=select(maxfd+1,&rset,NULL,NULL,NULL);
        if(nready==-1)
            perror("nready");
        if(nready==0)
            continue;
        if(FD_ISSET(sockfd,&rset)){
              int rc=read(sockfd,recvbuf,sizeof(recvbuf));
              if(rc<0){
                  perror("read() error");
                   break;
              }else if(rc==0){
                  printf("connect is cloesd !\n");
                  break;
              }
        }
         printf("recv message:%s\n",recvbuf);
          memset(sendbuf,0,sizeof(sendbuf));
          memset(recvbuf,0,sizeof(recvbuf));
          if(FD_ISSET(fd_stdin,&rset)){
              if(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL)
              write(sockfd,sendbuf,sizeof(sendbuf));
                
                else {
                    shutdown(sockfd,SHUT_WR);\\shutdown套接字的寫入功能,但是不關閉讀取功能
                          
                     };
          }
    }
   return 0;
}
View Code

 

 

結果就是,當我們在客戶端按下CTRL+D時,客戶端會等待,等待資訊回射完畢之後,再退出