这里写目录标题
- 一、OSI7
- 二、TCP和UDP协议
- (一)UDP和TCP示例代码
- 1、UDP
- 2、TCP
- (二)TCP三次握手和四次挥手
- 三、粘包
- 1、实际案例1
- 2、实际案例2
- 3、实际案例3
- 四、阻塞和非阻塞
- 五、IO多路复用
一、OSI7
在电脑和电脑之间进行数据传输时,往往将数据包装为7层,分别为数据层、应用层、表示层、会话层、传输层、网络层、数据链路层、物理层。
- 应用层:规定数据的格式;
- 表示层:对应用层数据的编码、压缩(解压)、分块、加密(解密)等任务;
- 会话层:负责与目标建立、中断链接;
- 传输层:建立端口到端口的通信,其实就是确定双方的端口信息;
- 网络层:标记目标IP信息;
- 数据链路层:对数据进行分组并设置源和目标mac地址。
- 物理层:将二进制数据在物理媒体上传输。
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('110.242.68.3', 80)) #向服务端发送数据包
key = "你好"
#应用层,将数据打包为特定的格式
content = "GET /s?wd={} http1.1\r\nHost:www.baidu.com\r\n\r\n".format(key)
#表示层,对应用层数据的编码、压缩(解压)、分块、加密(解密)等任务
content = content.encode('utf-8')
client.sendall(content) #会话层,与膜表建立链接,同时也确定了双方的端口信息。标记目标IP的信息,设置源和目标电脑的MAC地址
result = client.recv(8196)
print(result.decode('utf-8'))
#会话层&传输层
client.close()
二、TCP和UDP协议
协议:规定 连接、收发数据的一些规定。
在OSI的 传输层 中,除了定义端口信息以外,常见的还可以指定UDP或TCP的协议,协议不同,链接和传输数据的细节会不同。
UDP(User Data Protocal)用户数据协议,是一个无连接的简单的面向数据包的传输层协议,客户端和服务端不需要提前建立连接,客户端可以直接向服务端发送数据即可。所以UDP是不可靠的,它只把应用程序传给IP层,但是不能保证他们能够到达目的地。由于不许需要提前在客户和服务器之间建立连接,所以没有超时重发的机制,所以传输速度较快。常见的有:语音通话、视频通话、实时游戏画面
TCP(Transcation Control Protocal)传输控制协议,是面向连接的协议,也就是说在收发数据之前,必须和对方建立可靠的连接,然后再收发数据,这种情况下会确保客户端收到服务端的信息,如果未收到“已经接受到”的信息,服务端会再次发送信息。常见的有:网站、手机app数据获取。
(一)UDP和TCP示例代码
1、UDP
服务端
import socket
socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket.bind(('127.0.0.1', 8002))
while True:
#没有accpet了
data, (host, port) = socket.recvfrom(1024) #接收数据data,host代表IP地址,port为端口信息
print(data, host, port)
socket.sendto("好的".encode('utf-8'), (host, port))
客户端
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#不需要连接
while True:
text = input("请输入要发送的内容:")
if text.upper() == 'Q':
break
client.sendto(text.encode('utf-8'), ('127.0.0.1', 8002))
data, (host, port) = client.recvfrom(1024)
print(data.decode('utf-8'))
client.close()
2、TCP
服务端
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('127.0.0.1', 8001))
sock.listen(5)
while True:
#2、等待,有人来连接就执行(阻塞)
conn, addr = sock.accept()
#3、等待,连接着发送信息(阻塞)
client_data = conn.recv(1024)
print(client_data)
#4、给连接者回复信息
conn.sendall(b'hello word')
#5、关闭连接
sock.close()
客户端
import socket
#1、向指定IP发送连接申请
client = socket.socket()
client.connect(('127.0.0.1', 8001))
#2、连接成功后,发送信息
client.sendall(b"hello")
#3、等待,消息的回复(阻塞)
reply = client.recv(1024)
print(reply)
#4、关闭连接
client.close()
(二)TCP三次握手和四次挥手
网络中双方基于TCP进行通信,必须经过:
创建时,客户端和服务端进行三次握手
source prot是源端口,destination port是目标端口;sequence number是序列号,随机分配一个数字;acknowledgment number也是级分配的数值。
- 在创建连接时,客户端随机给出了一个序列号,seq=100;
- 当服务端收到信号时,给出seq=300,ack是在接收客户端seq100的基础上加1,最终ack=101;
- 当客户端接收到服务端的信号时,seq=101,ack实在服务端seq的基础上加1,ack=301。
- 在收发数据的过程中,只有有数据的传送就会应答(ack),如果没有ack,那么内部会尝试重复发送。
关闭连接时,客户端和服务端需要进行4次挥手:
三、粘包
当两台电脑进行收发数据时,其实不是直接将数据传输给对方。
- 对于发送者来说,执行sendall/send发送信息时,是将数据先发送给自己网卡的 写缓冲区,再由缓冲区将数据发送到对方网卡的 读缓冲区。
- 对于接收者来说,执行recv接收信息时,是从自己网卡的读缓冲区获取数据。
所以,如果发送者连续快速得发送两条信息,接收者在读取时会认为是一条信息,即:两个数据包粘在了一起。
#客户端
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8001))
client.sendall('alex在吃'.encode('utf-8'))
client.sendall('翔'.encode('utf-8'))
client.close()
#服务端
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('127.0.0.1', 8001))
sock.listen(5)
conn, addr = sock.accept()
client_data = sock.recv(1024)
print(client_data.decode('utf-8'))
conn.close()
sock.close()
如何解决粘包的问题?
每次发送信息时,都将信息划分为 头部(固定字节长度) 和 数据 两部分。例如:头部,用4个字节来表示后面的长度。
- 发送数据,先发送数据的长度,再发送数据(或拼接起来再发送)。
- 接收数据,先读4个字节就可以知道自己这个数据包中的数据长度,再根据长度读取数据。
头部需要一个数字并固定为4个字节,这个功能可以借助struct包来实现:
- struck把任意长度数字转化成具有固定4个字节长度的字节流;
- unpack把4个字节值恢复成原来的数字,返回最终的是元组。
import struct
v1 = struct.pack('i', 199)
# i => int 要转化的当前的数据是整型
print(v1) #b'\xc7\x00\x00\x00'
for item in v1:
print(item, bin(item))
tup = struct.unpack("i", v1)
print(tup) #(199,)
print(tup[0]) #199
解决黏包场景:
应用场景在实时通讯时,需要阅读此次发的消息是什么
不需要解决黏包场景:
下载或者上传文件的时候,最后要把包都结合在一起,黏包无所谓
1、实际案例1
客户端
import socket
import struct
client = socket.socket()
client.connect(("127.0.0.1", 9000))
# 处理收发数据的逻辑
strvar = input("请输入你要发送的数据")
mes = strvar.encode('utf-8')
length_ = len(mes)
print(length_)
#18
res = struct.pack("i", length_)
# 第一次发送的是字节流
client.sendall(res)
# 第二次发送的是真实的数据
client.sendall(mes)
message = '世界真美好'.encode('utf-8')
length1 = len(message)
res_ = struct.pack('i', length1)
client.sendall(res_)
client.sendall(message)
client.close()
服务端
import socket
import struct
sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sk.bind(("127.0.0.1", 9000))
sk.listen(5)
conn, addr = sk.accept()
header1 = sk.recv(4)
data_length1 = struct.unpack('i', header1)[0]
data1 = conn.recv(data_length1)
print(data1.decode('utf-8'))
header2 = sk.recv(4)
data_length2 = struct.unpack('i', header2)[0]
data2 = conn.recv(data_length2)
print(data2.decode('utf-8'))
2、实际案例2
客户端
import socket
import struct
client = socket.socket()
client.connect(('127.0.0.1', 8003))
data1 = "alex正在吃屎".encode('utf-8')
header1 = struct.pack('i', len(data1))
client.sendall(header1)
client.sendall(data1)
data2 = "翔".encode('utf-8')
header2 = struct.pack('i', len(data2))
client.sendall(header2)
client.sendall(data2)
client.close()
服务端
import socket
import struct
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('127.0.0.1', 8003))
sock.listen(5)
conn, addr = sock.accept()
header1 = conn.recv(4)
data_length1 = struct.unpack('i', header1)[0]
data1 = conn.recv(data_length1)
print(data1.decode('utf-8'))
header2 = conn.recv(4)
data_length2 = struct.unpack('i', header2)[0]
data2 = conn.recv(data_length2)
print(data2.decode('utf-8'))
conn.close()
sock.close()