网络通信与并发编程(二)基于tcp的套接字、基于udp的套接字、粘包现象

devtools/2024/10/19 20:03:51/

基于tcp的套接字

文章目录

  • 基于tcp的套接字
  • 一、套接字的工作流程
  • 二、基于tcp的套接字通信
  • 三、基于udp的套接字通信
  • 四、粘包现象

一、套接字的工作流程

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。

在这里插入图片描述

服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。

二、基于tcp的套接字通信

基于上面的套接字工作原理,我们可以用python编写处如下的一段代码:

python">#服务端 
import socket#socket.AF_INET表示套接字,socket.SOCK_STREAM表示tcp,tcp也称为流式协议
#创建套接字对象
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #绑定服务端ip和端口
phone.bind(('127.0.0.1',8081))#开始监听,listen表示半连接池,限制的是请求数
phone.listen(5)# 连接循环,服务端需要一直开启等待客户端的连接(连接循环)
while True: #收到客户端的请求,通过三次握手与四次挥手建立通信通道#conn是建立的通信通道,client_addr是客户端的信息#当没有建立链接请求时,服务端会一直停在phone.accept()处conn,client_addr=phone.accept()#通信通道建立完成,与客户端持续通信(通信循环)while True: try:print('服务端正在收数据...')#为了降低内存的压力,需要限制每次接收的字节数#当没有接收到客户端的消息时,服务端会一直停在conn.recv(1024)处data=conn.recv(1024) #linux中客户端中断后服务端会接收空字符,此时需要跳出通信循环if len(data) == 0:break print('来自客户端的数据',data)#回复客户端的信息conn.send(data.upper())#windows中客户端连接中断会报错,需要用try推出通信循环except ConnectionResetError:break#关闭通信通道,服务端准备与下一个客户端建立通信链接conn.close()#关闭套接字对象
phone.close()
python">#客户端
import socketphone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#客户端不需要绑定ip和端口,只需向服务端的ip和端口发送请求
phone.connect(('127.0.0.1',8080)) # 指定服务端ip和端口#通信循环
while True: msg=input('>>: ').strip()#套接字中无法发送空字符if len(msg) == 0:continuephone.send(msg.encode('utf-8'))data=phone.recv(1024)print(data)phone.close()

如果在重启服务端的过程中出现如下的情况表示服务端仍在四次挥手的time_wait状态(服务端进程依然在后台运行),此时可以采取两种方法。

  • 修改绑定给服务端的端口号
  • 在绑定服务端的ip和端口前加上phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)

在这里插入图片描述

udp_87">三、基于udp的套接字通信

基于udp协议编写的套接字如下:

python">#服务端
import socket#socket.SOCK_DGRAM表示udp协议,udp是数据报协议
server=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) 
server.bind(('127.0.0.1',8080))#udp协议不需要建立通信通道,因此它是不可靠的通信协议
#简单来说tcp是一对一的收发消息,一个客户端结束才会回应其他客户端
#udp是一对多的收发消息,由客户端发送消息时服务端就会回应
while True:#接收客户端的消息data,client_addr=server.recvfrom(1024)print('===>',data,client_addr)#发送消息给客户端,由于没有链接通道,发送信息需要带上客户端的ip和端口信息server.sendto(data.upper(),client_addr)server.close()
python">#客户端
import socketclient=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) while True:msg=input('>>: ').strip()#向服务端的ip和端口发送信息client.sendto(msg.encode('utf-8'),('127.0.0.1',8080))data,server_addr=client.recvfrom(1024)print(data)client.close()

四、粘包现象

将服务端的代码作如下的修改:

python">import socket,subprocessphone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(('127.0.0.1',8080))phone.listen(5)while True:conn,client_addr=phone.accept()while True:try:data=conn.recv(1024)if len(data) == 0: breaka=subprocess.Popen(data.decode('utf-8'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)res=a.stdout.read()conn.send(res)except ConnectionResetError:breakconn.close()
phone.close()

我们尝试在客户端通过指令tasklist查看服务端的进程列表,第一次客户端向服务端发送tasklist命令返回如下的结果:
映像名称 PID 会话名 会话# 内存使用
========================= ======== ================ =========== ============
System Idle Process 0 Services 0 8 K
System 4 Services 0 12 K
Registry 296 Services 0 26,600 K
smss.exe 892 Services 0 528 K
csrss.exe 1124 Services 0 2,840 K
wininit.exe 1236 Services 0 3,400 K
services.exe 1308 Services 0 8,912 K
lsass.exe 1332 Services 0 20,172 K
svchost.exe 1460 Services 0 29,432 K
fontdrvhost.exe 1484 Services 0 104 K
WUDFHost.exe 1536 Services 0 2,952 K
svchost.

