【脚本开发】第一节 Python Socket通信

通信协议概述

基本概念

通信协议是指双方实体完成通信或服务所必须遵循的规则和约定
通过通信信道和设备互连起来的多个不同地理位置的数据通信系统,要使其能协同工作实现信息交换和资源共享,它们之间必须具有共同的语言。交流什么、怎样交流及何时交流,都必须遵循某种互相都能接受的规则。这个规则就是通信协议。

TCP/IP参考模型

网络模型一般有七层模型、五层模型和四层模型,其中四层模型指的是TCP/IP参考模型(TCP/IP协议组)。
四层模型从下往上依次是:网络接口层、网络层、运输层和应用层。

所属层 相关协议
应用层 DNS、FTP、TELNET、HTTP、SMTP等
传输层 TCP、UDP
网络层 IP、ICMP、ARP等
网络接口层 协议内容传输的媒介

TCP协议

原理

传输控制协议(TCP,Transmission Control Protocol)是为了在不可靠的互联网络上提供可靠的端到端字节流而专门设计的一个传输协议。

互联网络与单个网络有很大的不同,因为互联网络的不同部分可能有截然不同的拓扑结构、带宽、延迟、数据包大小和其他参数。TCP的设计目标是能够动态地适应互联网络的这些特性,而且具备面对各种故障时的健壮性。

不同主机的应用层之间经常需要可靠的、像管道一样的连接,但是IP层不提供这样的流机制,而是提供不可靠的包交换。

应用层向TCP层发送用于网间传输的、用8位字节表示的数据流,然后TCP把数据流分区成适当长度的报文段(通常受该计算机连接的网络的数据链路层的最大传输单元(MTU)的限制)。之后TCP把结果包传给IP层,由它来通过网络将包传送给接收端实体的TCP层。TCP为了保证不发生丢包,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已成功收到的包发回一个相应的确认(ACK);如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据包就被假设为已丢失将会被进行重传。TCP用一个校验和函数来检验数据是否有错误;在发送和接收时都要计算校验和。

每台支持TCP的机器都有一个TCP传输实体。TCP实体可以是一个库过程、一个用户进程,或者内核的一部分。在所有这些情形下,它管理TCP流,以及与IP层之间的接口。TCP传输实体接受本地进程的用户数据流,将它们分割成不超过64KB(实际上去掉IP和TCP头,通常不超过1460数据字节)的分段,每个分段以单独的IP数据报形式发送。当包含TCP数据的数据报到达一台机器时,它们被递交给TCP传输实体,TCP传输实体重构出原始的字节流。为简化起见,我们有时候仅仅用“TCP”来代表TCP传输实体(一段软件)或者TCP协议(一组规则)。根据上下文语义你应该能很消楚地推断出其实际含义。例如,在“用户将数据交给TCP”这句话中,很显然这里指的是TCP传输实体。 [2]

IP层并不保证数据报一定被正确地递交到接收方,也不指示数据报的发送速度有多快。正是TCP负责既要足够快地发送数据报,以便使用网络容量,但又不能引起网络拥塞:而且,TCP超时后,要重传没有递交的数据报。即使被正确递交的数据报,也可能存在错序的问题,这也是TCP的责任,它必须把接收到的数据报重新装配成正确的顺序。简而言之,TCP必须提供可靠性的良好性能,这正是大多数用户所期望的而IP又没有提供的功能。

特点

TCP是一种面向广域网的通信协议,目的是在跨越多个网络通信时,为两个通信端点之间提供一条具有下列特点的通信方式:

(1)基于流的方式;

(2)面向连接;

(3)可靠通信方式;

(4)在网络状况不佳的时候尽量降低系统由于重传带来的带宽开销;

(5)通信连接维护是面向通信的两个端点的,而不考虑中间网段和节点。

