tcp协议
三次握手和四次挥手
所有的断开都是单方面的
粘包现象
为什么会出现粘包现象:
本质:接收端不知道发送端发送的数据长度是多少
tcp协议本身的特点导致:
流式传输、无边界
合包机制
缓存机制
拆包机制
如何解决这个问题?
自定义协议:先发送要传递数据的长度
用户登录验证协议
# 先创建一个文件,里面随便放几个用户名及对应的密文密码,如:
# userinfo
alex|802165d8e6a7e0fbf11b0feca3913271
jane|48610593feea04340acf643a71621e32
# server.py import socket import json import hashlib sk = socket.socket() sk.bind(("127.0.0.1", 8080)) sk.listen() def get_md5_code(usr, pwd): md5 = hashlib.md5(usr.encode()) md5.update(pwd.encode()) return md5.hexdigest() conn,addr = sk.accept() msg = conn.recv(1024).decode() dic_msg = json.loads(msg) with open("userinfo") as f: for line in f: usr,pwd = line.strip().split("|") if usr == dic_msg["username"] and pwd == get_md5_code(dic_msg["username"], dic_msg["password"]): ret = {"code": 1} res_msg = json.dumps(ret).encode() conn.send(res_msg) break else: ret = {"code": 0} res_msg = json.dumps(ret).encode() conn.send(res_msg) conn.close() sk.close()
# client.py import socket import json usr = input("username: ") pwd = input("password: ") dic = {"username": usr, "password": pwd, "operate": "login"} json_dic = json.dumps(dic) bytes_msg = json_dic.encode() sk = socket.socket() sk.connect(("127.0.0.1", 8080)) # 为什么要放在这里? # 因为比如有的用户打开客户端要输入用户名的时候想了半天还没想出来 # 但是客户端却一直开着,所以为了避免这种情况把上面两行放这里 sk.send(bytes_msg) recv_msg = sk.recv(1024).decode() print(recv_msg) dic_code = json.loads(recv_msg) if dic_code["code"]: print("登录成功") else: print("登录失败") sk.close()
如何让服务端与一个客户端聊天时,客户端退出后,服务端还能继续等待下一个客户端聊天?
# server.py import socket sk = socket.socket() sk.bind(('127.0.0.1',9000)) sk.listen() while True: # 和n个人聊天 conn,addr = sk.accept() while True: # 和一个人聊n久 send_msg = input('msg : ') conn.send(send_msg.encode()) if send_msg == 'q':break msg = conn.recv(1024).decode() if msg == '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() if msg == 'q':break print(msg) send_msg = input('msg : ') sk.send(send_msg.encode()) if send_msg == 'q':break sk.close() # 这里注意,客户端输入q退出程序后,会发现服务端没有关闭 # 这时只需重启客户端,相当于另一个用户进入,就可继续聊天
完成一个上传文件的程序
# server.py import struct import socket import json sk = socket.socket() sk.bind(("127.0.0.1", 8080)) sk.listen() conn, addr = sk.accept() bytes_len = conn.recv(4) info_len = struct.unpack("i", bytes_len)[0] json_info = conn.recv(info_len).decode() info_dic = json.loads(json_info) print(info_dic) with open(info_dic["filename"], "wb") as f: # 这里有问题,如果把这里的1024和client.py里的1024都改为2048 # 发现运行结果后查看该文件时文件大小变小了 # 原因是这里虽然写conn.recv(1024) # 但不代表每次一定就能接收到1024个字节 # 比如最后还剩800多字节,就会发生拆包现象 # 也就是说,虽然写的是接收1024,其实最后只有800多 # 这样就要把info_dic["filesize"] -= 1024的1024改掉 while info_dic["filesize"]: content = conn.recv(1024) f.write(content) # info_dic["filesize"] -= 1024 info_dic["filesize"] -= len(content) conn.close() sk.close()
import socket import os import json import struct # 在上传之前,应该先向server端传递文件信息:文件大小、文件名、以及要做的操作,这里就要用到send,但是可能会与下面的send产生粘包现象 # 打开文件 # 读文件 # 按照字节读取 # 5000:1024 1024 1024 1024 904 # 这样就要send五次,不用担心粘包,只要传过去5000字节就行 # server端先接收4字节 # 然后直到了文件信息的长度 # 按照长度接收文件信息 # 从文件信息中得到文件的大小 # 就知道要收取的文件的大小是多少 # 开始接收,直到收完文件大小这么多的数据 # 在上传之前,应该先向server端传递文件信息:文件大小、文件名、以及要做的操作 file_path = r"G:day01 视频以及笔记20181219_163449.mp4" # input("file_path") filesize = os.path.getsize(file_path) filename = os.path.basename(file_path) # print(filesize) # 143799118 # print(filename) # 20181219_163449.mp4 file_info = {"filesize": filesize, "filename": filename, "operate": "upload"} json_info = json.dumps(file_info) file_info_bytes = json_info.encode() bytes_len = len(file_info_bytes) sk = socket.socket() sk.connect(("127.0.0.1", 8080)) # 先发送文件信息的长度,再发送文件信息 sk.send(struct.pack("i", bytes_len)) sk.send(file_info_bytes) with open(file_path, "rb") as f: while filesize > 0: content = f.read(1024) filesize -= len(content) sk.send(content) # 这里已经是bytes类型 sk.close()
udp协议
# server.py import socket sk = socket.socket(type=socket.SOCK_DGRAM) sk.bind(("127.0.0.1", 8080)) # 这里不用写sk.listen(),没有三次握手 msg, client_addr = sk.recvfrom(1024) print(msg) sk.sendto(b"hello", client_addr) sk.close()
# client.py import socket sk = socket.socket(type=socket.SOCK_DGRAM) sk.sendto(b"hello", ("127.0.0.1", 8080)) msg, addr = sk.recvfrom(1024) print(msg) sk.close()
使用udp协议实现多人聊天功能
# server.py import socket sk = socket.socket(type=socket.SOCK_DGRAM) sk.bind(("127.0.0.1", 8080)) # 这里不用写sk.listen(),没有三次握手 # 和一个人多次聊天 while True: msg, client_addr = sk.recvfrom(1024) print(msg.decode()) content = input(">>>") sk.sendto(content.encode(), client_addr) sk.close()
# client.py import socket sk = socket.socket(type=socket.SOCK_DGRAM) # 和一个人多次聊天 # 可以再创建一个client.py,内容一模一样,然后依次运行 # 都可以跟server端聊天 while True: content = input(">>>") # 多人聊天时,可以这样区分谁跟server端聊天 content = "%s : %s" % ("alex", content) sk.sendto(content.encode(), ("127.0.0.1", 8080)) msg, addr = sk.recvfrom(1024) print(msg.decode()) sk.close()
使用socketserver来实现tcp协议socket的并发
# server.py import socketserver # socketserver是一个上层模块,socket是一个下层模块 # 因此 socketserver 可以实现并发 # socket是基于socketserver的下层模块 # socketserver是基于socket的上层模块 class Myserver(socketserver.BaseRequestHandler): # 注意:有多少个client端,就相当于这里有多少个handle def handle(self): # print("连接上了") # print(self.request) # 这个就是conn,因此这样写 # conn = self.request # conn.send(b"hello") # 可以与多个client端运行,即并发 while True: conn = self.request conn.send(b"hello") # 这里不需要实例化一个对象 server = socketserver.ThreadingTCPServer(("127.0.0.1", 8080), Myserver) server.serve_forever()
# socketserver
# 写一个socket的server端,能够同时和多个client端进行通信,互不干扰
# socketserver的server端不能用input,因为server端使用了并发机制
# 如何写?
# 1.必须继承socketserver.BaseRequestHandler
# 2.自定义一个类必须实现handler方法
# 3.自定义类中的self.request就是conn
# 4.启动程序的方法:
# socket.server.ThreadingTCPServer(ip, port),自定义类名)
# obj.server_forever()
# client.py import socket sk = socket.socket() sk.connect(("127.0.0.1", 8080)) while True: msg = sk.recv(1024) print(msg) sk.close()
# client1.py import socket sk = socket.socket() sk.connect(("127.0.0.1", 8080)) while True: msg = sk.recv(1024) print(msg) sk.close()
# client2.py import socket sk = socket.socket() sk.connect(("127.0.0.1", 8080)) while True: msg = sk.recv(1024) print(msg) sk.close()
1.两个连续的send会产生粘包现象
2.用struct自定义协议可以解决粘包问题
3.文件传输时不用考虑粘包问题
4.自定义协议的进阶版本:
先发送字符串的长度,再发送字符串
先发送json的长度,再发送json
json的字典中包含着下一条信息的长度,然后按照长度接收
tcp与udp协议
tcp
面向连接的,可靠的,全双工的,流式传输
面向连接:同一时刻只能和一个客户端通信
要进行三次握手,四次挥手
可靠的:数据不丢失、慢
全双工:能够双向通信(send,recv谁先都可以)
流式传输:粘包、无边界
udp
无连接的,面向数据包,不可靠的,快速
无连接的:不需要accept/connect, 也没有握手
面向数据包的:不会粘包
不可靠的:没有自动回复的机制
快速的:没有复杂的计算、保证数据传输的机制
# 再讲 pickle 模块 import pickle class Course: def __init__(self, name, period, price, teacher): self.name = name self.period = period self.price = price self.teacher = teacher python = Course("python", "six months", 19800, "alex") linux = Course("linux", "five months", 15800, "taibai") with open("course_info", "wb") as f: pickle.dump(python, f) pickle.dump(linux, f) with open("course_info", "rb") as f: while 1: try: # print(pickle.load(f).name) # python linux obj = pickle.load(f) print(obj.__dict__) # {'name': 'python', 'period': 'six months', # 'price': 19800, 'teacher': 'alex'} # {'name': 'linux', 'period': 'five months', # 'price': 15800, 'teacher': 'taibai'} except EOFError: break # dumps str-->bytes # dump str-->文件里的bytes # 1.json能处理的数据类型有限,但是所有语言通用 # pickle支持python中几乎所有对象,但是只能在python语言中使用 # 2.json的dumps的结果是str, pickle的dumps的结果是bytes # 3.json不能连续dump多个数据到文件中 # pickle可以
# 验证客户端的合法性 # server.py import os import hmac import socket def auth(conn): secret_key = b"jane" rand_b = os.urandom(32) conn.send(rand_b) obj = hmac.new(secret_key, rand_b) res1 = obj.digest() res2 = conn.recv(1024) cmp_res = hmac.compare_digest(res1, res2) return cmp_res sk = socket.socket() sk.bind(("127.0.0.1", 8080)) sk.listen() conn, addr = sk.accept() res = auth(conn) if res: print("是合法的客户端") conn.send("您好".encode()) else: conn.close() sk.close() # 运行后得出结果:是合法的客户端 # 只要两边的secret_key的值不一样,服务端就知道不是合法的客户端
# client.py import socket import hmac def auth(sk): secret_key = b"jane" rand_b = sk.recv(32) # 注意这里 obj = hmac.new(secret_key, rand_b) res2 = obj.digest() sk.send(res2) sk = socket.socket() sk.connect(("127.0.0.1", 8080)) auth(sk) msg = sk.recv(1024) print(msg.decode()) sk.close()