TCP/IP网络编程笔记Chapter I -5基于半关闭的文件传输程序 & shutdown函数

TCP/IP网络编程笔记Chapter I -5基于半关闭的文件传输程序 & shutdown函数

  • 1.TCP套接字中的IO缓冲
  • 2.基于TCP的半关闭
    • (1)单方面断开连接带来的问题
    • (2)shutdown()函数
    • (3)为何必须半关闭
  • 3.基于半关闭的文件传输程序
    • (1)服务器端
    • (2)客户端
    • (3)运行

如前所述,TCP套接字的数据收发无边界,在上一节 实现一个简单TCP服务器端与客户端的最后,在客户端我们调用了一次write()函数写入,但多次调用了read()函数。这让我们产生了疑问,假设每次传输40字节,而一次接受10字节,那么剩下的30字节在哪等候呢?TCP套接字中的IO缓冲将告诉我们答案。

1.TCP套接字中的IO缓冲

事实上,write()函数调用后并非立即传输数据,read()函数调用后也并非马上接收数据,更准确的说write()函数调用瞬间,数据移至输出缓冲并在适当的时候(不论是一次还是多次传送)传向对方的输入缓冲;read()函数调用瞬间,从输入缓冲中读取数据。
TCP/IP网络编程笔记Chapter I -5基于半关闭的文件传输程序 & shutdown函数_第1张图片
I/O缓冲特性如下:

  • I/O缓冲在创建套接字时自动生成,在每个TCP套接字中单独存在
  • 关闭套接字会继续传递输出缓冲中的数据
  • 关闭套接字将丢失输入缓冲的数据

2.基于TCP的半关闭

(1)单方面断开连接带来的问题

Linux和Windows的closesocket函数意味着完全断开连接,在某些情况下可能带来问题。若A、B相互通信,A发送完最后的数据后调用close()函数断开连接,之后A无法再发送数据给B,但是由主机B发送的,A必须接收的数据也销毁了。
TCP/IP网络编程笔记Chapter I -5基于半关闭的文件传输程序 & shutdown函数_第2张图片

(2)shutdown()函数

两台主机通过套接字建立连接后可进入交换数据的状态,我们可以把它视作是一种流(像水流一样朝一个方向流动)。
TCP/IP网络编程笔记Chapter I -5基于半关闭的文件传输程序 & shutdown函数_第3张图片
我们使用shutdown函数可以用来关闭其中一个流。

	#include
	int shutdown(int sock,int howto);
  • 成功时返回0,失败时返回-1
  • sock是需要断开套接字的文件描述符
  • howto传递断开方式信息
howto的参数 含义
SHUT_RD 断开输入流(会抹去输入缓冲的数据)
SHUT_WR 断开输出流(会继续传递输出缓冲中的数据)
SHUT_RDWR 同时断开I/O流

(3)为何必须半关闭

有的朋友可能会有疑惑,究竟为什么需要半关闭,如果留出足够长的时间来保证完成数据传输不就行了吗?没错,保留足够长的时间确实不需要半关闭,但是需要考虑如下情况:假设服务器只用来传输文件数据,而客户端无法知道数据接收到何时,客户端也不能无休止的调用输入函数。为解决此问题,服务器端应传递EOF告诉客户端表示文件传输结束这样就可以了。那么服务器怎么传递呢?shutdown函数应运而生,它可以关闭服务器端的输出流并传递EOF,又保留了输入流,用来接收客户端的消息。

3.基于半关闭的文件传输程序

下面我们结合以学内容实现一个收发文件的服务器端与客户端。整体流程如下,使用服务器端发送一个.c文件给客户端,发送结束服务器端后半关闭输出流,最后验证服务器端输入流不会受到影响,接受客户端发送的thank you。
TCP/IP网络编程笔记Chapter I -5基于半关闭的文件传输程序 & shutdown函数_第4张图片

(1)服务器端

服务器端创建并发送file_server.c给客户端,传输完成后半关闭输出流再读取客户端回传的数据。

#define BUF_SIZE 30
char buf[BUF_SIZE];
FILE *fp;
fp = fopen("file_server.c", "rb");
//每次读取30个字节,不够30字节则全部读取
while (1)
    {
     
        read_cnt = fread((void *)buf, 1, BUF_SIZE, fp);
        if (read_cnt < BUF_SIZE)
        {
     
            write(clnt_sd, buf, BUF_SIZE);
            break;
        }
        write(clnt_sd, buf, BUF_SIZE);
    }     
    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);

其中fread()函数函数原型如下
TCP/IP网络编程笔记Chapter I -5基于半关闭的文件传输程序 & shutdown函数_第5张图片
省略掉错误处理的完整代码如下

#include 
#include 
#include 
#include 
#include 
#include 
 
#define BUF_SIZE 30
int main(int argc, char *argv[])
{
     
    int serv_sd, clnt_sd;
    FILE *fp;
    char buf[BUF_SIZE];
    int read_cnt;
 
    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t clnt_adr_sz;
 
    if (argc != 2)
    {
     
        printf("Usage:%s\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]));
 
    bind(serv_sd, (struct sockaddr *)&serv_adr, sizeof(serv_adr));
    listen(serv_sd, 5);
 
    clnt_adr_sz = sizeof(clnt_adr);
    clnt_sd = accept(serv_sd, (struct sockaddr *)&clnt_adr, &clnt_adr_sz);
    while (1)
    {
     //每次读取30个字节,不够30字节则全部读取
        read_cnt = fread((void *)buf, 1, BUF_SIZE, fp);
        if (read_cnt < BUF_SIZE)
        {
     
            write(clnt_sd, buf, BUF_SIZE);
            break;
        }
        write(clnt_sd, buf, BUF_SIZE);
    }
     
    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;
}

(2)客户端

客户端创建并把收到数据写入receive.dat中,最后回传Thank you

#define BUF_SIZE 30 
FILE *fp;
fp = fopen("receive.dat", "wb");
while ((read_cnt = read(sd, buf, BUF_SIZE)) != 0)
	fwrite((void *)buf, 1, read_cnt, fp);
puts("Received file data");
write(sd, "Thank you", 10);
fclose(fp);

完整代码如下

#include 
#include 
#include 
#include 
#include 
#include 
 
#define BUF_SIZE 30 
int main(int argc, char *argv[])
{
     
    int sd;
    FILE *fp;
 
    char buf[BUF_SIZE];
    int read_cnt;
    struct sockaddr_in serv_adr;
    if (argc != 3)
    {
     
        printf("Usage:%s\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]));
 
    connect(sd, (struct sockaddr *)&serv_adr, sizeof(serv_adr));
 
    while ((read_cnt = read(sd, buf, BUF_SIZE)) != 0)
        fwrite((void *)buf, 1, read_cnt, fp);
 
    puts("Received file data");
    write(sd, "Thank you", 10);
    fclose(fp);
    close(sd);
    return 0;
}

(3)运行

服务器端
在这里插入图片描述
客户端
在这里插入图片描述
可以看出新创建的receive.dat文件
在这里插入图片描述
打开后可以看到传入的file_server.c代码
TCP/IP网络编程笔记Chapter I -5基于半关闭的文件传输程序 & shutdown函数_第6张图片

你可能感兴趣的:(TCP/IP网络编程,linux,socket,网络,c++)