为满足TCP协议的这些特点,TCP协议做了如下的规定:

  • 数据分片:在发送端对用户数据进行分片,在接收端进行重组,由TCP确定分片的大小并控制分片和重组;

  • 到达确认:接收端接收到分片数据时,根据分片数据序号向发送端发送一个确认;

  • 超时重发:发送方在发送分片时启动超时定时器,如果在定时器超时之后没有收到相应的确认,重发分片;

  • 滑动窗口:TCP连接每一方的接收缓冲空间大小都固定,接收端只允许另一端发送接收端缓冲区所能接纳的数据,TCP在滑动窗口的基础上提供流量控制,防止较快主机致使较慢主机的缓冲区溢出;

  • 失序处理:作为IP数据报来传输的TCP分片到达时可能会失序,TCP将对收到的数据进行重新排序,将收到的数据以正确的顺序交给应用层;

  • 重复处理:作为IP数据报来传输的TCP分片会发生重复,TCP的接收端必须丢弃重复的数据;

  • 数据校验:TCP将保持它首部和数据的检验和,这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到分片的检验和有差错,TCP将丢弃这个分片,并不确认收到此报文段导致对端超时并重发。

三次握手

所谓的三次握手即TCP连接的建立。这个连接必须是一方主动打开,另一方被动打开的。以下为客户端主动发起连接的图解:
在这里插入图片描述
握手之前主动打开连接的客户端结束CLOSED阶段,被动打开的服务器端也结束CLOSED阶段,并进入LISTEN阶段。随后开始“三次握手”:
(1)首先客户端向服务器端发送一段TCP报文,其中:
标记位为SYN,表示“请求建立新连接”;序号为Seq=X(X一般为1);随后客户端进入SYN-SENT阶段 。
(2)服务器端接收到来自客户端的TCP报文之后,结束LISTEN阶段。并返回一段TCP报文,其中:标志位为SYN和ACK,表示“确认客户端的报文Seq序号有效,服务器能正常接收客户端发送的数据,并同意创建新连接”(即告诉客户端,服务器收到了你的数据);序号为Seq=y;确认号为Ack=x+1,表示收到客户端的序号Seq并将其值加1作为自己确认号Ack的值;随后服务器端进入SYN-RCVD阶段。
(3)客户端接收到来自服务器端的确认收到数据的TCP报文之后,明确了从客户端到服务器的数据传输是正常的,结束SYN-SENT阶段。并返回最后一段TCP报文。其中:标志位为ACK,表示“确认收到服务器端同意连接的信号”(即告诉服务器,我知道你收到我发的数据了);序号为Seq=x+1,表示收到服务器端的确认号Ack,并将其值作为自己的序号值;确认号为Ack=y+1,表示收到服务器端序号Seq,并将其值加1作为自己的确认号Ack的值;随后客户端进入ESTABLISHED阶段。服务器收到来自客户端的“确认收到服务器数据”的TCP报文之后,明确了从服务器到客户端的数据传输是正常的。结束SYN-SENT阶段,进入ESTABLISHED阶段。

在客户端与服务器端传输的TCP报文中,双方的确认号Ack和序号Seq的值,都是在彼此Ack和Seq值的基础上进行计算的,这样做保证了TCP报文传输的连贯性。一旦出现某一方发出的TCP报文丢失,便无法继续"握手",以此确保了"三次握手"的顺利完成。
此后客户端和服务器端进行正常的数据传输。这就是“三次握手”的过程。

四次挥手

所谓的四次挥手即TCP连接的释放(解除)。连接的释放必须是一方主动释放,另一方被动释放。以下为客户端主动发起释放连接的图解:

(1)首先客户端想要释放连接,向服务器端发送一段TCP报文,其中:

标记位为FIN,表示“请求释放连接“;序号为Seq=U;随后客户端进入FIN-WAIT-1阶段,即半关闭阶段。并且停止在客户端到服务器端方向上发送数据,但是客户端仍然能接收从服务器端传输过来的数据。注意:这里不发送的是正常连接时传输的数据(非确认报文),而不是一切数据,所以客户端仍然能发送ACK确认报文。

