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时,客户端会等待,等待信息回射完毕之后,再退出

猜你喜欢

转载自www.cnblogs.com/illfuckingkyzb/p/10151271.html