python之路---网络篇之socket

什么是socket

socket是应用层与传输层之间(TCP/IP通信协议)的中间软件抽象层,它是一组接口,将复杂的TCP/IP等协议隐藏在简单的接口之后,我们只要使用socket去编程,自然而然就是遵循TCP/UDP协议的。

两个家族

基于文件型的(AF_UNIX)与基于网络型的(AF_INET)

 套接字工作流程

要使用套接字,肯定要知道其工作流程

基本模板

# 对于服务端
# 获得TCP套接字对象(TCP协议也称之为流式协议,UDP也称之为数据报协议)
sk_obj = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

# 绑定IP及端口
sk_obj.bind(('ip',port))

# 开启监听模式(设置半连接池)
sk_obj.listen(5)

#等待请求(建立通信双向通道,conn为建立的通道对象,client..为客户端地址)
conn,client_address = sk_obj.accept()

# 收消息
data = conn.recv(1024) # 设置一次最大接收1024个字节

# 发消息
conn.send() # 发送的数据为bytes类型

# 关闭连接及回收资源
conn.close()
sk_obj.close()


# 对于客户端
# 获得TCP套接字对象
sk_obj = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

# 向服务端发送连接请求
sk_obj.connect((server_ip,server_port))

# 发消息
sk_obj.send()

# 收消息
data = sk_obj.recv(1024)

sk_obj.close()

基于socket实现简单通信

# Server
import socket

obj = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print('开始接收客户端的请求....')

# obj.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)  #重用端口
obj.bind(('127.0.0.1', 8080))
obj.listen(5)

while True:
    conn, address = obj.accept()
    while True:
        try:
            data = conn.recv(1024)  # 等待接收(必须接收大于0个bytes数据,否则一直等待)
            if not data:break  # 用于linux等系统客户端单方面退出,服务端收空的场景
            print('来自客户端的数据>>>%s' % data.decode('utf-8'))
            conn.send(data.upper())
        except ConnectionResetError:
            break
    conn.close()
obj.close()



# Client
import socket

obj = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
obj.connect(('127.0.0.1', 8080))

while True:
    to_data = input('输入发送的数据>>>').strip()
    if not to_data:
        continue
    # 如果没有这个判断,客户端发空会卡住--->原因
    '''
    我们是在应用层进行操作,发送数据的过程是,在客户端的内存中(这里是pychrm应用软件的内存)产生一个变量,并将其转换为
    bytes类型,复制并发送给操作系统,操作系统根据TCP协议/IP协议/以太网协议等对数据进行封装,最后在物理层打散发送给网卡,
    由网卡根据路由协议发送给对方的网卡(己方操作系统并不会立马清除数据),再一层层的解包到对方操作系统的缓存区,服务端从操作系统那里
    拿到数据,并通过操作系统--网卡给对方反馈响应,对方才将数据清空(这也是TCP协议可靠的原因),那么当客户端发空的时候,操作系统
    是不会发数据给网卡的,而客户端在等待接收服务端的数据,服务端也在等待客户端发送数据
    '''
    obj.send(to_data.encode('utf-8'))
    data = obj.recv(1024)
    print('来自服务端的数据>>>%s' % data)

obj.close()

此外值得注意的是:send操作是将应用软件的数据发送给操作系统(应用软件不能直接和硬件打交道),而recv操作也是应用软件从操作系统的缓冲区拿数据

粘包问题

要知道的是,粘包问题来自于TCP,UDP没有粘包问题

TCP是流式协议,是面向连接的,当对方send一条消息时,无论底层如果分段,TCP协议层会把构成整体消息的数据段排序完后再呈现再内核缓冲区

UDP是数据报协议,面向消息的,无连接的,不存在发空(有报头),是有消息保护边界的

由发送方引起的粘包问题

nagle算法规定了在数据传输的过程中,会将数据量较小,时间间隔较短的多条消息合并发送

# Server
import socket

sk_obj = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sk_obj.bind(('127.0.0.1', 8080))
sk_obj.listen(5)

conn, client = sk_obj.accept()
data1 = conn.recv(1024)
data2 = conn.recv(1024)
data3 = conn.recv(1024)
print(data1)
print(data2)
print(data3)
conn.close()
sk_obj.close()


# Client
import socket

sk_obj = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sk_obj.connect(('127.0.0.1',8080))

sk_obj.send(b'data1')
sk_obj.send(b'data2')
sk_obj.send(b'data3')

由接收方引起的粘包问题

接收方来不及接受操作系统缓冲区的数据,造成下一次接受还是从操作系统中获取上一次遗留的数据内容 

如何解决粘包问题

粘包发生的原因就是接收端不清楚发送端发送的数据大小(少收/多收)

我们可以制作报头(用于记录数据的类型,长度等...),接受端根据报头内容,知道数据的长度,而报头的长度又可以通过struct模块

变成固定的长度

# Serve

from socket import *
import json
import struct
import subprocess

socket_family = AF_INET
socket_type = SOCK_STREAM
sk_obj = socket(socket_family,socket_type)
sk_obj.bind(('127.0.0.1',8080))
sk_obj.listen(5)
print('服务端开始监听>>>')

while True:
    conn,client_address = sk_obj.accept()
    while True:
        try:
            data = conn.recv(1024)
            print('来着客户端>>>',data.decode('utf-8'))
            if not data:
                break
            p = subprocess.Popen(data.decode('utf-8'),
                                 shell=True,
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.PIPE
                                 )
            stdout = p.stdout.read()
            stderr = p.stderr.read()
            # stdout = p.stdout.read()
            header = {'data_size':len(stderr)+len(stdout)}   # 制作报头
            header_bytes = json.dumps(header).encode('utf-8')  # 将报头转换为json格式的bytes
            header_len = struct.pack('i',len(header_bytes))   # 转换成固定长度

            conn.send(header_len)
            conn.send(header_bytes)
            conn.send(stdout)
            conn.send(stderr)

        except ConnectionResetError:
            break
    conn.close()


#Client

from socket import *
import struct
import json

socket_family = AF_INET
socket_type = SOCK_STREAM
sk_obj = socket(socket_family,socket_type)
sk_obj.connect(('127.0.0.1',8080))

while True:
    cmd = input('输入cmd命令>>>').strip()
    if not cmd:
        continue
    sk_obj.send(cmd.encode('utf-8'))
    header_len = sk_obj.recv(4)   # 接收报头的长度信息
    header_bytes = sk_obj.recv(struct.unpack('i',header_len)[0])  # 接收报头
    header = json.loads(header_bytes)
    b = ''
    total = 0
    while total < header['data_size']:
        data = sk_obj.recv(1024)
        b += data.decode('gbk')
        total += len(data)
    print(b)

猜你喜欢

转载自blog.csdn.net/ltfdsy/article/details/82347355