概要

socket套接字简介

socket模块

通信循环

优化代码及链接循环

半连接池

粘包问题

解决粘包问题

socket套接字简介

# 需求:编写一个C/S架构的程序 实现数据交互
思考:我们需要编写代码操作的OSI协议 操作起来相当复杂
    由于操作OSI七层是所有的CS架构的程序都要经历的过程 所有想是不是有固定的模块
    socket是一门计算机技术 在python中使用需要使用socket模块
    socket模块》》》:提供了快捷方式 需要自己去处理每一层
        
    以后们写软件连socket的影子都看到不到了 因为被封装了起来
    socket是最底层的原理 很多框架都已封装好了 其实我们不需要输入研究

android socket黏包 禁用NAGLE socket 粘包_服务端

socket模块

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()  # 等待并接听电话 没有人来就原地等待(程序阻塞)
# listen和accept对应TCP三次握手 服务端两种状态
print(addr)  # 客户端地址
data = sock.recv(1024)  # 听别人说话
print(data.decode('utf8'))
sock.send('您好啊'.encode('utf8'))  # 回复别人说的话
# recv和send接收和发送的都是bytes类型的数据
sock.close() # 挂电话
server.close()  # 关机

# 用户端
import socket
client = socket.socket # 产生一个socket的对象
client.connect('127.0.0.1',8080)  # 根据服务端的地址链接
client.send(b'hello sweet heart')  # 给服务端发送消息
data = client.recv(1024)  # 接收服务端回复的消息
print(data.decode('utf8'))
client.close()  # 关闭客户端

"""
服务端与客户端首次交互 一定要是一边是recv 那么另外一边必须是send 两边不能相同 否则就是冷战了"""

android socket黏包 禁用NAGLE socket 粘包_数据_02

通信循环

1.先解决消息固定的问题
利用的是input获取用户输入
2.再解决通信循环的问题
将双方用于数据交互的代码循环起来

while True:
    data = sock.recv(1024)
    print(data.decode('utf8'))
    msg = input('请回复消息>>>>:').strip()
    sock.send(msg.encode('utf8'))
    
while True:
    msg =input('请输入需要发送的消息').strip()
    client.send(msg.encode('utf8'))  # 给服务端消息
    data =client.recv(1024)  # 接收服务端回复的消息
    print(data.decode('utf8'))

android socket黏包 禁用NAGLE socket 粘包_客户端_03

优化代码及链接循环

1.发送消息不能为空
统计长度并加判断即可 if len(msg)== 0:continue
2.反复重启服务端可能会报错:address in use
    这个错在苹果电脑报的频繁 windows频率较少
    只要在下面加上这两句话即可解决"""
    from socket import SOL_SOCKET,SO_REUSEADDR
    server.setsocket(SOL_SOCKET,SO_REUSEADDR,1)  # 第二行这句话加在bind前加""" 
3.链接循环
客户端如果异常断开 服务端代码应该重新回到accept等待新的客人
3.1.如果是Windows 客户端异常退出之后 下面的代码走不了,服务端会直接报错 解决办法:异常处理 利用try......Except进行异常处理 然后在代码下面输入break 在while下面加上try:  捕获处理在最底下except Exception:break 万能异常处理
3.2 如果是mac或inux 服务端会接收到一个空消息 表示客户端断开了 直接结束循环break 不会报错

# 客户端如果异常断开 服务端代码应该重新回到accept等待新的客人 所以在加一个while True: 将内部while True 小循环退出循环 这样就可以接待更多的客人

"目前我们的服务只能实现一次服务一个人 不能做到同时服务多个 学了并发才可以实现"

android socket黏包 禁用NAGLE socket 粘包_客户端_04

半连接池

listen(5)
# py文件默认同一时间默认只能运行一次  如果想单独分开运行多次 点击客户端里面的edit configrations》》》勾上allow paraller run就可以了

半连接池 取决于listen 数字是几 表示的是在沙发上还可以坐几个人等待 
是指服务端设置的最大等待人数 除了正在客户端运行的第一个 listen(5)表示的是服务端在门口放了一张沙发最多还可以坐5个人继续等待第一个结束后再往下执行

粘包问题

服务端
data1 = conn.recv(1024)
print(data1)
data2 = conn.recv(1024)
print(data2)
data3 = conn.recv(1024)
print(data3)

客户端
client.send(b'hello')
client.send(b'jason')
client.send(b'kevin')

三次打印的结果
 b'hellojasonkevin'
 b''
 b''
 # TCP协议的特点
	会将数据量比较小并且时间间隔比较短的数据集合到一起发送
    如果数据长度超过recv内的数字时 只会打印部分 不会全部打印出来
    并且还会受制于recv括号内的数字大小(核心问题!!!)
    流式协议:跟水流一样不间断
        
        
 """
 问题的产生的原因其实是因为recv括号内我们不知道道即将要接收的数据到底有多大
 如果每次接收的数据我们都能够精确的知道它的大小 那么肯定不会出现粘包"""

思路:
    困扰我们的核心问题就是不知道即将要接收的数据多大 
    如果能够精准的知道数据量有多大 那么粘包问题就会自动解决了

解决粘包问题

方向:精准获取数据的大小
    
# struct模块
import struct
data1 = 'hello world'
print(len(data1))  # 字节数为12
res1 = struct.pack('i',len(data1))  # 第一个参数是格式 写i就可以了 包起来
print(len(res1))  # 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可以将任意长度的数字打包成固定长度4
    unpack可以将固定长度的数字解包成打包之前数据真实的长度
    
 """
 思路:
 	1.先将真实数据打包成固定长度的包
 	2.将固定长度的包先发给对方
 	3.对方接收到包之后再解包获取真实数据长度
 	4.接收真实数据长度"""

代码演示

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

所以针对大文件的接收应该采用循环的形式一次接受一点点

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

用户端
1.先接收固定长度的字典包
2.解析出字典的真实长度
3.接收字典数据
4.从字典中解析出各种信息
5.接收真实的数据
文件路径:绝对路径地址
服务端
import socket
import os
import json

server.bind('127.0.0.1',8080)
server.listen(5)

conn.addr = server.accept()
while True:
    data_dict ={
        'file_name':***.txt,
        'file_desc':'风控了一个多月了',
        'file_size':os.path.getsize('文本绝对路径')
    }
 # 1.先打包字典
	dict_json =json.dumps(data_dict)
    dict_bytes = dict_json_str.encode('utf8')
	dict_package =struct.pack('i',len(dict_bytes))
  # 2.发送报头
	conn.send(dict_package_haeder)
  # 3.发送字典
	conn.send(dict_bytes)
 # 4.发送真实数据
     with open(r'struct模块所在文件','rb') as f:
        for line in f:
            conn.send(line)

客户端
import socket
client = socket.socket()
client.connect('127.0.0.1',8080)
1.先接收固定长度的字典的报头
dict_header_len =client.recv(4)
2.解析出字典的真实长度
dict_real_len = struct.unpack('i',dict_header_len[0])
3.接收字典数据
dict_data_bytes = client.recv(dict_real_len)
dict_data =json.loads(dict_data_bytes)
4.循环接收文件数据 不要一次性接收
recv_size =0
with open(dict_data.get('file_name'),'wb') as f:
    while recv_size < dict_data.get('file_name'):
        data =client.recv(1024)  # 防止内存崩掉
        recv_size += len(data)
        f.write(data)