第二次当客户端向服务端发送ping www.baidu.com时会发现返回的结果依然是客户端的进程列表:
exe 1596 Services 0 15,092 K
svchost.exe 1640 Services 0 6,416 K
WUDFHost.exe 1764 Services 0 21,224 K
svchost.exe 1876 Services 0 3,868 K
svchost.exe 1884 Services 0 7,436 K
svchost.exe 1904 Services 0 4,420 K
svchost.exe 1940 Services 0 9,876 K
svchost.exe 1948 Services 0 7,880 K
svchost.exe 2036 Services 0 7,128 K
svchost.exe 1304 Services 0 15,372 K
svchost.exe 2128 Services 0 4,932 K
svchost.exe 2140 Services 0 6,348 K
svchost.exe 2148 Services 0 7,032 K
svchost.exe

这是怎么回事呢?我们知道tcp协议是流式协议,也就是说基于tcp协议发送消息时,服务端套接字会把需要发送的消息给自己的操作系统,而自己的操作系统将这些消息一段一段发送给客户端的操作系统,由于是一段一段的发送,客户端无法判断一条消息的始末,所以客户端套接字每次只从操作系统中取字节数限制字节的消息,当发送的消息量过大时,只有一部分消息会被接收并打印到终端上,剩余的消息依然在客户端的操作系统中。当我们再次向服务端发送消息接收消息以后,套接字会先接收上次没有接受完的消息,再接受新的消息,这就产生了粘包现象。
另外如果tcp多次短间隔的发送消息,发送端的套接字会将这些消息并再一起发送,这样会发送接受方的另一种粘包问题。
这时候肯定有人要说如果我们不限制套接字每次接受的字节数是不是就能解决这个问题呢?问题是如果我们接受的是一个很大的内容,比如50g,套接字会将接受的消息全部读入内存,这就会引发内存爆满的情况,显然这种解决方式是不可取的。
udp协议是数据报式的协议,也就是说udp每次收发消息都是以一个数据报为单位的(套接字会给每次的消息加上消息头),每次接受消息都会一次取完。如果服务端接收的字节限制比接收内容小时,多出来的内容会丢失(windows中会报错),而不会发送粘包的问题。由于udp的消息都含有消息头,所以即便是短时间内发送多次消息,也不会发生上面说到的第二种粘包问题。
tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头。


http://www.ppmy.cn/devtools/127092.html

相关文章

RTKLIB学习记录【postpos、execses_b、execses_r】

本文主要记录对RTKLIB源码中postpos、execses_b、execses_r 函数的源码解读,不涉及其中的天线、星历等文件读取的内容,且为个人理解,如果有误,欢迎交流讨论。 一、postpos 函数部分 /rxn2rtkp函数 → postpos函数传递参数&#x…

PyTorch深度学习入门汇总

PyTorch 是由 Facebook 的人工智能研究小组开发的深度学习框架,可以基于PyTorch开发和训练各种深度学习模型。自 2016 年问世以来,PyTorch 因其灵活性和易用性而受到深度学习从业者的极大关注。 汇总目录 基于conda包的环境创建、激活、管理与删除 Pyt…

DS几大常见排序讲解和实现(中)(14)

文章目录 前言一、希尔排序( 缩小增量排序 )基本思想实现思路时间空间复杂度分析总结 二、选择排序基本思想实现思路时间空间复杂度分析总结 三、堆排序四、冒泡排序基本思想实现思路总结 五、归并排序基本思想实现思路总结 六、计数排序基本思想总结 总结 前言 承上启下&#…

深度学习的高级应用

1. 计算机视觉(Computer Vision) 图像分类:如经典的ImageNet分类任务。通过卷积神经网络(CNNs),深度学习可以识别和分类图片中的物体。目标检测:如YOLO、Faster R-CNN等算法,用于识…

HALCON数据结构之字符串

1.1 String字符串的基本操作 *将数字转换为字符串或修改字符串 *tuple_string (T, Format, String) //HALCON语句 *String: T $ Format //赋值操作*Format string 由以下四个部分组成&#xff1a; *<flags><field width>.<precision><conversion 字符&g…

5G智能终端:低空经济崛起的隐形翅膀!

随着5G技术的快速发展&#xff0c;低空经济正迎来前所未有的发展机遇。5G智能终端在这一领域的应用&#xff0c;不仅极大地提升了操作体验&#xff0c;还为多个行业带来了革命性的变革。在无人机领域&#xff0c;传统的遥控器正逐渐被手机或平板遥控所取代&#xff0c;这些设备…

鸿蒙Next设备上的ProxyMan、Charles网络抓包配置教程

一、Proxyman配置 1. 导出证书 ProxyMan菜单栏依次点击 证书—>导出—>根证书为PEM 然后保存.pem文件传送(如hdc命令<下文会有介绍>)至鸿蒙Next设备存储任意位置 2. 安装证书 系统设置搜索“证书”&#xff0c;结果列表中点击“证书与凭据” 点击“从存储设备…

【VUE3】子组件中的el-upload 选择图片点击取消 会触发父组件emit cancel方法

源代码如下&#xff1a; <createOriginalitycancel"cancelCreate"></createOriginality> 问题是&#xff1a;每次上传图片点击取消 总会触发cancelCreate方法 经反复测试 发现问题应该是el-upload 点击取消的时候触发了我的组件的cancel方法 解决方式…