1.1 tcp协议的粘包现象
tcp协议传输数据存在粘包现象,udp协议不存在粘包协议。
1.1.1 什么是粘包现象
1.发生在发送端的粘包
由于两个数据的发送时间间隔短+数据的长度小,所以由tcp协议的优化机制将两条信息作为一条信息发送出去了,是为了减少tcp协议中的“确认收到”的网络延迟时间
2.在接收端的粘包
由于tcp协议中所传输的数据无边界,所以来不及接收的多条数据会在接收方的内核的缓存端黏在一起
3.本质: 接收信息的边界不清晰
1.1.2 解决粘包问题
1.自定义协议1:
a.首先发送报头
报头长度4个字节
内容是 即将发送的报文的字节长度
struct模块- struck.pack 能够把所有的数字都固定的转换成4字节
b.再发送报文
2.自定义协议2
专门用来做文件发送的协议
- 先发送报头字典的字节长度
- 再发送字典(字典中包含文件的名字、大小。。。)
- 再发送文件的内容
1.2 tcp协议和udp协议的特点
- tcp : 是一个面向连接的,流式的,可靠的,慢的,全双工通信(三次握手、四次挥手)
如:邮件 / 文件/ http / web - udp : 是一个面向数据报的,无连接的,不可靠,快的,能完成一对一、一对多、多对一、多对多的高效通讯协议
如:即时聊天工具 / 视频的在线观看
1.3 三次握手 / 四次挥手
1.三次握手
- server端:accept接受过程中等待客户端的连接
- connect客户端发起一个SYN链接请求
- 如果收到了server端响应ACK的同时还会再收到一个由server端发来的SYN链接请求
- client端进行回复ACK之后,就建立起了一个tcp协议的链接
三次握手的过程在代码中是由accept和connect共同完成的,具体的细节再socket中没有体现出来
2.四次挥手
- server和client端对应的在代码中都有close方法
- 每一端发起的close操作都是一次FIN的断开请求,得到'断开确认ACK'之后,就可以结束一端的数据发送。
- 如果两端都发起close,那么就是两次请求和两次回复,一共是四次操作,可以结束两端的数据发送,表示链接断开了
2.1 阻塞与非阻塞
2.1 io模型
io模型种类:
- 阻塞io模型、非阻塞io模型、事件驱动io、io多路复用、异步io模型
2.2 socket的非阻塞io模型
server端同时与多个client客户端之间的聊天:
socket的非阻塞io模型 + io多路复用实现的
虽然非阻塞,提高了CPU的利用率,但是耗费CPU做了很多无用功
# server.py
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',9000))
sk.setblocking(False) # 非阻塞,setblocking()的参数为False时,表示非阻塞,如果参数不写,默认为True。
sk.listen()
conn_l = []
del_l = []
while True:
try:
conn,addr = sk.accept() # 阻塞,直到有一个客户端来连我
print(conn)
conn_l.append(conn)
except BlockingIOError:
for c in conn_l:
try:
msg = c.recv(1024).decode('utf-8')
if not msg:
del_l.append(c)
continue
print('-->',[msg])
c.send(msg.upper().encode('utf-8'))
except BlockingIOError:pass
for c in del_l:
conn_l.remove(c)
del_l.clear()
sk.close()
# client.py
import time
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',9000))
for i in range(30):
sk.send(b'zhangsan')
msg = sk.recv(1024)
print(msg)
time.sleep(0.2)
sk.close()
2.3 socketserver模块
socketserver模块解决了socket的阻塞问题,直接实现tcp协议可并发的server端
# server.py
import socketserver
class Myserver(socketserver.BaseRequestHandler):
def handle(self): # 自动触发了handle方法,并且self.request == conn
msg = self.request.recv(1024).decode('utf-8')
self.request.send('1'.encode('utf-8'))
msg = self.request.recv(1024).decode('utf-8')
self.request.send('2'.encode('utf-8'))
msg = self.request.recv(1024).decode('utf-8')
self.request.send('3'.encode('utf-8'))
server = socketserver.ThreadingTCPServer(('127.0.0.1',9000),Myserver)
server.serve_forever()
# client.py
import socket
import time
sk = socket.socket()
sk.connect(('127.0.0.1',9000))
for i in range(3):
sk.send(b'hello,yuan')
msg = sk.recv(1024)
print(msg)
time.sleep(1)
sk.close()
3. 验证客户端的合法性
客户端是提供给 用户使用的 —— 登陆验证
你的用户 就能看到你的client端源码了,用户就不需要自己写客户端了客户端是提供给 机器使用的 —— 验证客户端的合法性
防止非法用户进入服务端窃取内部重要信息
# server.py import os import hashlib import socket def get_md5(secret_key,randseq): md5 = hashlib.md5(secret_key) md5.update(randseq) res = md5.hexdigest() return res def chat(conn): while True: msg = conn.recv(1024).decode('utf-8') print(msg) conn.send(msg.upper().encode('utf-8')) sk = socket.socket() sk.bind(('127.0.0.1',9000)) sk.listen() secret_key = b'names' while True: conn,addr = sk.accept() randseq = os.urandom(32) conn.send(randseq) md5code = get_md5(secret_key,randseq) ret = conn.recv(32).decode('utf-8') print(ret) if ret == md5code: print('是合法的客户端') chat(conn) else: print('不是合法的客户端') conn.close() sk.close() # client.py import hashlib import socket import time def get_md5(secret_key,randseq): md5 = hashlib.md5(secret_key) md5.update(randseq) res = md5.hexdigest() return res def chat(sk): while True: sk.send(b'hello') msg = sk.recv(1024).decode('utf-8') print(msg) time.sleep(0.5) sk = socket.socket() sk.connect(('127.0.0.1',9000)) secret_key = b'names' randseq = sk.recv(32) md5code = get_md5(secret_key,randseq) sk.send(md5code.encode('utf-8')) chat(sk) sk.close()