(2)服务器端接收到从客户端发出的TCP报文之后,确认了客户端想要释放连接,随后服务器端结束ESTABLISHED阶段,进入CLOSE-WAIT阶段(半关闭状态)并返回一段TCP报文,其中:

标记位为ACK,表示“接收到客户端发送的释放连接的请求”;序号为Seq=V;确认号为Ack=U+1,表示是在收到客户端报文的基础上,将其序号Seq值加1作为本段报文确认号Ack的值;随后服务器端开始准备释放服务器端到客户端方向上的连接。客户端收到从服务器端发出的TCP报文之后,确认了服务器收到了客户端发出的释放连接请求,随后客户端结束FIN-WAIT-1阶段,进入FIN-WAIT-2阶段

前"两次挥手"既让服务器端知道了客户端想要释放连接,也让客户端知道了服务器端了解了自己想要释放连接的请求。于是,可以确认关闭客户端到服务器端方向上的连接了

(3)服务器端自从发出ACK确认报文之后,经过CLOSED-WAIT阶段,做好了释放服务器端到客户端方向上的连接准备,再次向客户端发出一段TCP报文,其中:

标记位为FIN,ACK,表示“已经准备好释放连接了”。注意:这里的ACK并不是确认收到服务器端报文的确认报文。序号为Seq=W;确认号为Ack=U+1;表示是在收到客户端报文的基础上,将其序号Seq值加1作为本段报文确认号Ack的值。随后服务器端结束CLOSE-WAIT阶段,进入LAST-ACK阶段。并且停止在服务器端到客户端的方向上发送数据,但是服务器端仍然能够接收从客户端传输过来的数据。

(4)客户端收到从服务器端发出的TCP报文,确认了服务器端已做好释放连接的准备,结束FIN-WAIT-2阶段,进入TIME-WAIT阶段,并向服务器端发送一段报文,其中:

标记位为ACK,表示“接收到服务器准备好释放连接的信号”。序号为Seq=U+1;表示是在收到了服务器端报文的基础上,将其确认号Ack值作为本段报文序号的值。确认号为Ack=W+1;表示是在收到了服务器端报文的基础上,将其序号Seq值作为本段报文确认号的值。随后客户端开始在TIME-WAIT阶段等待2MSL

服务器端收到从客户端发出的TCP报文之后结束LAST-ACK阶段,进入CLOSED阶段。由此正式确认关闭服务器端到客户端方向上的连接。

客户端等待完2MSL之后,结束TIME-WAIT阶段,进入CLOSED阶段,由此完成“四次挥手”。

后“两次挥手”既让客户端知道了服务器端准备好释放连接了,也让服务器端知道了客户端了解了自己准备好释放连接了。于是,可以确认关闭服务器端到客户端方向上的连接了,由此完成“四次挥手”。

与“三次挥手”一样,在客户端与服务器端传输的TCP报文中,双方的确认号Ack和序号Seq的值,都是在彼此Ack和Seq值的基础上进行计算的,这样做保证了TCP报文传输的连贯性,一旦出现某一方发出的TCP报文丢失,便无法继续"挥手",以此确保了"四次挥手"的顺利完成。

UDP 协议

原理

UDP 是User Datagram Protocol的简称, 中文名是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联) 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,IETF RFC 768 [1] 是UDP的正式规范。UDP在IP报文的协议号是17。

UDP协议与TCP协议一样用于处理数据包,在OSI模型中,两者都位于传输层,处于IP协议的上一层。UDP有不提供数据包分组、组装和不能对数据包进行排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。UDP用来支持那些需要在计算机之间传输数据的网络应用。包括网络视频会议系统在内的众多的客户/服务器模式的网络应用都需要使用UDP协议。UDP协议从问世至今已经被使用了很多年,虽然其最初的光彩已经被一些类似协议所掩盖,但即使在今天UDP仍然不失为一项非常实用和可行的网络传输层协议。

