网络编程:socket套接字、通信循环、粘包问题、大文件上传

2022.4.15socket模块及粘包问题

  • socket套接字
  • 通信循环
  • 链接循环及代码优化
  • 粘包问题.
  • 大文件上传

一、socket套接字

1、socket套接字简介

需求:编写一个CS架构的程序,实现数据交互

思考:需要编写代码操作OSI七层 相当的复杂
由于操作OSI七层是所有cs架构的程序都需要经历的过程 所以有固定的模块

socket套接字技术

socket模块>>>:提供了快捷方式,不需要自己处理每一层

2、socket模块

(1)服务端

cs架构无论是编写还是运行,都应该先考虑服务端,就像有门店运营了才可以接待客人一样

import socket

server = socket.socket()  # 相当于买手机
"""通过查看源码得知 
括号内不写参数默认就是基于网络的遵循TCP协议的套接字"""
server.bind(('127.0.0.1', 8080))  # 相当于插电话卡
"""127.0.0.1是计算机的本地回环地址 只有当前计算机本身可以访问"""
server.listen(5)  # 相当于开机,数字是等待接收的客户端数量(半连接池)
sock, addr = server.accept()  # 相当于等待并接听电话,没有收到信息就原地等待(程序阻塞)
print(addr)  # 客户端地址
data = sock.recv(1024)  # 相当于听电话,接收信息,参数是接收的信息长度
print(data.decode('utf8'))  # 打印接收的信息,接收的信息需要解码
sock.send('你好啊'.encode('utf8'))  # 发送信息,同样需要编码
注意:recv和send接收和发送的都是bytes类型的数据
sock.close  # 相当于挂电话
server.close  # 相当于关机

(2)客户端

import socket

client = socket.socket()  # 产生一个socket对象
client.connect(('127.0.0.1', 8080))  # 根据服务端地址链接
client.send('你是谁'.encode('utf8'))  # 发送信息至服务端
data = client.recv(1024)  # 接收服务端回复的消息
print(data.decode('utf8'))  # 打印接收的消息
client.close()  # 关闭客户端

注意:服务端和客户端首次交互时必须一边时send,一边是recv,两边不能相同,不然程序就会停滞,都在接收信息

二、通信循环

上面的代码我们已经实现了客户端与服务端的交互,但是只能获取和接收一次,因此我们需要解决通信循环问题

1.先解决消息固定的问题
	利用input获取用户输入
2.再解决通信循环问题
	使用while循环语句
import socket
# 客户端
client = socket.socket()
client.connect(('127.0.0.1', 8080))
while True:
    msg = input('输入需要发送的信息>>>:').strip()
    client.send(msg.encode('utf8'))  # 发送信息
    data = client.rscv(1024)  # 接收信息
    print(data)  # 打印获取信息
client.close

import socket
# 服务端
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
sock,addr = server.accept()
while True:
    data = sock.recv(1024)
    print(data.decode('utf8'))
    msg = input('输入需要发送的信息>>>:').strip()
    sock.send(msg.encode('utf8'))
sock.close
server.close

三、链接循环及代码优化

问题1:

当我们正常循环代码时,如果客户端突然异常退出的话,服务端就会直接报错,遇到这种情况我们需要让服务端重新回到accept等待新的客户端接入,怎么做呢?

# windows 系统  >>>  报错
处理方式:异常处理
try:
	...
except exception:
    continue
# mac或linux系统  >>>  不会报错,而是接收空消息
处理方式:len判断
data = sock.recv(1024)
if len(data) == 0:
    continue

问题2:

反复重启服务端可能会报错>>>:address in use
这个错在苹果电脑报的频繁 windows频率较少

from socket import SOL_SOCKET,SO_REUSEADDR  # 先调用模块

server = socket.socket()
server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) # 再在bind前加这个固定搭配
server.bind(('127.0.0.1', 8080))
...

问题3:

发送消息为空时,如何交互

解决方案:len判断长度,为0则直接返回

四、半连接池

服务端使用这种方法是不能同时接收多个客户端消息的,必须要一个个接收,而且接收也是有限制的,这个限制就是listen的参数,这个参数就是最大等待人数,这就是半连接池的概念

# 半连接池
即设置的最大等待人数 >>>:  节省资源,提高效率

server.listen(5)  # # 除当前接收的客户端外,还允许接收5个,这5个为等待状态 

五、黏包问题及解决方法

1、粘包问题

TCP协议的特点:

会将数据量比较小并且时间间隔比较短的数据整合到一起发送,并且还会受制于recv括号内的数字大小(核心问题!!!)
流式协议:跟水流一样不间断

eg:


# 客户端
client.send(b'hello')
client.send(b'jason')
client.send(b'kevin')
# 服务端
data1 = rock.recv(1024)
print(data1)
data2 = rock.recv(1024)
print(data2)
data3 = rock.recv(1024)
print(data3)
"""
三次打印的结果
  b'hellojasonkevin'
  b''
  b''
"""

可见不管发送几次消息,都会被整合成一个信息,并且我们不知道接收的信息长度,所以无法判断recv()括号里面该接收多长的信息,反之,如果我们如果能够精确知道它的大小,那么肯定不会出现黏包

2、解决粘包问题

方向:精准获取数据的大小

import stuuct  # 调用struct模块

data1 = 'hello world!'
print(len(data1))  # 12
res1 = struct.pack('i', len(data1))  # 打包,第一个参数是格式,写i就可以
print(len(res1))  # 4  ,打包后长度为4
ret1 = struct.unpack('i', res1)  # 拆包,拆包后是一个元组
print(ret1)  # (12,)

data2 = 'hello baby baby baby baby baby baby baby baby'
print(len(data2))  # 45
res2 = struct.pack('i', len(data2))
print(len(res2))  # 4
ret2 = struct.unpack('i', res2)
print(ret2)  # (45,)

结论:

pack可以将任意长度的数字打包成固定长度
unpack可以将固定长度的数字解包成打包之前数据真实的长度

可以考虑使用字典封装数据然后再打包

注意:

recv括号内的数字尽量不要写太大 1024 2048 4096足够了
字典数据很难突破上面的数值

思路:
    1.先将真实数据打包成固定长度的包
    2.将固定长度的包先发给对方
    3.对方接收到包之后再解包获取真实数据长度
    4.接收真实数据长度

终极解决方案
# 服务端
    1.先构造一个字典 
        内部存档了真实数据相关的信息
            大小 名称 简介 ...
    2.对字典做打包处理
    3.将固定长度的数据(字典)发送给对方
    4.发送真实的字典数据
    5.发送真实的真正数据
    
# 客户端    
    1.先接收固定长度的字典包
    2.解析出字典的真实长度
    3.接收字典数据
    4.从字典数据中解析出各种信息
    5.接收真实的数据

data_dict = {
    'file_name': 'XXX合集.zip',
    'file_desc': '营养快线',
    'file_size': 321312312312312312312312312
}
print(len(data_dict))  # 3
res = struct.pack('i', len(data_dict))
print(len(res))  # 4
ret = struct.unpack('i', res)
print(ret)  # (3,),元组形式,3是原数据的真实长度
posted @ 2022-04-15 23:38  马氵寿  阅读(159)  评论(0)    收藏  举报