基于TCP协议的套接字

服务端必须满足三个条件:

  1. 绑定一个固定的 ip 和 port
  2. 一直对外提供服务,稳定运行
  3. 能够支持并发

利用Socket实现循环通信

1.客户端实现循环通讯

import socket
client = socket.socket()
client.connect(('127.0.0.1', 8888))
while True:
    msg = input('>>>>').encode('utf-8')
    if len(msg) == 0:  # 判断客户端是否为输入,直接敲回车,如果直接回车会导致服务端阻塞
        continue
    client.send(msg)
    data = client.recv(1024)
    print(data)
client.close()

2.服务端实现循环通讯

import socket

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

conn,client_addr = server.accept()  # 阻塞

while True:
    try:
        data = conn.recv(1024)  # 阻塞
        if len(data) == 0:  # 针对linux 客户端异常断开反复收空的情况
            break
        print('收到客户端的消息<<<:', data)
        conn.send(data.upper())
    except ConnectionResetError:
        break

conn.close()
server.close()

链接循环

可以启动多个客户端,但是只有一个客户端是处于连接状态,其余部分在半连接池等待连接,等待的数量不能超过半连接池的最大监听数量

import socket

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

链接循环,可以同时启动最多6个客户端,但是只有一个处于连接状态,其余最多5个在半连接池等待。只有当连接状态的客户端断开连接,下一个客户端才进入连接

conn,client_addr = server.accept()  # 阻塞
while True:
    try:
        data = conn.recv(1024)  # 阻塞
        if len(data) == 0:  # 针对linux 客户端异常断开反复收空的情况
            break
        print('收到客户端的消息<<<:', data)
        conn.send(data.upper())
    except ConnectionResetError:
        break
conn.close()
server.close()

粘包

模拟ssh实现远程执行命令

1.客户端

import socket

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

while True:
msg = input('>>>>').encode('utf-8')
if len(msg) == 0: 
continue
client.send(msg)
data = client.recv(1024)
print(data.decode('gbk'))  # Windows系统默认编码是gbk
client.close()

2.服务端

import socket
import subprocess
server =socket.socket()
server.bind(('127.0.0.1', 8888))
server.listen(5)
while True:
    conn, addr = server.accept()
    while True:
        try:
            data = conn.recv(1024)
            obj = subprocess.Popen(data.decode('utf_8'),
                                   shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE
                                   )
            stdout = obj.stdout.read()
            stderr = obj.stderr.read()
            conn.send(stdout+stderr)
        except ConnectionResetError:
            break
    conn.close()

server.close()

在上面的示例中,我们会发现当我们执行命令会得到很多结果时,服务器返回的数据不会一次接受完,当你输入下一次命令时会把上次命令的执行结果继续接着打印,这就是TCP协议本身造成的粘包。

粘包:

多个数据包被连续存储于连续的缓存中,在对数据包进行读取时由于无法确定发生方的发送边界,而采用某一估测值大小来进行数据读出,若双方的size不一致时就会使指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。

出现粘包的原因

出现粘包现象的原因是多方面的,它既可能由发送方造成,也可能由接收方造成。所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。

1. 发送方引起的粘包

是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据。若连续几次发送的数据都很少,通常TCP会根据优化算法把这些数据合成一包后一次发送出去,这样接收方就收到了粘包数据。

2. 接收方引起的粘包

是由于接收方用户进程不及时接收数据,从而导致粘包现象。这是因为接收方先把收到的数据放在系统接收缓冲区,用户进程从该缓冲区取数据,若下一包数据到达时前一包数据尚未被用户进程取走,则下一包数据放到系统接收缓冲区时就接到前一包数据之后,而用户进程根据预先设定的缓冲区大小从系统接收缓冲区取数据,这样就一次取到了多包数据。

解决粘包问题的方法

问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据。为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据。

我们可以利用struct模块可以把一个类型,如数字,转成固定长度的bytes字节类型数据的特点。为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据

1.客户端

import socket
import struct
import json

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

while True:
    msg = input('>>>:').encode('utf-8')
    if len(msg) == 0:continue
    client.send(msg)
    header = client.recv(4)

# 对这个头进行解包,获取真实数据的长度
head_len = struct.unpack('i',header)[0]
head_dic = json.loads(client.recv(head_len).decode('utf-8'))
print(head_dic)
# 对需要接受的数据 进行循环接收
total_size = head_dic['len']
recv_size = 0
res = b''
while recv_size < total_size:
    data = client.recv(1024)
    res += data
    recv_size += len(data)
print(res.decode('gbk'))

2.服务端

import socket
import subprocess
import struct
import json
server = socket.socket()
server.bind(('127.0.0.1',8081))
server.listen(5)  # 半连接池

while True:
    conn,addr = server.accept()  # 阻塞
    while True:
        try:
            data = conn.recv(1024).decode('utf-8')  # 阻塞
            if len(data) == 0:break  
            obj = subprocess.Popen(data,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            stdout = obj.stdout.read()
            stderr = obj.stderr.read()
            print(len(stdout+stderr))

            header_dic = {
                'filename':'cls.av',
                'len':len(stdout+stderr)
            }
            header_bytes = json.dumps(header_dic).encode('utf-8')
            # 制作报头
            header = struct.pack('i', len(header_bytes))  # 将需要发送给客户端的数据打包成固定4个字节
            conn.send(header)

            conn.send(header_bytes)

            conn.send(stdout+stderr)

        except ConnectionResetError:
            break
    conn.close()

server.close()

猜你喜欢

转载自blog.csdn.net/linwow/article/details/89713621