《TCP/IP网络编程》学习笔记 | Chapter 7:优雅地断开套接字连接

《TCP/IP网络编程》学习笔记 | Chapter 7:优雅地断开套接字连接

  • 《TCP/IP网络编程》学习笔记 | Chapter 7:优雅地断开套接字连接
    • 基于 TCP 的半关闭
      • 单方面断开连接带来的问题
      • 套接字和流
      • 针对优雅断开的 shutdown 函数
      • 为何需要半关闭?
      • 基于半关闭的文件传输程序
    • 基于 Windows 的实现
      • Windows 下的 shutdown 函数
      • 基于 Windows 的半关闭文件传输程序
    • 习题
      • (1)解释 TCP 中 “流” 的概念。UDP 中能否形成流?请说明原因。
      • (2)Linux 中的 close 函数或 Windows 中的closesocket函数属于单方面断开连接的方法,有可能带来一些问题。什么是单方面断开连接?什么情况下会出现问题?
      • (3)什么是半关闭?针对输出流执行半关闭的主机处于何种状态?半关闭会导致对方主机接收什么信息?

《TCP/IP网络编程》学习笔记 | Chapter 7:优雅地断开套接字连接

基于 TCP 的半关闭

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

Linux 中的 close 与 Windows 中的 closesocket 函数意味着完全断开连接,完全断开后,套接字既无法传输数据,也无法接收数据。

在这里插入图片描述

如上图所示,主机A断开连接后,再也无法接受主机B传输的数据,最终主机B传输的数据只能销毁。

为了解决这一问题,在关闭连接时,只关闭流的一部分(半关闭),即可以传输数据但不能接受数据,或者可以接收数据但不能传输数据。

套接字和流

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

在这里插入图片描述

Linux 中的 close 与 Windows 中的 closesocket 函数将同时断开这两个流。

针对优雅断开的 shutdown 函数

半关闭函数:

#include <sys/socket.h>int shutdown(int sock, int howto);

成功时返回 0,失败时返回 -1。

参数:

  • sock:需要断开的套接字文件描述符。
  • howto:断开方式信息。其有 3 种可能值。SHUT_RD:断开输入流;SHUT_WR:断开输出流;SHUT_RDWR:同时断开 I/O 流。

SHUT_RD,SHUT_WR,SHUT_RDWR 的值按序分别是 0,1,2。若向 shutdown 的第二个参数传递SHUT_RD,则断开输入流,套接字无法接收数据。即使输入缓冲收到数据也会抹去,而且无法调用输入相关函数。如果向 shutdown 的第二个参数传递SHUT_WR,则中断输出流,也就无法传输数据。若如果输出缓冲中还有未传输的数据,则将传递给目标主机。最后,若传递关键字SHUT_RDWR,则同时中断 I/O 流。这相当于分 2 次调用 shutdown ,其中一次以SHUT_RD为参数,另一次以SHUT_WR为参数。

为何需要半关闭?

  1. 数据传输完成: 当一方已经发送完所有需要发送的数据,但仍然需要接收对方的响应或数据时,可以使用半关闭。这样,发送方可以告诉对方已经没有更多的数据要发送了。
  2. 错误处理: 如果一方在通信过程中遇到错误,它可能会选择关闭发送方向,以防止发送更多的数据,同时仍然监听对方可能发送的错误响应或状态信息。
  3. 保持连接: 在某些应用场景中,即使数据传输已经完成,一方可能仍希望保持连接,以便在将来需要时重新使用,而不是重新建立连接。
  4. 优雅地关闭连接: 在TCP连接中,半关闭允许一方在发送完所有数据后优雅地关闭连接,而不是突然断开,这有助于另一方正确地处理连接的关闭。
  5. 调试和诊断: 在调试网络应用程序时,半关闭可以帮助开发者理解数据流和连接状态,从而更容易地诊断问题。

比如服务器给客户端发数据,发完后客户端回一个 “Thank you”,但客户端不知道什么时候发完,所以需要一直调用 read() 函数。

改进1:可以在发完数据后服务器向客户端发送EOF表示发送结束。

问题:服务器调用 close() 函数关闭连接并发送 EOF 后,输入流也断了,客户端发的 “Thank you” 将无法收到。

改进2:调用 shutdown() 函数只关闭服务器的输入流就行了。

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

协议示意图:

在这里插入图片描述

服务器端的代码:

#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(char *message);int main(int argc, char *argv[])
{int serv_sd, clnt_sd;FILE *fp;char buf[BUF_SIZE];int read_cnt;struct sockaddr_in serv_addr, clnt_addr;socklen_t clnt_addr_sz;if (argc != 2){printf("Usage: %s <port>\n", argv[0]);exit(1);}fp = fopen("file_server.c", "rb");serv_sd = socket(PF_INET, SOCK_STREAM, 0);memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);serv_addr.sin_port = htons(atoi(argv[1]));bind(serv_sd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));listen(serv_sd, 5);clnt_addr_sz = sizeof(clnt_addr);clnt_sd = accept(serv_sd, (struct sockaddr *)&clnt_addr, &clnt_addr_sz);while (1){read_cnt = fread((void *)buf, 1, BUF_SIZE, fp);if (read_cnt < BUF_SIZE){write(clnt_sd, buf, read_cnt);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;
}void error_handling(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 30void error_handling(char *message);int main(int argc, char *argv[])
{int sd;FILE *fp;char buf[BUF_SIZE];int read_cnt;struct sockaddr_in serv_addr;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_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = inet_addr(argv[1]);serv_addr.sin_port = htons(atoi(argv[2]));connect(sd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));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;
}void error_handling(char *message)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}

基于 Windows 的实现

Windows 下的 shutdown 函数

#include <winsock2.h>int shutdown(SOCKET s, int howto);

成功时返回 0,失败时返回 SOCKET_ERROR。

参数:

  • s:要断开的套接字的句柄。
  • howto:断开方式信息。其有 3 种可能值。SHUT_RECEIVE:断开输入流;SHUT_SEND:断开输出流;SHUT_BOTH:同时断开 I/O 流。其值按序分别是 0,1,2。

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

file_server_win.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>#define BUF_SIZE 30void ErrorHanding(char *message)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}int main(int argc, char *argv[])
{WSADATA wsaData;SOCKET serverSock, clientSock;SOCKADDR_IN serverAddr, clientAddr;int clientAddrSize;int read_cnt;char file_name[] = "file_server_win.c";char buf[BUF_SIZE];if (argc != 2){printf("Usage: %s <port>\n", argv[0]);exit(1);}if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)ErrorHanding("WSAStartup() error!");serverSock = socket(PF_INET, SOCK_STREAM, 0);if (serverSock == INVALID_SOCKET)ErrorHanding("socket() error!");memset(&serverAddr, 0, sizeof(serverAddr));serverAddr.sin_family = AF_INET;serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);serverAddr.sin_port = htons(atoi(argv[1]));if (bind(serverSock, (SOCKADDR *)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)ErrorHanding("bind() error!");if (listen(serverSock, 5) == SOCKET_ERROR)ErrorHanding("listen() error!");clientAddrSize = sizeof(clientAddr);clientSock = accept(serverSock, (SOCKADDR *)&clientAddr, &clientAddrSize);if (clientSock == INVALID_SOCKET)ErrorHanding("accept() error!");FILE *fp = fopen(file_name, "rb");if (fp != NULL){while (1){read_cnt = fread((void *)buf, 1, BUF_SIZE, fp);if (read_cnt < BUF_SIZE){send(clientSock, (char *)&buf, read_cnt, 0);break;}elsesend(clientSock, (char *)&buf, BUF_SIZE, 0);}}shutdown(clientSock, SD_SEND);recv(clientSock, (char *)buf, BUF_SIZE, 0);printf("Message from client: %s\n", buf);fclose(fp);closesocket(clientSock);closesocket(serverSock);WSACleanup();return 0;
}

file_client_win.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>#define BUF_SIZE 30void ErrorHanding(char *message)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}int main(int argc, char *argv[])
{WSADATA wsaData;SOCKET sock;SOCKADDR_IN serverAddr;int read_cnt;char file_name[] = "receive.dat";char buf[BUF_SIZE];if (argc != 3){printf("Usage: %s <IP> <port>\n", argv[0]);exit(1);}if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)ErrorHanding("WSAStartup() error!");sock = socket(PF_INET, SOCK_STREAM, 0);if (sock == INVALID_SOCKET)ErrorHanding("sock() error!");memset(&serverAddr, 0, sizeof(serverAddr));serverAddr.sin_family = AF_INET;serverAddr.sin_addr.s_addr = inet_addr(argv[1]);serverAddr.sin_port = htons(atoi(argv[2]));if (connect(sock, (SOCKADDR *)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)ErrorHanding("connect() error!");FILE *fp = fopen(file_name, "wb");while ((read_cnt = recv(sock, buf, BUF_SIZE, 0)) != 0)fwrite((void *)buf, 1, read_cnt, fp);printf("Received file data\n");send(sock, "Thank you", 10, 0);fclose(fp);closesocket(sock);WSACleanup();return 0;
}

编译:

gcc file_server_win.c -lwsock32 -o fileServWin
gcc file_client_win.c -lwsock32 -o fileClntWin

运行结果:

