【一】三次握手和四次挥手
TCP协议位于osi七层协议中的传输层
使用三次握手来建立连接
使用四次挥手来断开连接
有几个名词:
SYN:SYN=1 表示要建立连接
SYN:SYN=1 表示要建立连接
ACK:ACK=1 表示我收到了,允许
seq:随机数,建立连接无论客户端还是服务端要建立连接就要要携带
ack:回应请求就要加1返回
FIN:表示断开连接
【1】三次握手
发生在建立连接的阶段 【1】第一次请求 由客户端发起请求 带SYN=1 表示我自己是客户端我要建立连接 seq随机数带 发送给服务端 客户端 ---> 携带 SYN和SEQ ---> 发送给服务端 【2】第二次请求 服务端接收到客户端的请求 ACK=1 表示收到了当前客户端发送给我的请求 SYN=1 表示要建立连接 seq:随机数 服务端 ---> 接收到客户端的请求,同意建立连接 ---> 发送给客户端 【3】第三次请求 客户端接收到了服务端的请求 ACK=1 表示收到了当前服务端发送给我的请求 SYN=1 表示要建立连接 seq:随机数 和服务端建立连接成功
【2】四次挥手
发生在断开连接上 【1】第一次 客户端向服务端发送请求,表示想要断开连接 【2】第二次 服务端接收到客户端的请求 表示同意断开连接 【3】第三次 服务端向客户端发送请求,请求的原因是当前还有数据没有传输完成 请求等待,等待数据传输完成 发起请求,断开连接 服务端向客户端发送请求,请求断开连接 【4】第四次 客户端接收到服务端的请求 直接断开连接
【二】UDP协议模型
client文件
# 【一】引入socket模块 from conf import settings import socket # 【二】创建一个client对象 # AF_INET: 当前连接的是基于网络的套接字 # SOCK_DGRAM:连接模式是UDP协议的报式模式 client = socket.socket(family=socket.AF_INET,type=socket.SOCK_DGRAM) # 【三】直接发送数据 to_server_send_data = f'这是来自客户端的数据' to_server_send_data = to_server_send_data.encode() client.sendto(to_server_send_data,settings.ADDR) print(f"client:{client}") # 【四】接收到服务端回的消息 from_server_recv_data,addr = client.recvfrom(1024) from_server_recv_data = from_server_recv_data.decode() print(f'from_server_recv_data:{from_server_recv_data}') print(from_server_recv_data) # 【五】关闭连接对象 client.close()
server文件
from conf import settings # 【一】引入socket模块 import socket # 【二】创建一个server对象 # AF_INET:当前连接是基于网络的套接字 # SOCK_DGRAM:连接模式是UDP协议的报式模式 # 只会发送一次数据 server = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) # 【三】绑定IP和PORT server.bind(settings.ADDR) print(f'server:{server}') # 【四】接收到客户端的数据 from_client_socket_data,addr = server.recvfrom(1024) from_client_socket_data = from_client_socket_data.decode() print(f'from_client_socket_data: {from_client_socket_data}') print(f'addr :>>>>{addr}') # 【五】返回给客户端数据 to_client_send_data = '这是来自服务端的一条消息!' to_client_send_data = to_client_send_data.encode() server.sendto(to_client_send_data,addr) # 【六】关闭连接和服务 server.close()
补充:
【1】localhost和127.0.0.1的区别
127.0.0.1又称为本机IP地址
DNS解析 ---》一串字符解析成IP
localhost就是一个域名,但是只能在本地使用
【2】报错问题
# 【一】解决办法一:通用 : 换 端口号 # 直接将启动的IP和端口号中的端口号更改 # 【二】解决办法二:加一个配置 # address already in use # 【1】加配置 # SO_REUSEADDR : re 重新 use 使用 addr 地址 # server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 绑定IP和PORT # 【2】绑定端口 # server.bind(settings.ADDR) # 【三】解决办法三:# OSError: [WinError 10048] 通常每个套接字地址(协议/网络地址/端口)只允许使用一次。 # 【1】Windows # 查看当前端口对应的进程,杀死进程 # netstat -ano|findstr 8002 # 杀死当前进程PID对应的进程 # taskkill /pid PID号 /F
【三】TCP协议模型
settings文件
IP = '127.0.0.1' PORT = 8088 ADDR = (IP, PORT)
【1】第一版
客户端文件
# 【一】引入socket模块 from conf import settings import socket # 【二】创建一个server对象 # AF_INET: 当前连接的是基于网络的套接字 # SOCK_STREAM:连接模式是TCP的流式模式 client = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM) # 【三】绑定IP和PORT client.connect(settings.ADDR) # 【四】直接发送数据 to_server_send_data = f'这是来自客户端的数据' to_server_send_data = to_server_send_data.encode() client.send(to_server_send_data) print(f'to_server_send_data :>>>> {client}') # 【五】接收到服务端回的消息 from_server_recv_data = client.recv(1024) from_server_recv_data = from_server_recv_data.decode() print(from_server_recv_data) # 【六】关闭连接对象 client.close()
服务器文件
from conf import settings # 【一】引入socket模块 import socket # 【二】创建一个server对象 # AF_INET:当前连接是基于网络的套接字 # SOCK_STREAM:连接模式是TCP协议的流式模式 server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) # 【三】绑定IP和PORT server.bind(settings.ADDR) # 【四】监听连接对象 server.listen(5) # 【五】建立连接对象 conn,addr = server.accept() # 【六】接收到客户端的数据 from_client_socket_data = conn.recv(1024) from_client_socket_data = from_client_socket_data.decode() print(from_client_socket_data) # 【七】返回给客户端数据 to_client_send_data = '这是来自服务端的一条消息!' to_client_send_data = to_client_send_data.encode() conn.send(to_client_send_data) # 【八】关闭连接和服务 conn.close() server.close()
【2】第二版(解决了只能发送一次数据的问题)
在里面套一个while循环即可
client文件
# 【一】引入socket模块 from conf import settings import socket # 【二】创建一个server对象 # AF_INET: 当前连接的是基于网络的套接字 # SOCK_STREAM:连接模式是TCP的流式模式 client = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM) # 【三】绑定IP和PORT client.connect(settings.ADDR) while True: # 【四】直接发送数据 to_server_send_data = input(f'请输入要传递给服务端的数据:').strip() to_server_send_data = to_server_send_data.encode() client.send(to_server_send_data) print(f'to_server_send_data :>>>> {client}') # 【五】接收到服务端回的消息 from_server_recv_data = client.recv(1024) from_server_recv_data = from_server_recv_data.decode() print(from_server_recv_data) # 【六】关闭连接对象 client.close()
server文件
from conf import settings # 【一】引入socket模块 import socket # 【二】创建一个server对象 # AF_INET:当前连接是基于网络的套接字 # SOCK_STREAM:连接模式是TCP协议的流式模式 server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) # 【三】绑定IP和PORT server.bind(settings.ADDR) # 【四】监听连接对象 server.listen(5 ) while True: # 【五】建立连接对象 conn, addr = server.accept() # 【六】接收到客户端的数据 from_client_socket_data = conn.recv(1024) from_client_socket_data = from_client_socket_data.decode() print(from_client_socket_data) # 【七】返回给客户端数据 to_client_send_data = input(f'请输入传递给客户端的数据:').strip() to_client_send_data = to_client_send_data.encode() conn.send(to_client_send_data) # 【八】关闭连接和服务 conn.close() server.close()
【3】第三版(上面只能发一个来回,这里解决了这个问题)
server文件
from conf import settings # 【一】引入socket模块 import socket # 【二】创建一个server对象 # AF_INET:当前连接是基于网络的套接字 # SOCK_STREAM:连接模式是TCP协议的流式模式 server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) # 【三】绑定IP和PORT server.bind(settings.ADDR) # 【四】监听连接对象 server.listen(5) # 【五】建立连接对象 conn, addr = server.accept() # 将他放到while循环外面,每次都会换对象,在里面不会换 while True: # 这里面交流的对象永远都是上面接收到的 # 【六】接收到客户端的数据 from_client_socket_data = conn.recv(1024) from_client_socket_data = from_client_socket_data.decode() print(from_client_socket_data) # 【七】返回给客户端数据 to_client_send_data = input(f'请输入传递给客户端的数据:').strip() to_client_send_data = to_client_send_data.encode() conn.send(to_client_send_data) # 【八】关闭连接和服务 conn.close() server.close()
【4】第四版(不允许传递空格及q退出,在里面再次添加一个while判断)
client文件
# 【一】引入socket模块 from conf import settings import socket # 【二】创建一个server对象 # AF_INET: 当前连接的是基于网络的套接字 # SOCK_STREAM:连接模式是TCP的流式模式 client = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM) # 【三】绑定IP和PORT client.connect(settings.ADDR) while True: # 【四】直接发送数据 to_server_send_data = input(f'请输入要传递给服务端的数据:').strip() if not to_server_send_data: print(f'不允许传递空格') continue if to_server_send_data == 'q': print(f'成功断开连接') break to_server_send_data = to_server_send_data.encode() client.send(to_server_send_data) print(f'to_server_send_data :>>>> {client}') # 【五】接收到服务端回的消息 from_server_recv_data = client.recv(1024) from_server_recv_data = from_server_recv_data.decode() print(from_server_recv_data) # 【六】关闭连接对象 client.close()
server文件
from conf import settings # 【一】引入socket模块 import socket # 【二】创建一个server对象 # AF_INET:当前连接是基于网络的套接字 # SOCK_STREAM:连接模式是TCP协议的流式模式 server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) # 【三】绑定IP和PORT server.bind(settings.ADDR) # 【四】监听连接对象 server.listen(5) # 【五】建立连接对象 conn, addr = server.accept() while True: # 这里面交流的对象永远都是上面接收到的 # 【六】接收到客户端的数据 from_client_socket_data = conn.recv(1024) from_client_socket_data = from_client_socket_data.decode() print(from_client_socket_data) while True: # 【七】返回给客户端数据 to_client_send_data = input(f'请输入传递给客户端的数据:').strip() if not to_client_send_data: print(f"不允许传递空格") continue if to_client_send_data == "q": print(f'成功断开连接') break to_client_send_data = to_client_send_data.encode() conn.send(to_client_send_data) break # 【八】关闭连接和服务 conn.close() server.close()
【5】第五版(无论哪一边输入q,两边都断开,不会傻等了)
client文件
# 【一】引入socket模块 from conf import settings import socket # 【二】创建一个server对象 # AF_INET: 当前连接的是基于网络的套接字 # SOCK_STREAM:连接模式是TCP的流式模式 client = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM) # 【三】绑定IP和PORT client.connect(settings.ADDR) while True: # 【四】直接发送数据 to_server_send_data = input(f'请输入要传递给服务端的数据:').strip() if not to_server_send_data: print(f'不允许传递空格') continue if to_server_send_data == 'q': print(f'成功断开连接') break to_server_send_data = to_server_send_data.encode() client.send(to_server_send_data) print(f'to_server_send_data :>>>> {client}') # 【五】接收到服务端回的消息 from_server_recv_data = client.recv(1024) from_server_recv_data = from_server_recv_data.decode() print(from_server_recv_data) if from_server_recv_data == 'q': break # 【六】关闭连接对象 client.close()
server文件
from conf import settings # 【一】引入socket模块 import socket # 【二】创建一个server对象 # AF_INET:当前连接是基于网络的套接字 # SOCK_STREAM:连接模式是TCP协议的流式模式 server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) # 解决端口号占用报错的方法,重用ip端口号 server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 【三】绑定IP和PORT server.bind(settings.ADDR) # 【四】监听连接对象 server.listen(5) # 【五】建立连接对象 conn, addr = server.accept() while True: # 添加一个try捕获异常 try: # 这里面交流的对象永远都是上面接收到的 # 【六】接收到客户端的数据 from_client_socket_data = conn.recv(1024) from_client_socket_data = from_client_socket_data.decode() print(from_client_socket_data) if not from_client_socket_data: break while True: # 【七】返回给客户端数据 to_client_send_data = input(f'请输入传递给客户端的数据:').strip() if not to_client_send_data: print(f"不允许传递空格") continue if to_client_send_data == "q": print(f'成功断开连接') # 这里输入q之后编码传过去,然后执行break断开 to_client_send_data = to_client_send_data.encode() conn.send(to_client_send_data) break except Exception as e: break # 【八】关闭连接和服务 conn.close() server.close()
【四】粘包
【1】粘包问题的介绍
[1]粘包问题的背景
粘包问题只会发生在TCP协议 ---> 流式协议 ---> 不断的传输数据
比如有一个大缸:先放红颜色的颜料放了一缸但是还有一勺没有放进去
没有放进去的这一勺红颜色颜料和下一缸的绿颜色颜料混到了一起
不会发生在UDP协议 ---> 报式协议 ---> 一次性传输数据
比如有一个大缸:先放红颜色的颜料放了一缸但是还有一勺没有放进去
那么直接将这勺颜料扔了
[2]粘包问题
客户端发送的数据远远超出服务端的接收范围,出现没接收完整的情况,导致了不同数据之间的数据混乱
问题
[3]实例
执行本地的 ipconfig 命令会获取到当前执行的结果
将结果传递给 服务端
服务端接收到数据 只能接收一部分 另外一部分发现接收不到
只能和第二次的数据合并到一起发送
[4]解决办法
问题产生在服务端
原因是客户端向服务端发送数据,但是服务端不知道总的数据大小,只能按照默认的数据大小接收
解决思路:
客户端在发送数据的时候将数据的总大小一起发送给服务端
服务端接收到总的大小的数据长度 , 根据自己的容量大小分批次接收
【2】问题演示
client文件
# 【一】引入socket模块 from conf import settings import socket import subprocess def run_cmd(command): result = subprocess.run( command, # 子进程要执行的命令 shell=True, # 执行的是shell的命令 # 存放的是执行命令成功的结果 stdout=subprocess.PIPE, # 存放的是执行命令失败的结果 stderr=subprocess.PIPE, encoding="gbk", timeout=1) # return code属性是run()函数返回结果的状态。 if result.returncode == 0: return result.stdout else: return result.stderr # 【二】创建一个server对象 # AF_INET: 当前连接的是基于网络的套接字 # SOCK_STREAM:连接模式是TCP的流式模式 client = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM) # 【三】绑定IP和PORT client.connect(settings.ADDR) while True: # 【四】直接发送数据 command = input(f'请输入要传递给服务端的数据:').strip() if not command: print(f'不允许传递空格') continue if command == 'q': print(f'成功断开连接') break to_server_send_data = run_cmd(command=command) to_server_send_data = to_server_send_data.encode() client.send(to_server_send_data) print(f'to_server_send_data :>>>> {client}') # 【五】接收到服务端回的消息 from_server_recv_data = client.recv(1024) from_server_recv_data = from_server_recv_data.decode() print(from_server_recv_data) if from_server_recv_data == 'q': break # 【六】关闭连接对象 client.close()
server文件
from conf import settings # 【一】引入socket模块 import socket # 【二】创建一个server对象 # AF_INET:当前连接是基于网络的套接字 # SOCK_STREAM:连接模式是TCP协议的流式模式 server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) # 解决端口号占用报错的方法,重用ip端口号 server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 【三】绑定IP和PORT server.bind(settings.ADDR) # 【四】监听连接对象 server.listen(5) # 【五】建立连接对象 conn, addr = server.accept() while True: try: # 这里面交流的对象永远都是上面接收到的 # 【六】接收到客户端的数据 from_client_socket_data = conn.recv(1024) from_client_socket_data = from_client_socket_data.decode() print(from_client_socket_data) if not from_client_socket_data: break while True: # 【七】返回给客户端数据 to_client_send_data = input(f'请输入传递给客户端的数据:').strip() if not to_client_send_data: print(f"不允许传递空格") continue if to_client_send_data == "q": print(f'成功断开连接') to_client_send_data = to_client_send_data.encode() conn.send(to_client_send_data) break except Exception as e: break # 【八】关闭连接和服务 conn.close() server.close()
【3】解决办法
import json from conf import settings # 【一】引入socket模块 import socket import subprocess import uuid def run_cmd(command): result = subprocess.run( command, # 子进程要执行的命令 shell=True, # 执行的是shell的命令 # 存放的是执行命令成功的结果 stdout=subprocess.PIPE, # 存放的是执行命令失败的结果 stderr=subprocess.PIPE, encoding="gbk", timeout=1) # returncode属性是run()函数返回结果的状态。 if result.returncode == 0: return result.stdout else: return result.stderr # 【二】创建一个server对象 # AF_INET:当前连接是基于网络的套接字 # SOCK_STREAM:连接模式是TCP协议的流式模式 client = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) # 【三】绑定IP和PORT client.connect(settings.ADDR) while True: # 【四】直接发送数据 command = input("请输入需要执行的命令 :>>>> ").strip() if not command: print(f'不允许发送空的数据') continue if command == 'q': print(f'当前连接已退出!') break # 【1】执行本地的命令,获取到当前命令结果 result = run_cmd(command=command) # 【2】对命令的结果进行编码---> 转成二进制数据 result_bytes = result.encode() # 【3】计算长度 data_length = len(result_bytes) # 【4】增加一个数据概览 --> 字典格式 做数据概览 # 存储当前文件名 / 结果名 / md5加密盐(用来校验数据的完整性) salt = uuid.uuid4().hex encrypted = settings.encrypt_data(data=result_bytes, salt=salt) send_data_info = { 'command': command, 'data_length': data_length, 'salt': salt, 'encrypted': encrypted } # 【5】将上面打包好的数据全部发送给服务端 # (1)字典格式无法发送 # 将字典转换为字符串数据 ----> json # dump : 处理文件数据 # dumps : 做格式转换的 json_str = json.dumps(send_data_info) # (2)将json字符串数据转换为二进制数据 json_bytes = json_str.encode() # 【6】问题产生 # JSON字符串转换为的二进制数据还是会很长 # 让数据变短 # struct 模块 ---> 将某几个数字转换为四个字节的二进制数据 json_length_pack = settings.pack_data(data_length=len(json_bytes)) # 【7】发送struct打包的数据(四个字节) + JSON数据 + 原始数据 # JSON数据里面存的是所有数据信息而没有原始的二进制数据 # 服务端接受的顺序取决于客户端发送的顺序 # 先发送struct打包后的数据 client.send(json_length_pack) # 4 字节 --> 包含json二进制数据的长度 # 先发送 json_bytes 打包后的数据 client.send(json_bytes) # 不知道 # 再发送 result_bytes 原始数据 client.send(result_bytes) # 【五】接收到服务端回的消息 from_server_recv_data = client.recv(1024) from_server_recv_data = from_server_recv_data.decode() if from_server_recv_data == 'q': break print(f'这是来自服务端的数据 :>>>> \n{from_server_recv_data}') # 【六】关闭连接对象 client.close()
import json from conf import settings # 【一】引入socket模块 import socket # 【二】创建一个server对象 # AF_INET:当前连接是基于网络的套接字 # SOCK_STREAM:连接模式是TCP协议的流式模式 server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 【三】绑定IP和PORT server.bind(settings.ADDR) # 【四】监听连接对象 server.listen(5) # 【五】建立连接对象 # 放在这里 进入的 while 循环中的哪个连接对象会一致不变 conn, addr = server.accept() while True: try: # 这里面交流的对象永远是上面接收到的那一个 # conn, addr = server.accept() : 接收到新的对象,忘记上一个连接过的对象 # 【六】接收到客户端的数据 # 【1】先接接收四个字节的数据 ---> struct打包好的四个字节的数据 json_pack_data = conn.recv(4) if not json_pack_data: break json_bytes_length = settings.unpack_data(data=json_pack_data) # 【2】根据json二进制数据长度解出JSON二进制数据 json_data_bytes = conn.recv(json_bytes_length) # 【3】将json二进制数据转为json字符串数据 json_str = json_data_bytes.decode() # 【4】将json字符串数据转换为python的字典 data_info = json.loads(json_str) # 【5】从字典中获取自定的参数 # 获取到总的数据长度 # 10000 data_length = data_info.get('data_length') # 【6】定义参数 # (1)总数据 all_data = b'' # (2)每次接收的数据大小 size = 1024 # data_length : 5 # size : 2 # count : 2 , last_size : 1 count, last_size = divmod(data_length, size) # (3)已经接受的数据大小 all_size = 0 while all_size < count + 1: all_size += 1 # 接收到每一次的数据并和总数据拼接 if all_size == count + 1: all_data += conn.recv(last_size) else: all_data += conn.recv(size) from_client_recv_data = all_data.decode() print(f'这是来自客户端的数据 :>>>> \n {from_client_recv_data}') # 【七】返回给客户端数据 while True: to_client_send_data = input("请输入发送给客户端的数据 :>>>> ").strip() if not to_client_send_data: print(f'不允许发送空的数据') continue if to_client_send_data == 'q': print(f'当前连接已退出!') to_client_send_data = to_client_send_data.encode() conn.send(to_client_send_data) break except Exception as e: break # 【八】关闭连接和服务 conn.close() server.close()
【4】subprocess模块回顾
import subprocess def run_cmd(command): result = subprocess.run( command, # 子进程要执行的命令 shell=True, # 执行的是shell的命令 # 存放的是执行命令成功的结果 stdout=subprocess.PIPE, # 存放的是执行命令失败的结果 stderr=subprocess.PIPE, encoding="gbk", timeout=1) # returncode属性是run()函数返回结果的状态。 if result.returncode == 0: return result.stdout else: return result.stderr if __name__ == '__main__': print(run_cmd(['dir']))
【5】struct模块介绍
# 【一】模块介绍 # ● struct.pack()是Python内置模块struct中的一个函数 # ● 它的作用是将指定的数据按照指定的格式进行打包 # 并将打包后的结果转换成一个字节序列(byte string),可以用于在网络上传输或者储存于文件中。 # 【二】参数简介 # struct.pack(fmt, v1, v2, ...) # ● 其中,fmt为格式字符串,指定了需要打包的数据的格式,后面的v1,v2,...则是需要打包的数据。 # ● 这些数据会按照fmt的格式被编码成二进制的字节串,并返回这个字节串。 # 【三】示例 import struct # 定义一个包含不同类型字段的格式字符串 format_string = 'i' # 示例数据:整数、四个字节的原始数据、短整数 data_to_pack = '十七dasdadsad asd 撒大撒多所adsaddasdadsa da dsa asad撒大大带我去大青蛙大大大大大萨达去问问恰饭恰饭放散阀昂发昂发沙发阿发发发放上千万请发送方三房启发法阿发发发ad sada dsa dsa dsa sa dsa dsa as ad sad ad ada顿撒大大三大撒打我前端' data_to_pack_bytes = data_to_pack.encode() data_to_pack_len = len(data_to_pack_bytes) print(data_to_pack_len) # 使用 struct.pack 将数据打包成二进制字节串 packed_data = struct.pack(format_string, data_to_pack_len) # 41000000 # 64000000 # 19010000 print("Packed data:", len(packed_data)) # 打印打包后的十六进制表示 # 解析二进制字节串,恢复原始数据 unpacked_data = struct.unpack(format_string, packed_data) # print("Unpacked data:", unpacked_data) # 打印解析后的数据
##