网络并发编程02--socket&粘包

1. socket 套接字编程

# 要求:自己想写一款可以数据交互的程序。
只要涉及到远程数据交互,必须要操作OSI七层,所有由现成的模块直接实现。

socket模块

架构启动一定是先启动服务端,再启动客户端。

image


#   服务端

import socket

# 阅读源码
sock = socket.socket()  # 默认是基于网络的TCP传输协议
sock.bind(('127.0.0.1', 8001))  # 绑定IP和port,插卡
sock.listen(5)  # 半连接池  开机

conn, addr = sock.accept()  # 监听  三次握手的listen态

# -> 箭头函数,后面相当于他的返回值
# :  冒号表示,后面的数据类型

print(addr)  # 客户端地址
# conn 是一个对象,可以做一些操作
data = conn.recv(1024)  # 接收客户端发送的消息,听别人说话
print(data)

conn.send(b'hello world!')  # 给被人回话

conn.close()  # 挂电话
sock.close()  # 关机


扩展

# 在阅读源码的时候

"1. -> 函数后面跟横杠加大于号,表示的是函数的返回值类型"

"2. 变量名后面跟冒号 : , 表示的意思是该变量名需要指代的数据类型"

image

image


# 客户端

import socket

client = socket.socket()
client.connect(('127.0.0.1', 8001))  # 直接拨号

# 讲话
client.send(b'hello big big!!')

# 听
data = client.recv(1024)
print(data)

client.close()

2. 通信循环 & 代码优化

image

  1. 客户端校验消息不能为空。
  2. 服务端添加兼容性代码(mac Linux)
  3. 服务端重启频繁报端口占用错误
    from socket import SOL_SOCKET, SO_REUSEADDR
    
    server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    # 加在bind前面
    
    
  4. 客户端异常关闭,服务端报错的问题,可以用异常捕获
  5. 服务端连接循环
  6. 半连接池
    设置可以等待的客户端数量

# 服务端

import socket
from sokcet import SOL_SOCKET, SO_REUSEADDR

sock = socket.socket()
sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
sock.bind(('127.0.0.1', 8001))
sock.listen(5)  # 半连接池

while True:  # 针对问题5优化
    conn, addr = sock.accept()
    print(addr)
    while True:
        try:
            data = conn.recv(1024)
            """
            针对mac和Linux的优化
            if len(data) == 0:
                continue
            """
            print(data.decode())
            conn.send(data+b'123')
        except ConnectionResetError as e:
            print(e)
            break

conn.close()
sock.close()

3. 粘包问题

  1. 数据管道的数据没有被完全取出,从而导致数据残留,被下一次数据取到。
  2. TCP协议有一个特性:
    当数据量比较小,且时间间隔比较短的多次数据,
    TCP会自动打包成一个数据包发送
  3. 报头
    能够标识即将到来的数据具体信息(数据量大小)
    报头的长度必须是固定的。

示例一:

import subprocess

sub = subprocess.Popen("ipconfig", shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)

print(sub.stdout.read().decode('gbk'))
print(sub.stderr.read().decode('gbk'))


# 客户端

import socket

client = socket.socket()
client.connect(("127.0.0.1", 8001))  # 直接拨号

while True:
    # 说话
    msg = input('请输入命令>>>:').strip()
    if len(msg) == 0:
        continue

    
    client.send(msg.encode('gbk'))

    # 听
    data = client.recv(1024)
    print(data.decode('gbk'))

client.close()




# 服务端
import socket
import subprocess



sock = socket.socket()
sock.bind(("127.0.0.1", 8001))
sock.listen(5)  # 半连接池


while True:  # 针对问题5优化
    conn, addr = sock.accept()
    print(addr)
    while True: 
        try:
            data = conn.recv(1024)
            """
            针对mac和Linux的优化
            if len(data) == 0:
                continue
            """
            command_cmd = data.decode('gbk')
            sub = subprocess.Popen(command_cmd, shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)data.decode('gbk')
            res = sub.stdout.read() + sub.stderr.read()
            
            conn.send(res.encode('gbk'))
            
        except ConnectionResetError as e:
            print(e)
            break