许多应用只支持UDP,如:多媒体数据流,不产生任何额外的数据,即使知道有破坏的包也不进行重发。当强调传输性能而不是传输的完整性时,如:音频和多媒体应用,UDP是最好的选择。在数据传输时间很短,以至于此前的连接过程成为整个流量主体的情况下,UDP也是一个好的选择。

特点

UDP是一个无连接协议,传输数据之前源端和终端不建立连接,当它想传送时就简单地去抓取来自应用程序的数据,并尽可能快地把它扔到网络上。在发送端,UDP传送数据的速度仅仅是受应用程序生成数据的速度、计算机的能力和传输带宽的限制;在接收端,UDP把每个消息段放在队列中,应用程序每次从队列中读一个消息段。

由于传输数据不建立连接,因此也就不需要维护连接状态,包括收发状态等,因此一台服务机可同时向多个客户机传输相同的消息。

UDP信息包的标题很短,只有8个字节,相对于TCP的20个字节信息包而言UDP的额外开销很小。

吞吐量不受拥挤控制算法的调节,只受应用软件生成数据的速率、传输带宽、源端和终端主机性能的限制。

UDP是面向报文的。发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付给IP层。既不拆分,也不合并,而是保留这些报文的边界,因此,应用程序需要选择合适的报文大小。

虽然UDP是一个不可靠的协议,但它是分发信息的一个理想协议。例如,在屏幕上报告股票市场、显示航空信息等等。UDP也用在路由信息协议RIP(Routing Information Protocol)中修改路由表。在这些应用场合下,如果有一个消息丢失,在几秒之后另一个新的消息就会替换它。UDP广泛用在多媒体应用中。

协议比较

TCP 是面向连接的传输控制协议,而UDP 提供了无连接的数据报服务;TCP 具有高可靠性,确保传输数据的正确性,不出现丢失或乱序;UDP 在传输数据前不建立连接,不对数据报进行检查与修改,无须等待对方的应答,所以会出现分组丢失、重复、乱序,应用程序需要负责传输可靠性方面的所有工作;

UDP 具有较好的实时性,工作效率较 TCP 协议高;UDP 段结构比 TCP 的段结构简单,因此网络开销也小。TCP 协议可以保证接收端毫无差错地接收到发送端发出的字节流,为应用程序提供可靠的通信服务。对可靠性要求高的通信系统往往使用 TCP 传输数据。比如 HTTP 运用 TCP 进行数据的传输。

协议通信

socket通信

工作图示:
![[Pasted image 20220303101552.png]]

服务器端:初始化Scoket–>绑定端口–>端口监听–>调用accept阻塞–>等待连接
客户端: 初始化Socket–>连接服务端–>数据交互–>通信结束

UDP Socket 简单通信

# -*- coding: utf-8 -*-  
# @Time    : 2022/3/3 10:33 上午  
# @Author  : Qixi  
# @File    : Client.py  
  
import socket  
# 初始化客户端  
client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)  
# 和服务端保持一致  
client.sendto(input("输入:").encode('GBK'),('localhost',1234))  
recv_data,ip_port = client.recvfrom(1024)  
print("来自服务端的消息:{} ".format(recv_data.decode('GBK')))  
print("服务端IP即端口",ip_port)  
  
client.close()
# -*- coding: utf-8 -*-  
# @Time    : 2022/3/3 10:21 上午  
# @Author  : Qixi  
# @File    : Server.py  
  
# 1、导入模块  
import socket  
# 2、创建套接字  
server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)  
# 3、绑定服务端的IP和端口  
server.bind(("localhost",1234))  
# 4、接收客户端的信息  
recv_data,ip_port = server.recvfrom(1024)  
# 5、解码接收到的信息并显示  
recv_str = recv_data.decode("GBK")  
print("接收到客户端发送的数据: {}".format(recv_str))  
print("客户端IP及端口",ip_port)  
# 6、回复信息给客户端  
server.sendto("服务端收到信息啦!".encode("GBK"),ip_port)  
# 7、关闭套接字  
server.close()