// 服务器端
C:\Users\81228\Documents\Program\TCP IP Project\Chapter 7>fileServWin 9190
Message from client: Thank you
// 客户端
C:\Users\81228\Documents\Program\TCP IP Project\Chapter 7>fileClntWin 127.0.0.1 9190
Received file data

接收到的文件 receive.dat 里面的内容就是 file_server_win.c,这里只展示部分内容:

在这里插入图片描述

习题

(1)解释 TCP 中 “流” 的概念。UDP 中能否形成流?请说明原因。

TCP的流是指,两台主机通过套接字建立连接后进入可交换数据的状态,也称为“流形成的状态”。也就是把建立套接字后可交换数据的状态看做一种流。

UDP是基于报文面向无连接的,没有建立连接的过程,所以不能形成流。

(2)Linux 中的 close 函数或 Windows 中的closesocket函数属于单方面断开连接的方法,有可能带来一些问题。什么是单方面断开连接?什么情况下会出现问题?

单方面断开连接就是两台主机正在通信,其中一台主机关闭了所有连接,那么一台主机向另一台主机传输的数据可能会没有接收到而损毁。

单方面的断开连接意味着套接字无法再发送数据。一般在对方有剩余数据未发送完成时,断开己方连接,会造成问题。

(3)什么是半关闭?针对输出流执行半关闭的主机处于何种状态?半关闭会导致对方主机接收什么信息?

半关闭就是把输入流或者输出流关了。

针对输出流执行半关闭的主机处于可以接收数据而不能发送数据。

半关闭会使其​发送最后一个报文段时附带一个EOF,告诉对方主机自己没有数据要发了,但还是可以接收对方主机传送的数据。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/468575.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python | 包

1. 在python中什么是包&#xff1f; ​ 包是一种组织代码的方式&#xff0c;如下图所示红色部分目录mypackage就称为一个包&#xff0c;它之所以称为一个包完全是因为它里面有蓝色方框里的文件__init__.py。 ​ 这个目录被定义为一个包之后&#xff0c;我们就可以通过import来…

Qt信号和槽-->day04

Qt信号和槽 标准的信号和槽函数Qt中的槽函数Qt中的信号 connect案例 自定义信号和槽案例分析 信号槽的拓展信号连接信号案例 信号槽的两种连接方式Qt5中的处理方式Qt4中的处理方式Qt5处理信号槽重载问题案例 lambda表达式简单案例Qt中的应用 补充知识点 标准的信号和槽函数 QW…

Golang | Leetcode Golang题解之第552题学生出勤记录II

题目&#xff1a; 题解&#xff1a; const mod int 1e9 7type matrix [6][6]intfunc (a matrix) mul(b matrix) matrix {c : matrix{}for i, row : range a {for j : range b[0] {for k, v : range row {c[i][j] (c[i][j] v*b[k][j]) % mod}}}return c }func (a matrix) p…

腾讯首个3D生成大模型Hunyuan3D-1.0分享

Hunyuan3D-1.0是腾讯混元团队开发的首个同时支持文字、图像转3D的大模型&#xff0c;可以基于文本描述或单张图像生成3D模型。 Hunyuan3D-1.0采用了多视图生成和多视图重建两阶段的方法&#xff0c;能够从不同视角捕捉对象的纹理和几何信息。 在多视图生成阶段&#xff0c;Hu…

【JavaEE初阶 — 多线程】内存可见性问题 volatile

1. 内存可见性问题 内存可见性的概念 什么是内存可见性问题呢&#xff1f; 当一个线程对共享变量进行了修改&#xff0c;那么另外的线程都是立即可以看到修改后的最新值。在Java中&#xff0c;可以借助 synchronized、volatile 以及各种Lock 实现可见性。如果我们将变量声…

排序算法.

排序算法是最常用的一种算法.它解决的主要问题是在一定的时间复杂度和空间复杂度的条件下,对n个数按照一定的顺序进行排序.排序算法主要分为四大类,即插入类,交换类,选择类和归并类,不同的排序算法的时间复杂程度和空间复杂程度差别很大. 排序算法主要有以下几种: 1.插入类排…

iOS18.1通話錄音實測 錄音夠清晰 文字轉錄廣東話用唔到?

iOS 18.1功能實測&#xff5c;期待已久的通話錄音功能終在iOS18.1推出&#xff0c;讓用家可以在通話過程中輕鬆錄音&#xff0c;並附上逐字稿功能&#xff0c;為使用者提供更靈活的通話記錄方式。記者實測通話錄音功能&#xff0c;看看錄音清晰度、方便性、逐字轉錄的表現。 打…

403 Request Entity Too Lager(请求体太大啦)

昨天收到 QA 的生产报障&#xff0c;说是测试环境的附件上传功能报了 403 的错误&#xff0c;错误信息&#xff1a;403 Request Entity Too Lager。我尝试复现问题&#xff0c;发现传个几兆的文件都费劲啊&#xff0c;一传一个失败。不用说&#xff0c;项目用到 ng 代理&#x…

