网络编程 TCP协议:三次握手,四次挥手,反馈机制 socket套接字通信 粘包问题与解决方法
TCP协议:传输协议,基于端口工作
三次握手,四次挥手
TCP协议建立双向通道。
三次握手, 建连接:
1:客户端向服务端发送建立连接的请求
2:服务端返回收到请求的信息给客户端,并且发送往客户端建立连接的请求
3:客户端接收到服务端发来的请求,返回接成功给服务端,完成双向连接
第一客戶向服务端发送请求,请求建立连接
服务端同客户端的请求,并同时向客户端发送建立
连接的请求,最后客户端同意后建立 双向连接。
C ----> S
C <---- S
- 反馈机制:
客户端往服务端发送请求,服务端必须返回响应,
告诉客户端收到请求了,并且将服务端的数据一并返回给客户端。
C-->S: 一次请求,必须有一次响应。
- 缺点:
- 洪水攻击:
指的是通过伪造大量的请求,往对方服务器发送请求,
导致对方服务器响应跟不上,以至于瘫痪。
linux系统有个参数可以限制。
- 半连接池listen: 限制用户在同一个时间段内的访问数量。
四次挥手, 断开连接:
1:客户端向服务端发送断开连接的请求
2:服务端返回收到请求的信息给客户端
3:服务端确认所有数据接收完成以后,再发送同意断开连接的请求给客户端
4:客户端返回收到断开连接的请求,给服务端。
socket套接字通信:
- 什么是socket?
socket是一个模块, 又称套接字,用来封装 互联网协议(应用层以下的层)。
- 为什么要有socket?
socket可以实现 互联网协议应用层以下的层的工作。
- 提高开发效率
- 怎么使用socket?
import socket
写socket套接字:
Client
Server
socket初级版
注意:server和client的发送接收命令必须一一对应,recv接收命令必须收到内容能会运行
windows系统下,即使发送消息为空,也有数据头,接收消息不为空;而linux下,接收消息则会为空
''' 先启动套接字服务端 server代码 ''' import socket # 买手机 server = socket.socket() # 绑定手机卡 server.bind( # 相当于手机号码 # 127.0.0.1 == localhost 本机回环地址 # 单机测试下: 127.0.0.1 ('127.0.0.1', 9527) ) # 半连接池 server.listen(5) # 最多5个人坐椅子, 实际上==6 print( 'server is running...' ) # 等待电话接入 # conn: 指的是服务端往客户端的管道 conn, addr = server.accept() # 接听对方讲话的内容 # data客户端发送过来的消息 data = conn.recv(1024) # 可接受一次1024bytes的数据 print(data) # 挂电话 conn.close()
''' 启动服务端后再启动客户端 Client代码 ''' import socket # 买手机 client = socket.socket() # 拨号 client.connect( # 客户端的ip与port必须与服务端一致 ('127.0.0.1', 9527) ) print( 'client is running...' ) # 必须发送bytes类型的数据 # client.send('hello'.encode('utf-8')) # 讲话给对方听 client.send(b'hello')
2)socket升级版
''' 注意: 客户端先一次发送,服务端得先一次接受,再发送消息。 ''' import socket # 买手机 server = socket.socket() # 绑定手机卡 server.bind( # 相当于手机号码 # 127.0.0.1 == localhost 本机回环地址 # 单机测试下: 127.0.0.1 ('127.0.0.1', 9527) ) # 半连接池 server.listen(5) # 最多5个人坐椅子, 实际上==6 print('server is running...') # 等待电话接入 # conn: 指的是服务端往客户端的管道 conn, addr = server.accept() # 接听对方讲话的内容 # data客户端发送过来的消息 data = conn.recv(1024) # 可接受一次1024bytes的数据 print(data) # 服务端往客户端发送消息 conn.send(b'hi im server') # 挂电话 conn.close()
''' 启动服务端后再启动客户端 ''' import socket # 买手机 client = socket.socket() # 拨号 client.connect( # 客户端的ip与port必须与服务端一致 ('127.0.0.1', 9527) ) print('client is running...') # 必须发送bytes类型的数据 # client.send('hello'.encode('utf-8')) # 讲话给对方听 client.send(b'hello im client...') data = client.recv(1024) print(data) client.close()
3)socket升级版
''' 注意: 客户端先一次发送,服务端得先一次接受,再发送消息。 server ''' import socket # 买手机 server = socket.socket() # 绑定手机卡 server.bind( # 相当于手机号码 # 127.0.0.1 == localhost 本机回环地址 # 单机测试下: 127.0.0.1 ('127.0.0.1', 9527) ) # 半连接池 server.listen(5) # 最多5个人坐椅子, 实际上==6 print('server is running...') # 等待电话接入 # conn: 指的是服务端往客户端的管道 conn, addr = server.accept() while True: # 接听对方讲话的内容 # data客户端发送过来的消息 data = conn.recv(1024) # 可接受一次1024bytes的数据 if len(data) == 0: # 没有意义,windows系统下不可能为0 break if data.decode('utf-8') == 'q': break print(data) send_data = input('服务端>>>:') # 服务端往客户端发送消息 conn.send(send_data.encode('utf-8')) # 挂电话 conn.close()
''' 启动服务端后再启动客户端 client ''' import socket # 买手机 client = socket.socket() # 拨号 client.connect( # 客户端的ip与port必须与服务端一致 ('127.0.0.1', 9527) ) print('client is running...') # 必须发送bytes类型的数据 # 讲话给对方听 while True: send_data = input('客户端>>>:') client.send(send_data.encode('utf-8')) data = client.recv(1024) if data.decode('utf-8') == 'q': break if len(data) == 0: break print(data) client.close()
4)socket最终版
''' 注意: 客户端先一次发送,服务端得先一次接受,再发送消息。 server ''' import socket # 买手机 server = socket.socket() # 绑定手机卡 server.bind( # 相当于手机号码 # 127.0.0.1 == localhost 本机回环地址 # 单机测试下: 127.0.0.1 ('127.0.0.1', 9527) # 局域网内测试 # ('192.168.12.202', 9527) ) # 半连接池 server.listen(5) # 最多5个人坐椅子, 实际上==6 print('server is running...') # 循环实现可接受多个用户访问 while True: # 等待电话接入 ---> 接入客户端 # conn: 指的是服务端往客户端的管道 conn, addr = server.accept() print(addr) # 循环实现循环通信 while True: try: # 监听代码块是否有异常出现 # 接听对方讲话的内容 # data客户端发送过来的消息 data = conn.recv(1024) # 可接受一次1024bytes的数据 if len(data) == 0: break if data.decode('utf-8') == 'q': break print(data.decode('utf-8')) send_data = input('服务端>>>:') # 服务端往客户端发送消息 conn.send(send_data.encode('utf-8')) # 捕获异常信息,并打印 # e: 报错信息 except Exception as e: print(e) break # 挂电话 conn.close()
''' 启动服务端后再启动客户端 client ''' import socket # 买手机 client = socket.socket() # 拨号 client.connect( # 客户端的ip与port必须与服务端一致 ('127.0.0.1', 9527) ) print('client is running...') # 必须发送bytes类型的数据 # 讲话给对方听 while True: send_data = input('客户端>>>:') client.send(send_data.encode('utf-8')) data = client.recv(1024) if data.decode('utf-8') == 'q': break if len(data) == 0: break print(data.decode('utf-8')) client.close()
3.粘包问题
- 1) 问题: 无法确认对方发送过来数据的大小。
# server import socket import subprocess server = socket.socket() server.bind( ('127.0.0.1', 9000) ) server.listen(5) while True: conn, addr = server.accept() print(addr) while True: try: # recv从内存中获取数据 cmd = conn.recv(10) if len(cmd) == 0: continue cmd = cmd.decode('utf-8') # dir if cmd == 'q': break # 调用subprocess连接终端,对终端进行操作,并获取操作后正确或错误的结果 obj = subprocess.Popen( # cmd接收解码后的字符串 cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) # 结果交给result变量名 result = obj.stdout.read() + obj.stderr.read() print(len(result)) # gbk print(result.decode('gbk')) # 将结果返回给客户端 conn.send(result) except Exception as e: print(e) break conn.close()
#client import socket client = socket.socket() client.connect( ('127.0.0.1', 9000) ) while True: cmd = input('客户端输入的内容: ') client.send(cmd.encode('utf-8')) data = client.recv(19190) # 无法估计大小 print(len(data)) print(data.decode('gbk'))
- 2) 问题: 在发送数据间隔短并且数据量小的情况下,
会将所有数据一次性发送。
# server import socket server = socket.socket() server.bind( ('127.0.0.1', 9000) ) server.listen(5) conn, addr = server.accept() data = conn.recv(1024) print(data) # b'hellohellohello' data = conn.recv(1024) print(data) # b'' data = conn.recv(1024) print(data) # b''
# client import socket client = socket.socket() client.connect( ('127.0.0.1', 9000) ) client.send(b'hello') client.send(b'hello') client.send(b'hello')
解决: 确认对方数据的大小。
4.解决粘包问题(struct模块)
- 无论哪一端先发送数据
- 客户端
- 1) 先制作报头,并发送 (struct)
- 2) 发送真实数据
- 服务端:
- 1) 接收报头,并解包获取 真实数据长度
- 2) 根据真实数据长度 接收真实数据
recv(真实数据长度)
''' 1.struct是什么? 是一个python内置的模块, 它可以将固定长度的数据,打包成固定格式的长度。 # 模式 i: 4 # 其他模式 2.作用: 可以将真实数据,做成一个固定长度多的报头,客户端发送给服务端,服务端可以接收报头, 然后对报头进行解包,获取真实数据的长度,进行接收即可。 ''' import struct data = b'11111111111111' print(len(data)) # 打包制作报头 header = struct.pack('i', len(data)) print(header) print(len(header)) # 解包获取真实数据长度 ---> 得到一个元组,元组中第一个值是真实数据的长度 res = struct.unpack('i', header)[0] print(res)
案例一:
# server import socket import subprocess import struct server = socket.socket() server.bind( ('127.0.0.1', 9000) ) server.listen(5) while True: conn, addr = server.accept() print(addr) while True: try: # 获取客户端传过来的报头 header = conn.recv(4) # 解包获取真实数据长度 data_len = struct.unpack('i', header)[0] # 准备接收真实数据 cmd = conn.recv(data_len) if len(cmd) == 0: continue cmd = cmd.decode('utf-8') # dir if cmd == 'q': break # 调用subprocess连接终端,对终端进行操作,并获取操作后正确或错误的结果 obj = subprocess.Popen( # cmd接收解码后的字符串 cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) # 结果交给result变量名 result = obj.stdout.read() + obj.stderr.read() # 二进制,不用转码 print('发给客户端端的真实数据长度', len(result)) # gbk # print(result.decode('gbk')) # 将结果返回给客户端 # 做一个报头, 返回给客户端 header = struct.pack('i', len(result)) print(len(header)) conn.send(header) conn.send(result) except Exception as e: print(e) break conn.close()
#client import socket import struct client = socket.socket() client.connect( ('127.0.0.1', 9000) ) while True: cmd = input('客户端输入的内容: ') cmd_bytes = cmd.encode('utf-8') # 做一个报头 header = struct.pack('i', len(cmd_bytes)) print(len(header)) client.send(header) # 待服务端确认长度后,发送真实数据长度 client.send(cmd_bytes) # 接收服务端返回的报头 headers = client.recv(4) # 解包,接收服务端返回的真实数据 data_len = struct.unpack('i', headers)[0] result = client.recv(data_len) print('接收服务端返回的真实数据长度', len(result)) print(result.decode('gbk'))
案例二:
#server import socket import json import struct server = socket.socket() server.bind( ('127.0.0.1', 9000) ) server.listen(5) while True: conn, addr = server.accept() print(addr) while True: try: # 获取客户端传过来的报头 header = conn.recv(4) # 解包获取真实数据长度 json_len = struct.unpack('i', header)[0] # 接收json(dict)的真实长度 json_bytes_data = conn.recv(json_len) # 将bytes类型数据转为json数据 json_data = json_bytes_data.decode('utf-8') # 反序列化 json ---> dict back_dic = json.loads(json_data) print(back_dic) print(back_dic.get('movie_len')) # 准备接收真实数据 # movie_data = conn.recv(back_dic.get('movie_len')) # print(movie_data) except Exception as e: print(e) break conn.close()
#client import socket import struct import json client = socket.socket() client.connect( ('127.0.0.1', 9000) ) while True: movie_name = input('请输入上传电影的名字: ') # 伪装电影真实数据 movie_len = 1000000 send_dic = { 'movie_name': movie_name, 'movie_len': movie_len } # 序列化 json = json.dumps(send_dic) print(json) print(json.encode('utf-8')) print(len(json.encode('utf-8'))) json_bytes = json.encode('utf-8') # 做一个报头 header = struct.pack('i', len(json_bytes)) # 先发送报头 client.send(header) # 后发送真实数据 client.send(json_bytes)