在这里插入图片描述

TCP Socket 实现聊天

# -*- coding: utf-8 -*-  
# @Time    : 2022/3/3 10:33 上午  
# @Author  : Qixi  
# @File    : Client.py  
  
import socket  
# 初始化客户端  
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)  
# 和服务端保持一致  
client.connect(('localhost',1234))  
while True:  
    msg = input("输入待发送数据: ")  
    client.send(msg.encode('utf-8'))  
    data = client.recv(1024)  
    print("收到来自服务器的数据:",data.decode())  
client.close()
# -*- coding: utf-8 -*-  
# @Time    : 2022/3/3 10:21 上午  
# @Author  : Qixi  
# @File    : Server.py  
  
import socket  
# 初始化服务端  
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)  
# 绑定监听端口  
server.bind(('localhost',1234))  
server.listen(5)  
while True:  
    conn,addr = server.accept()  
    # print(conn,addr)  
 print("通信成功连接!!")  
    while True:  
        try:  
            data = conn.recv(1024)  
            print("收到来自客户端的数据:{}".format(data.decode()))  
            sdata = input("输入:")  
            conn.send(sdata.encode('utf-8'))  
        except Exception as e:  
            print("连接关闭!")  
            break  
 conn.close()

在这里插入图片描述

TCP Socket 实现命令执行

# -*- coding: utf-8 -*-  
# @Time    : 2022/3/3 10:33 上午  
# @Author  : Qixi  
# @File    : Client.py  
  
import socket  
import os  
# 初始化客户端  
import sys  
  
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)  
# 和服务端保持一致  
client.connect(('localhost',1234))  
while True:  
    data = client.recv(1024)  
    data = data.decode('utf-8')  
    print("收到来自服务器命令:",data)  
    if data == "exit":  
        sys.exit(0)  
    else:  
        msg = os.popen(data).read()  
        client.send(msg.encode('utf-8'))  
client.close()
# -*- coding: utf-8 -*-  
# @Time    : 2022/3/3 10:21 上午  
# @Author  : Qixi  
# @File    : Server.py  
  
import socket  
# 初始化服务端  
import sys  
  
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)  
# 绑定监听端口  
server.bind(('localhost',1234))  
server.listen(5)  
while True:  
    conn,addr = server.accept()  
    # print(conn,addr)  
 print("主机上线啦!!")  
    while True:  
        try:  
            sdata = input("输入命令:")  
            if sdata == 'quit':  
                sys.exit(0)  
            else:  
                conn.send(sdata.encode('utf-8'))  
                data = conn.recv(1024)  
                print("收到来自客户端的数据:{}".format(data.decode('utf-8')))  
        except Exception as e:  
            print("连接关闭!")  
            break  
conn.close()

运行效果:支持基本命令回显,支持退出。服务端输入命令,客户端接受命令并执行
在这里插入图片描述在这里插入图片描述

TCP/UDP Socket 编程区别

服务端

  • UDP无需开启监听
  • TCP使用accept方法接收信息,UDP使用recvfrom方法接收信息
  • TCP步骤较为复杂

客户端

  • UDP socket属性可选
  • TCP发送数据使用 send、recv或read、write方法
  • UDP 使用 sendto方法 发送数据

参考

https://baike.baidu.com/item/UDP
https://baike.baidu.com/item/TCP/33012
https://baijiahao.baidu.com/s?id=1654225744653405133
https://www.runoob.com/python/python-socket.html
https://www.cnblogs.com/zhangyingai/p/7097922.html
https://www.cnblogs.com/zhangyingai/p/7097922.html
https://www.cnblogs.com/LUO77/p/5801977.html

猜你喜欢

转载自blog.csdn.net/weixin_54430466/article/details/123270101
今日推荐