第七章:优雅地断开套接字连接

文章介绍了在TCP通信中如何优雅地断开连接,特别是使用shutdown函数实现半关闭连接,允许单向数据传输。通过示例展示了服务器如何在发送完文件后关闭输出流,仍能接收客户端的反馈,而客户端在接收完整文件后发送感谢信息。
摘要由CSDN通过智能技术生成

第七章:优雅地断开套接字连接

7、1 基于TCP的半关闭:

  • 单方面断开连接的问题:

    • Linux的close函数意味着完全断开连接,完全断开连接不仅无法传输数据,而且不能接收数据。通信一方调用close函数断开连接显得不太优雅。
      在这里插入图片描述

    • 正在双向通信的A,B主机,在主机A发送完数据后,调用close函数断开连接后,主机A就无法调用接收数据的函数来接收主机B传输的数据,所以由主机B传输的、主机A必须接收的数据也销毁了。

    • “只关闭一部分数据交换中使用的流。(Half-close)” 断开一部分连接指,可以传输数据但是无法接收,或可以接收数据但无法传输。即只关闭流的一半。

  • 套接字和流:(Stream)

    • 两台主机通过套接字建立连接进入可交换数据的状态,称为“流形成的状态”,即将建立套接字连接后可交换数据的状态看作一种流。

    • 套接字的流中,数据也只能向一个方向移动。所以为了进行双向通信,需要两个流。
      在这里插入图片描述

    • 两台主机建立了套接字连接,每个主机会拥有单独的输入流和输出流。其中一个主机的输入流与另一个主机的输出流相连,输出流与另一个主机的输入流相连。

  • 针对优雅地断开secoket的shutdown函数:

    • shutdown函数用来关闭其中1个流:

    • #include <sys/socket.h>
      int shutdown(int sock, int howto); // 成功返回0,失败返回-1
      
      • sock : 需要断开的套接字文件描述符
      • howto:传递断开方式的信息
    • howto参数决定断开连接的方式,其值可能为:

      • SHUT_RD:断开输入流
      • SHUT_WR:断开输入流
      • SHUT_RDWR:同时断开I/O流
    • howto参数传入SHUT_RD,则断开输入流,套接字无法接收数据,即使输入缓冲区收到数据也会抹去,而且无法调用输入相关函数。

    • howto 参数传入 SHUT_WR,则断开输出流,无法传输数据,如果输出缓冲中还留有未传输的数据,则将传递至目标主机。

    • howto参数传入SHUT_RDER相当于分两次调用shutdown,一次以SHUT_RD为参数一次以SHUT_WR为参数。

  • 基于半关闭的文件传输程序:

    • 文件传输服务器与客户端的数据流如图:
      在这里插入图片描述

    • 服务器端代码示例:

      • #include <stdio.h>
        #include <stdlib.h>
        #include <string.h>
        #include <unistd.h>
        #include <arpa/inet.h>
        #include <sys/socket.h>
        
        #define BUF_SIZE 30
        void error_handling(const char* message);
        
        int main(int argc, char* argv[]){
            FILE * fp;
            int serv_sd,clnt_sd;
            struct sockaddr_in serv_adr,clnt_adr;
            socklen_t clnt_adr_sz;
            char buf[BUF_SIZE];
            int read_cnt;
        
            if(argc != 2){
                printf("Usage: %s <port>\n",argv[0]);
                exit(1);
            }
            // 二进制文件读, 打开文件以向客户端传输服务器端源文件file_server.c
            fp = fopen("file_server.c","rb");
            serv_sd = socket(PF_INET,SOCK_STREAM,0);
        
            memset(&serv_adr,0,sizeof(serv_adr));
            serv_adr.sin_family = AF_INET;
            serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
            serv_adr.sin_port = htons(atoi(argv[1]));
        
            if(bind(serv_sd,(struct sockaddr*)&serv_adr,sizeof(serv_adr)) == -1)
                error_handling("bind() error");
            
            if(listen(serv_sd,5) == -1)
                error_handling("listen() error");
            
            clnt_adr_sz = sizeof(clnt_adr);
            clnt_sd = accept(serv_sd,(struct sockaddr*)&clnt_adr,&clnt_adr_sz);
        
            if(clnt_sd == -1)
                error_handling("accept() error");
            else
                printf("Connected......");
            // 向客户端传输文件数据的循环语句。客户端是accept函数调用中连接的
            while(1){
                read_cnt = fread((void*)buf, 1, BUF_SIZE,fp);
                if(read_cnt < BUF_SIZE){
                    write(clnt_sd, buf,read_cnt);
                    printf("last read_cnt : %d\n", read_cnt);
                    break;
                }
                write(clnt_sd,buf,BUF_SIZE);
                printf("read_cnt : %d\n", read_cnt);
            }
            // 发送完文件后针对输出流进行半关闭
            shutdown(clnt_sd,SHUT_WR);
            // 只关闭了输出流,还可以通过输入流接收数据
            read(clnt_sd,buf,BUF_SIZE);
            printf("Message from client: %s \n",buf);
        
            fclose(fp);
            close(clnt_sd);
            close(serv_sd);
            return 0;
        }
        
        void error_handling(const char* message){
            fputs(message,stderr);
            fputc('\n',stderr);
            exit(1);
        }
        
    • 客户端代码示例:

      • #include <stdio.h>
        #include <stdlib.h>
        #include <string.h>
        #include <unistd.h>
        #include <arpa/inet.h>
        #include <sys/socket.h>
        
        #define BUF_SIZE 30
        void error_handling(const char* message);
        
        int main(int argc, char* argv[]){
            FILE* fp;
            int sd;
            struct sockaddr_in serv_adr; 
            char buf[BUF_SIZE];
            int read_cnt;
            if(argc != 3){
                printf("Usage : %s <IP> <port>\n",argv[0]);
                exit(1);
            }
            // 创建新文件以保存服务器端传输的文件数据
            fp = fopen("receive.dat","wb");
            sd = socket(PF_INET, SOCK_STREAM,0);
        
            memset(&serv_adr, 0, sizeof(serv_adr));
            serv_adr.sin_family = AF_INET;
            serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
            serv_adr.sin_port = htons(atoi(argv[2]));
        
            if(connect(sd,(struct sockaddr*)&serv_adr, sizeof(serv_adr)))
                error_handling("Connect() error");
            else{
                puts("Connected .......");
            }
            // 接收数据保存到文件fp中,知道接收到EOF为止
            while((read_cnt = read(sd,buf,BUF_SIZE)) != 0)
                fwrite((void*)buf,1,read_cnt,fp);
            
            puts("Recevied file data");
            //向服务器端发送感谢信息。如果服务器端没有关闭输入流,则可以接收到这个消息
            write(sd,"Thank you",10);
        
            fclose(fp);
            close(sd);
            return 0;
        }
        
        void error_handling(const char* message){
            fputs(message,stderr);
            fputc('\n',stderr);
            exit(1);
        }
        
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值