Python socket & socket server

socket

网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket(套接字)。
建立网络通信连接至少要一对socket。socket是对TCP/IP的封装

使用方法

步骤:
服务器端:

  1. 实例化类
  2. bind 绑定端口
  3. listen 监听端口
  4. accept 等待连接(阻塞)
    sock, addr = server.accept() sock 是为客户端实例化的socket类, addr 是客户端的地址
  5. 与客户端交互:recv 接收(阻塞)、send 发送数据
  6. close 断开连接

客户端:

  1. 实例化类
  2. connect 连接端口
  3. 与服务器端交互:recv 接收(阻塞)、send 发送数据
  4. close 断开连接
只传输一条数据

server:

import socket

server = socket.socket()
server.bind(('localhost', 8001))
server.listen(3)

conn, addr = server.accept()
data = conn.recv(1024)
conn.send(b'Got')
print('recv:', data)

server.close()

client:

import socket

client = socket.socket()
client.connect(('localhost', 8001))
client.send(b'Hello')

data = client.recv(1024)
print('recv:', data)

client.close()

循环发送数据:

server:

import socket

server = socket.socket()
server.bind(('localhost', 8001))
server.listen(3)
while 1:
    conn, addr = server.accept()
    print('new connection:', addr)
    while 1:
        data = conn.recv(1024)
        conn.send(b'Got')
        if not data:
            break
        else:
            print('recv:', data)

client:

import socket

client = socket.socket()
client.connect(('localhost', 8001))

while 1:
    data = input('>>>').strip()
    if len(data) == 0:
        # 注意不能发送内容为空,否则客户端会卡住
        continue
    if data == 'e':
        break
    else:
        client.send(data.encode('utf-8'))
        data = client.recv(1024)
        print('recv:', data.decode())

client.close()
模拟SSH

server:

import socket
import os

server = socket.socket()
server.bind(('localhost', 8001))
server.listen(3)
while 1:
    try:
        conn, addr = server.accept()
        print('new connection:', addr)
        while 1:
            data = conn.recv(1024)
            if not data:
                break
            else:
                res = os.popen(data.decode()).read()
                conn.send(res.encode())
    except ConnectionResetError:
        continue

client:

import socket

client = socket.socket()
client.connect(('localhost', 8001))

while 1:
    data = input('>>>').strip()
    if len(data) == 0:
        # 注意不能发送内容为空,否则客户端会卡住
        continue
    if data == 'e':
        break
    else:
        client.send(data.encode('utf-8'))
        data = client.recv(1024)
        print(data.decode())

client.close()

socket 粘包

当 send 被调用时,数据其实并没有立刻被发送给客户端,而是放到了系统的 socket 发送缓冲区里,等缓冲区满了、或者数据等待超时了,数据才会被 send 到客户端,这样就把好几次的小数据拼成一个大数据,统一发送到客户端了,这么做的目地是为了提高IO利用效率,一次性发送总比连发好几次效率高嘛。但也带来一个问题,就是“粘包”,即2次或多次的数据粘在了一起统一发送了。

解决方法:

  1. 让数据等待超时,发送一次数据之后 sleep(0.5)(直接忽略)
  2. 不要连续 send ,可以用 recv 将多次 send 隔开,只需要数据接收端接收到一次数据后,send 回复发送端
  3. 如果之前知道传输数据的大小,直接接收那么多的数据
传输大文件

server:

import socket
import os
import hashlib

server = socket.socket()
server.bind(('localhost', 8001))
server.listen(3)
while 1:
    try:
        conn, addr = server.accept()
        print('new connection:', addr)
        while 1:
            cmd = conn.recv(1024).decode()
            if not cmd:
                print('lost client')
                break
            else:
                file_name = cmd.split()[1]
                if not os.path.isfile(file_name):
                    res = 'no such file'
                else:   # 存在可以传输的文件
                    m = hashlib.md5()
                    file_size = os.stat(file_name).st_size
                    print(file_size)
                    conn.send(str(file_size).encode())
                    conn.recv(1024)
                    with open(file_name, 'rb')as f:
                        for line in f:
                            conn.send(line)
                            m.update(line)
                    print('file md5:', m.hexdigest())
                    conn.send(m.hexdigest().encode())
    except ConnectionResetError:
        print('lost client')
        continue

client:

import socket
import hashlib

client = socket.socket()
client.connect(('localhost', 8001))

while 1:
    cmd = input('get filename: >>>').strip()
    if len(cmd) == 0:
        # 注意不能发送内容为空,否则客户端会卡住
        continue
    else:
        client.send(cmd.encode('utf-8'))
        file_name = cmd.split()[1]
        received_size = 0
        m = hashlib.md5()
        total_size = client.recv(1024)
        client.send(b'Get size')
        f = open(file_name + '.new', 'wb')
        while received_size < int(total_size.decode()):
            size = int(total_size.decode()) - received_size
            if size < 1024:
                receive_size = size
            else:
                receive_size = 1024
            data = client.recv(receive_size)
            received_size += len(data)
            f.write(data)
            m.update(data)
        else:
            f.close()
            print(m.hexdigest())
            server_md5 = client.recv(1024).decode()
            if m.hexdigest() == server_md5:
                print('file checked')
client.close()

有时结束连接之后再连接会显示端口占用,可以通过重用端口省去等待的时间:server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) (bind 端口之前进行设置)

socket server

创建socketserver的基本步骤:

  1. 创建一个请求处理类,继承 BaseRequestHandler 并且重写父类中的 handle()
  2. 实例化 TCPServer ,将 server IP 和请求处理类传给 TCPServer
  3. 调用 server.handle_request() #只处理一个请求 server.serve_forever() #处理多个一个请求,永远执行
  4. server_close()

继承关系

class BaseServer:

class TCPServer(BaseServer):
class UDPServer(TCPServer):

class UnixStreamServer(TCPServer):
class UnixDatagramServer(UDPServer):

class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass

使用方法

import socketserver


class MyTCPHandler(socketserver.BaseRequestHandler):
    def handle(self):
        # 处理和客户端所有的交互
        pass


if __name__ == '__main__':
    HOST, PORT = 'localhost', 8001
    server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)
    server.serve_forever()

如果要设置允许地址重用:
allow_reuse_address 是 TCPServer 和 UDPServer 的类变量,之后又被实例化的类继承了,由于实例化的类继承父类的同时已经开始 bind,所以只能在他继承父类之前修改父类的类变量

HOST, PORT = 'localhost', 8001
socketserver.TCPServer.allow_reuse_address = True
server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)  # 实例化 bind listen
server.serve_forever()

socket server 多并发

多线程:ThreadingTCPServer
多进程:ForkingTCPServer -only in Linux

使用时只需在实例化类时继承其中一个:

HOST, PORT = 'localhost', 8001
socketserver.TCPServer.allow_reuse_address = True
server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler)  # 实例化 bind listen
server.serve_forever()
原文地址:https://www.cnblogs.com/dbf-/p/10997566.html