zoukankan      html  css  js  c++  java
  • 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()
    
  • 相关阅读:
    Spring框架之什么是IOC的功能?
    Spring框架的特点
    spring框架的概述与入门
    struts框架总结
    struts框架之总结OGNL表达式的特殊的符号
    struts框架值栈问题七之EL表达式也会获取到值栈中的数据
    struts框架问题六之从值栈中获取值
    struts框架问题五之向值栈中保存数据
    struts框架问题四之获取到值栈的对象
    java获取屏幕密度
  • 原文地址:https://www.cnblogs.com/dbf-/p/10997566.html
Copyright © 2011-2022 走看看