3.1.1 理论
应用层:python代码,b’hello’
传输层:预备如何传输、使用的端口,b’发送者的端口+hello+接受者的端口’
网络层:ip,b’发送者的ip+发送者的端口+hello+接受者的端口+接受者的端口’
数据链路层:mac,b’发送者mac地址+发送者的ip+发送者的端口+hello+接受者的端口+接受者的端口+接受者mac地址’
物理层:转化成电信号,通过网线传输
3.1.2 每层涉及到的协议和物理设备
第五层 应用层:python代码。
第四层 传输层:port,udp,tcp。四层路由器,四层交换机。
第三层 网络层:ipv4和ipv6。路由器,三层交换机。(家里的路由器带有交换机功能)
第二层 数据链路层:mac,arp协议。网卡,二层交换机。
第一层 物理层:网线。
3.2 tcp协议和udp协议
3.2.1 tcp协议
tcp(语音聊天)- 线下缓存高清电影、qq远程控制、发邮件
需要先建立连接,然后才能通信的。
特点:占用连接、可靠(消息不会丢失)、实时性高、慢
建立连接:指三次握手
断开连接:指四次挥手
3.2.2 udp协议
udp(发短信)- 在线播放视频、qq发消息、微信发消息
不需要建立连接,就可以通信的。
特点:不占用连接、不可靠(消息可能因为网络不稳定而丢失)、快
四、tcp协议的代码
操作系统会统一分配计算机的所有资源
socket():tcp协议的server
socket(type=socket.SOCK_DGRAM):udp协议的server【udp协议中使用】
bind:绑定一个id和端口
listen:监听,代表socket服务的开启
accept:等到有客户端来访问,与客户端建立连接
send:直接通过连接发送消息,不需要写地址
sendto:需要写一个对方的地址【udp协议中使用】
recv:只接收消息
recvfrom:接收消息和地址【udp协议中使用】
connect:客户端/tcp协议的方法,和server端建立连接
close:关闭服务(sk.close)/连接(conn.close)
4.1 能够接收多个客户端的请求
server.py
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 9000)) # 申请操作系统的资源
sk.listen()
while True:
# print(f'sk:{sk}')
# conn里存储的是一个客户端和服务端的连接信息
conn, adder = sk.accept() # 能够和多个客户端进行握手了
# print(f'conn:{conn}')
conn.send(b'hello')
msg = conn.recv(1024)
print(msg)
conn.close() # 挥手,断开连接
sk.close() # 归还申请的操作系统的资源
client.py
import socket
sk = socket.socket()
# 必须和server端的一致
sk.connect(('127.0.0.1', 9000))
msg = sk.recv(1024)
print(msg)
# 客户端 发送 消息给服务端
sk.send(b'bye')
sk.close()
4.2 在连接内,和客户端多说几句。以及解决不能传输中文问题
server.py
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 9000)) # 申请操作系统的资源
sk.listen()
while True: # 为了和多个客户端进行握手
# conn里存储的是一个客户端和服务端的连接信息
conn, adder = sk.accept() # 能够和多个客户端进行握手了
while True:
send_msg = input('please input you want say:')
conn.send(send_msg.encode('utf-8'))
if send_msg.upper() == 'Q':
break
msg = conn.recv(1024).decode('utf-8')
if msg.upper() == 'Q':
break
print(msg)
conn.close() # 挥手,断开连接
sk.close()
client.py
import socket
sk = socket.socket()
sk.connect(('127.0.0.1', 9000))
while True:
msg = sk.recv(1024).decode('utf-8')
if msg.upper() == 'Q':
break
print(msg)
send_msg = input('please input you want say:')
sk.send(send_msg.encode('utf-8'))
if send_msg.upper() == 'Q':
break
sk.close()
4.3 程序执行到哪里会阻塞,什么时候结束阻塞
input() 等待,直到用户输入enter键
accept() 阻塞,有客户端来与我建立完连接之后
recv() 阻塞,直到收到对方发过来的消息之后
recvfrom() 阻塞,直到收到对方发过来的消息之后
connect() 阻塞,直到server端结束了对一个client的服务,开始和当前client建立连接的时候
五、udp协议的代码
server.py
import socket
sk = socket.socket(type=socket.SOCK_DGRAM) # 表示一个udp协议
sk.bind(('127.0.0.1', 9000))
# 服务端不能先发送消息,因为服务端不知道客户端的ip
while True:
msg, addr = sk.recvfrom(1024)
print(msg.decode('utf-8'))
send_msg = input('please input you want say:')
sk.sendto(send_msg.encode('utf-8'), addr)
# server端不需要判断退出
# 因为不和这个客户端通信,还要和其他客户端通信
client.py
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
server = ('127.0.0.1', 9000)
while True:
send_msg = input('please input you want say:')
if send_msg.upper() == 'Q':
break
sk.sendto(send_msg.encode('utf-8'), server)
msg = sk.recv(1024).decode('utf-8')
if msg.upper() == 'Q':
break
print(msg)
六、粘包问题
6.1 tcp协议中的粘包现象
两条或更多条分开发送的信息连在一起就是粘包现象
server.py
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 9001))
sk.listen()
conn, addr = sk.accept()
conn.send(b'hello')
conn.send(b'nice')
conn.close()
sk.close()
client.py
import time
import socket
sk = socket.socket()
sk.connect(('127.0.0.1', 9001))
time.sleep(0.1)
msg1 = sk.recv(1024)
print(msg1)
msg2 = sk.recv(1024)
print(msg2)
sk.close()
先启动server,然后启动client,client打印结果如下:
b'hellonice'
b''
产生了粘包现象,去掉睡眠时间结果如下:
b'hellonice'
b'nice'
【注意】即使不设置睡眠时间,也有几率粘包
6.2 产生粘包现象的两个原因
粘包现象只出现在tcp协议中,因为tcp协议中,多条消息之间没有边界(流式传输),并且还有一大堆优化算法。
- 发送端:两条消息很短,并且发送的间隔时间也很短,由于优化机制就合并在一起发送了
- 接收端:多条消息由于没有及时接收,在接收方的缓存中堆在一起而导致的粘包
6.3 粘包发生的本质
tcp协议的传输是流式传输,数据与数据之间没有边界
6.4 解决粘包的问题的本质
设置边界
6.5 解决粘包
自定义协议
服务端:先发送4个字节的数据长度,再按照长度发送数据
客户端:先接收4个字节,知道数据的长度,再按照长度接收数据
6.6 struct模块
该模块可以把一个类型,如数字,转成固定长度的 bytes。
数字的范围 -2**31-1 ~ 2**31
,共 2**32 个数字。pack可以把这个范围内的任意一个数字转为长度为 4 的字节。
import struct
num1 = 123456789
num2 = 12345
num3 = 123
ret1 = struct.pack('i', num1)
print(ret1, len(ret1)) # b'\x15\xcd[\x07' 4
ret2 = struct.pack('i', num2)
print(ret2, len(ret2)) # b'90\x00\x00' 4
ret3 = struct.pack('i', num3)
print(ret3, len(ret3)) # b'{\x00\x00\x00' 4
print(struct.unpack('i', ret1)) # (123456789,)
print(struct.unpack('i', ret2)) # (12345,)
print(struct.unpack('i', ret3)) # (123,)
6.7 解决粘包代码
server.py
import socket
import struct
sk = socket.socket()
sk.bind(('127.0.0.1', 9001))
sk.listen()
conn, addr = sk.accept()
msg1 = input('>>>')
msg2 = input('>>>')
blen = struct.pack('i', len(msg1.encode()))
conn.send(blen)
conn.send(msg1.encode())
conn.send(msg2.encode())
conn.close()
sk.close()
client.py
import time
import socket
import struct
sk = socket.socket()
sk.connect(('127.0.0.1', 9001))
time.sleep(0.1)
length = sk.recv(4)
length = struct.unpack('i', length)[0]
msg1 = sk.recv(length)
msg2 = sk.recv(1024)
print(msg1.decode('utf-8'))
print(msg2.decode('utf-8'))
sk.close()
6.8 总结
- recv(1024)不代表一定收到1024个字节,而是最多只能收1024个字节
- 两条连续发送的数据一定要避免粘包问题
- tcp协议的自定义协议解决粘包问题的思路:先发送数据的长度,再发送数据。【将发送的数据组成json格式:先发送json的长度,再发json(json中存了接下来要发送的数据长度),再发数据】
七、基于udp协议的多人聊天
7.1 基于udp协议的多人聊天
自动识别用户,不能使用 ip 和 port
server.py
import socket
friend_lst = {'jack': '31', 'tom': '34'}
sk = socket.socket(type=socket.SOCK_DGRAM)
sk.bind(('127.0.0.1', 9000))
while 1:
msg, addr = sk.recvfrom(1024)
msg = msg.decode('utf-8')
name, message = msg.split('|')
print(f'\033[1;{friend_lst.get(name,"30")}m {name}:{message} \033[0m')
content = input('>>>')
sk.sendto(content.encode('utf-8'), addr)
client.py
import socket
name = 'jack'
sk = socket.socket(type=socket.SOCK_DGRAM)
while 1:
content = input('>>>')
if content.upper() == 'Q':
break
content = f'{name}|{content}'
sk.sendto(content.encode('utf-8'), ('127.0.0.1', 9000))
msg = sk.recv(1024).decode('utf-8')
if msg.upper() == 'Q':
break
print(msg)
八、基于tcp协议的文件传输
server.py
import socket
import json
# 接收
sk = socket.socket()
sk.bind(('127.0.0.1', 9002))
sk.listen()
conn, addr = sk.accept()
msg = conn.recv(1024).decode('utf-8')
msg = json.loads(msg)
with open(msg['filename'], 'wb') as f:
content = conn.recv(msg['filesize'])
print('-->', len(content))
f.write(content)
print(msg)
conn.close()
sk.close()
client.py
import os
import json
import socket
# 发送
sk = socket.socket()
sk.connect(('127.0.0.1', 9002))
# 文件名、文件大小
abs_path = r'E:\A.PythonProject\action\day30\作业\a.txt'
filename = os.path.basename(abs_path)
filesize = os.path.getsize(abs_path)
dic = {'filename': filename, 'filesize': filesize}
str_dic = json.dumps(dic)
sk.send(str_dic.encode('utf-8'))
with open(abs_path, 'rb') as f:
content = f.read()
sk.send(content)
sk.close()
8.1 优化
上面代码存在的问题:发送的字典可能和文件内容发送粘包;发送大文件时,接收的文件大小比原来的小
server.py
import socket
import json
# 接收
import struct
sk = socket.socket()
sk.bind(('127.0.0.1', 9002))
sk.listen()
conn, addr = sk.accept()
msg_len = conn.recv(4)
dic_len = struct.unpack('i', msg_len)[0]
### 最后
Python崛起并且风靡,因为优点多、应用领域广、被大牛们认可。学习 Python 门槛很低,但它的晋级路线很多,通过它你能进入机器学习、数据挖掘、大数据,CS等更加高级的领域。Python可以做网络应用,可以做科学计算,数据分析,可以做网络爬虫,可以做机器学习、自然语言处理、可以写游戏、可以做桌面应用…Python可以做的很多,你需要学好基础,再选择明确的方向。这里给大家分享一份全套的 Python 学习资料,给那些想学习 Python 的小伙伴们一点帮助!
#### 👉Python所有方向的学习路线👈
Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
![](https://img-blog.csdnimg.cn/img_convert/604bae65027d4d67fb62410deb210454.png)
#### 👉Python必备开发工具👈
工欲善其事必先利其器。学习Python常用的开发软件都在这里了,给大家节省了很多时间。
![](https://img-blog.csdnimg.cn/img_convert/fa276175617e0048f79437bd30465479.png)
#### 👉Python全套学习视频👈
我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了。
![](https://img-blog.csdnimg.cn/img_convert/16ac689cb023166b2ffa9c677ac40fc0.png)
#### 👉实战案例👈
学python就与学数学一样,是不能只看书不做题的,直接看步骤和答案会让人误以为自己全都掌握了,但是碰到生题的时候还是会一筹莫展。
因此在学习python的过程中一定要记得多动手写代码,教程只需要看一两遍即可。
![](https://img-blog.csdnimg.cn/img_convert/0d8c31c50236a205928a1d8ae8a0b883.png)
#### 👉大厂面试真题👈
我们学习Python必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。
![](https://img-blog.csdnimg.cn/img_convert/99461e47e58e503d2bc1dc6f4668534a.png)
**[需要这份系统化学习资料的朋友,可以戳这里无偿获取](https://bbs.csdn.net/topics/618317507)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**