【C++】新手入门指南

> &#x1f343; 本系列为初阶C的内容&#xff0c;如果感兴趣&#xff0c;欢迎订阅&#x1f6a9; > &#x1f38a;个人主页:[小编的个人主页])小编的个人主页 > &#x1f380; &#x1f389;欢迎大家点赞&#x1f44d;收藏⭐文章 > ✌️ &#x1f91e; &#x1…

ElasticSearch备考 -- Cross cluster replication(CCR)

一、题目 操作在cluster1&#xff08;local&#xff09;中操作索引task&#xff0c;复制到cluster2&#xff08;remote&#xff09;中 二、思考 CCR 我们可以对标MySQL 理解为为主从&#xff0c;后者备份。主节点负责写入数据&#xff0c;从/备节点负责同步时主节点的数据。 …

IDEA在编译时: java: 找不到符号符号: 变量 log

一、问题 IDEA在编译的时候报Error:(30, 17) java: 找不到符号符号: 变量 log Error:(30, 17) java: 找不到符号 符号: 变量 log 位置: 类 com.mokerson.rabbitmq.config.RabbitMqConfig 二、解决方案 背景&#xff1a;下载其他同事代码时&#xff0c;第一次运行&#xff0c…

一文熟悉新版llama.cpp使用并本地部署LLAMA

0. 简介 最近是快到双十一了再给大家上点干货。去年我们写了一个大模型的系列&#xff0c;经过一年&#xff0c;大模型的发展已经日新月异。这一次我们来看一下使用llama.cpp这个项目&#xff0c;其主要解决的是推理过程中的性能问题。主要有两点优化&#xff1a; llama.cpp …

[翻译]ANSI X9.24-3-2017

目录 1 目的 2 范围 2.1 应用 3 参考文献 4 术语和定义 4.1 高级加密标准&#xff08;AES&#xff09; 4.2 AES 4.3 算法 4.4 ANSI 4.5 基础推导密钥(BDK) 4.6 BDK 4.7 BDK ID 4.8 加密密钥 4.9 加密密钥同步 4.10 密码强度 4.11 派生 4.12 派生标识符(ID) 4…

使用 GitHub Actions 部署到开发服务器的详细指南

使用 GitHub Actions 部署到开发服务器的详细指南 在本篇博客中&#xff0c;我们将介绍如何使用 GitHub Actions 实现自动化部署&#xff0c;将代码从 GitHub 仓库的 dev 分支自动部署到开发服务器。通过这种方式&#xff0c;可以确保每次在 dev 分支推送代码时&#xff0c;服…

Docker安装部署RabbitMQ

1. Docker环境准备 1.1 安装Docker 在开始Docker安装部署RabbitMQ之前&#xff0c;确保您的系统环境已经满足Docker的运行要求。以下是在不同操作系统上安装Docker的步骤和命令行演示。 对于Linux系统 在基于Debian的系统&#xff08;如Ubuntu&#xff09;上&#xff0c;您…

UniAPP u-popup 禁止背景滑动

增加class .NoScroll {overflow: hidden;position: fixed; }在外层div上增加该class判断条件

ubuntu 24.04运行chattts时cuda安装错误原因分析

使用ubuntu 24.04&#xff0c;按照2noise/ChatTTS官方流程安装依赖时报错。ChatTTShttps://github.com/2noise/ChatTTS 这是因为cuda版本不对&#xff0c;ChatTTS目前的版本&#xff0c;要求支持cuda 12.4及以上&#xff0c;但是如果nvidia显卡驱动版本较老&#xff0c;无法支…

spring-security(记住密码,CSRF)

注册PersistentTokenRepository PersistentTokenRepository实现类 InMemoryTokenRepositoryImpl基于内存实现 JdbcTokenRepositoryImpl基于数据库实现 基于内存实现 Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { Bean publi…

iOS问题记录 - 503 Service Temporarily Unavailable

文章目录 前言开发环境问题描述问题分析解决方案最后 前言 最近有个项目经历了大改动&#xff0c;本地测试没什么问题&#xff0c;于是准备通过打包机打包用于内部测试的包&#xff0c;然后问题就来了。 开发环境 Xcode: 16.1Fastlane: 2.219.0 问题描述 问题出在登录苹果…

linux-vlan

# VLAN # 1.topo # 2.创建命名空间 ip netns add ns1 ip netns add ns2 ip netns add ns3 # 3.创建veth设备 ip link add ns1-veth0 type veth peer name ns21-veth0 ip link add ns3-veth0 type veth peer name ns23-veth0 # 4.veth设备放入命名空间,启动接口 ip link set n…