conn.close()



示例二:

# 服务端

import socket

sock = socket.socket()
sock.bind(('127.0.0.1', 8888))
sock.listen(5)

conn, addr = sock.accept()

data = conn.recv(1024)
print(data)
data = conn.recv(1024)
print(data)
data = conn.recv(1024)
print(data)



# 客户端
import socket

client = socket.socket()
client.connect(('127.0.0.1', 8888))

client.send(b'hello')
client.send(b'jsono')
client.send(b'oscar')



4. struct模块解决粘包


import struct

# ########### 数值转换为固定4个字节,四个字节的范围 -2147483648 <= number <= 2147483647 ########### 

v1 = struct.pack('i', 199)
print(v1) # b'\xc7\x00\x00\x00' 
for item in v1:
	print(item, bin(item))
# ########### 4个字节转换为数字 ########### 
v2 = struct.unpack('i', v1)
# v1= b'\xc7\x00\x00\x00'
print(v2)
# (199,)

简易版报头

"服务端"

import socket
import subprocess
import struct
import json


sock = socket.socket()
sock.bind(('127.0.0.1', 8008))
sock.listen(5)

while True:
    conn, addr = sock.accept()
    while True:
        data = sock.recv(1024)  # 接收cmd命令
        command_cmd = data.decode('utf8')
        sub = subprocess.Popen(command_cmd, shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
        res = substdout.read() + sub.stderr.read()

        # 1. 制作报头
        data_fir = struct.pack('i', len(res))
   
        # 2. 发送报头
        sock.send(data_fir)  # 数字
	
        # 3. 发送数据
        sock.send(res)




"客户端"
import socket
import struct


client = socket.socket()
client.connect(('127.0.0.1', 8008))

while True:
    msg = input('请输入cmd命令>>>:').strip()
    if len(msg) == 0:
        continue

    client.send(msg.encode())
    # 1. 先接收固定长度为4的报头数据
    recv_fir = client.recv(4)

    # 2. 解析报头
    real_length = struct.unpack('i', recv_fir)[0]
	
    # 3. 接收真实数据
    real_data = client.recv(real_length)
    print(real_data.decode('gbk'))


高级版本

服务端


import socket
import subprocess
import struct
import json


sock = socket.socket()
sock.bind(('127.0.0.1', 8080))
sock.listen(5)

while True:
    conn, addr = sock.accept()
    while True:
        data = sock.recv(1024)
        command_cmd = data.decode('utf8')
        sub = subprocess.Popen(command_cmd, shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
        res = substdout.read() + sub.stderr.read()

        # 1. 定义一个字典数据
        data_dict = {
            'desc':'重要数据',
            'size':len(res),
            'info':'哈喽啊啊啊啊啊'
        }
        data_json = json.dumps(data_dict)
        
        # 2. 制作字典报头
        data_fir = struct.pack('i', len(data_json))

        # 3. 发送字典报头
        sock.send(data_fir)  # 数字
		
        # 4. 发送字典
        sock.send(data_json.encode('utf8'))
		
        # 5. 发送真实数据
        sock.send(res)
		
		

客户端


import socket
import struct


client = socket.socket()
client.connect(('127.0.0.1', 8080))

while True:
    msg = input('请输入cmd命令>>>:').strip()
    if len(msg) == 0:
        continue

    client.send(msg.encode())
    # 1. 先接收固定长度为4的报头数据
    recv_fir = client.recv(4)

    # 2. 解析字典报头
    dict_length = struct.unpack('i', recv_fir)[0]
	
    # 3. 接收字典数据
    real_data = client.recv(dict_length)
   
    # 4. 解析字典
    "json格式的bytes数据,loads自动解码,先解码再反序列化"
    real_dict = json.loads(real_data)
    print(real_dict)

    # 5. 获取字典中的各项数据
    data_length = real_dict.get('size')
   
    data_bytes = client.recv(data_length)
    print(data_bytes.decode('gbk'))

posted @ 2022-01-12 16:59  Joshua_jiaxue  阅读(46)  评论(0编辑  收藏  举报