zoukankan      html  css  js  c++  java
  • python网络编程之socket

      socket

      应用层和tcp,ucp协议之间的一个接口,用户只需操作接口,复杂的数据组织工作由其内部自行完成。

      TCP协议的socket(套接字):

        服务端

    import socket
    sk = socket.socket()  # 创建一个套接字对象
    sk.bind(('127.0.0.1', 8080))  # 绑定本地IP地址和端口
    sk.listen()     # 监听
    
    conn, address = sk.accept()   # 创建连接
    
    
    while True:
        ret = conn.recv(1024)   # 接收数据  需要指定接收字节数
        if ret == b'bye':
            conn.send(ret)
            break
        print(ret.decode('utf-8'))
        info = input('>>>').encode('utf-8')
        conn.send(info)   # 发送数据  必须是bytes类型
    conn.close()   # 关闭连接
    sk.close()   # 关闭套接字

        客户端

    import socket
    sk = socket.socket()  # 创建套接字
    sk.connect(('127.0.0.1', 8080))  # 连接服务端
    
    while True:
        info = input('>>>').encode('utf-8')
        sk.send(info)   # 发送数据
        ret = sk.recv(1024)  # 接收数据
        if ret == b'bye':
            break
        print(ret.decode('utf-8'))
    
    sk.close()  # 关闭套接字

      UDP协议的scoket(套接字):

        服务端

    import socket
    sk = socket.socket(type=socket.SOCK_DGRAM)  # 创建ucp套接字对象
    sk.bind(('127.0.0.1', 8080))  # 绑定IP和端口
    
    msg, address = sk.recvfrom(1024)  # 等待接收数据  ucp必须先接收数据
    print(msg.decode('utf-8'))
    sk.sendto(b'hello', address)  # 发送数据  要携带发送数据的地址
    
    sk.close()

        客户端

    import socket
    sk = socket.socket(type=socket.SOCK_DGRAM)  # 创建ucp套接字对象
    ip_port = ('127.0.0.1', 8080)  # 指定服务端IP和端口
    
    sk.sendto(b'hi', ip_port)  # 发送数据到指定服务端
    msg, address = sk.recvfrom(1024)  # 接收返回的数据
    print(msg.decode('utf-8'))
    
    
    sk.close()

       

      黏包

      先写个例子看下黏包现象。

    import socket
    sk = socket.socket()
    sk.bind(('127.0.0.1', 8080))
    sk.listen()
    
    connect, address = sk.accept()
    
    while True:
        cmd = input('>>>')
        if cmd == 'q':
            connect.send(b'q')
            break
        connect.send(cmd.encode('gbk'))
        ret = connect.recv(1024).decode('gbk')
        print(ret)
    connect.close()
    sk.close()
    server
    import socket
    import subprocess
    
    sk = socket.socket()
    sk.connect(('127.0.0.1', 8080))
    
    while True:
        cmd = sk.recv(1024).decode('gbk')
        if cmd == 'q':
            break
        res = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        sk.send(res.stdout.read())
        sk.send(res.stderr.read())
    sk.close()
    client

      这里用到了一个subprocess模块,它可以创建子进程,并与进程进行各种交互。

    import subprocess
    ret = subprocess.Popen('dir', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    print('stdout:'+ret.stdout.read().decode('gbk'))  # 读取正常的返回值 解码方式以操作系统而定
    print('stderr:'+ret.stderr.read().decode('gbk'))  # 读取错误的返回值 解码方式以操作系统而定
    """
        subprocess.Popen('dir', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            dir:系统命令
            shell:True确认 False报错
            stdout:接收正常返回值
            stderr:接收错误返回值
    """

      当执行命令的时候,会出现上次命令的返回的信息在执行下一个命令时才会显示。这种连续传送数据,数据发生混乱的情况就叫做黏包。

    在tcp协议的数据传输中,在时间间隔短暂的情况下进行连续的send,且send数据量小,
    由于自身的优化算法,会将这些数据打包在一起才发送出去。
    这样接收端就无法分辨数据之间的分界,造成接收到的数据和实际不符合的现象

      UDP协议不建立连接,也没有什么优化算法,所以不会有黏包,但是会出现丢包。

      黏包问题解决

      了解黏包问题后,归根结底是因为接收端无法知道发送端所发数据的大小,知道了数据大小我们就可以接收到对应的数据,从而避免黏包造成的数据混乱问题。下面具体代码看下解决办法:

    import struct
    import socket
    
    sk = socket.socket()
    sk.bind(('127.0.0.1', 8080))
    sk.listen()
    
    connect, address = sk.accept()
    while True:
        cmd = input('>>>')
        if cmd == 'q':
            connect.send(b'q')
            break
        connect.send(cmd.encode('gbk'))
        num = connect.recv(4)  # 因为长度转成了4位的,所以这里接收的一定是要传输数据的长度struct.pack值
        num = struct.unpack('i', num)[0]  # unpack转回原来的数值,结果是个元组,取第一个值
        res = connect.recv(num).decode('gbk')  # 接收对应长度的数据,这样就可以接收到自己想要的数据了
        print(res)
    
    connect.close()
    sk.close()
    server
    import struct
    import socket
    import subprocess
    
    sk = socket.socket()
    sk.connect(('127.0.0.1', 8080))
    
    while True:
        cmd = sk.recv(1024).decode('gbk')
        if cmd == 'q':
            break
        ret = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        ret_out = ret.stdout.read()
        ret_err = ret.stderr.read()
        len_num = len(ret_err) + len(ret_out)  # 计算传输的数据的长度
        num_bytes = struct.pack('i', len_num)  # 将int长度转换为4位的bytes类型数据
        sk.send(num_bytes)  # 将这个数据传给接收端
        sk.send(ret_out)
        sk.send(ret_err)
    sk.close()
    client

      这里用到了一个struct模块,它通过pack(‘i’,v)可以将指定类型的数据转换为特定长度的字符串(bytes),在通过unpack重新取回原来的数据,以元组的方式返回。

      我们还可以通过自定制报头来更好的处理TCP数据传输过程中的黏包问题。

    import json
    import struct
    import socket
    sk = socket.socket()
    sk.bind(('127.0.0.1', 8090))
    sk.listen()
    buffer = 4096
    connect, address = sk.accept()
    
    num = connect.recv(4)
    head_len = struct.unpack('i', num)[0]
    json_head = connect.recv(head_len).decode('utf-8')
    head = json.loads(json_head)
    file_size = head['file_size']
    with open(head['file_name'], 'wb') as f:
        while file_size:
            if file_size > buffer:
                content = connect.recv(buffer)
                f.write(content)
                file_size -= len(content)
            else:
                content = connect.recv(file_size)
                f.write(content)
                file_size -= len(content)
    
    connect.close()
    sk.close()
    server
    import os
    import json
    import struct
    import socket
    
    sk = socket.socket()
    sk.connect(('127.0.0.1', 8090))
    buffer = 4096
    
    head = {'file_name': r'CentOS-7-x86_64-DVD-1708.iso',
            'file_path': r'D:下载安装包',
            'file_size': None}
    path = os.path.join(head['file_path'], head['file_name'])
    file_size = os.path.getsize(path)
    head['file_size'] = file_size
    json_head = json.dumps(head)
    head_len = struct.pack('i', len(json_head))
    sk.send(head_len)
    sk.send(json_head.encode('utf-8'))
    with open(path, 'rb') as f:
        while file_size:
            if file_size >= buffer:
                content = f.read(buffer)
                sk.send(content)
                file_size -= buffer
            else:
                content = f.read(buffer)
                sk.send(content)
                break
    sk.close()
    client

      验证客户端合法性

      验证客户端的合法性:为了防止一些违法的客户端连接服务端进行数据的盗取或破坏

    import os
    import hmac
    import socket
    sk = socket.socket()
    sk.bind(('127.0.0.1', 8080))
    sk.listen()
    secret_key = b'secret'   # 创建秘钥
    
    
    def check_connect(conn):
        msg = os.urandom(32)  # 随机生成一个32字节的bytes类型的字符串
        conn.send(msg)  # 将随机字符串发送给连接端
        h = hmac.new(secret_key, msg)  # 通过一个秘钥创建一个加密对象
        digest = h.digest()    # 获取加密值
        client_digest = conn.recv(1024)
        return hmac.compare_digest(digest, client_digest)  # 对比连接端和服务端的加密值是否相同 返回bool值
    
    
    connect, address = sk.accept()
    if check_connect(connect):  # 相同 说明为自己的合法客户端
        print('合法')
    else:                        # 不相同说明为违法客户端
        print('不合法')
    connect.close()
    sk.close()
    server
    import hmac
    import socket
    sk = socket.socket()
    sk.connect(('127.0.0.1', 8080))
    secret_key = b'secret'
    msg = sk.recv(1024)  # 接收服务端的随机字符串
    h = hmac.new(secret_key, msg)  # 创建加密对象
    digest = h.digest()  # 获取加密值
    sk.send(digest)  # 发给服务端验证
    sk.close()
    client

      秘钥是验证合法性的关键,所以秘钥要加密保存,自己合法的客户端要加密保存好秘钥,这里只是简单的演示。

      这里的hmac模块提供hmac算法,作用和hashlib的加盐摘要类似,使用秘钥对内容进行加密算法。

      socketserver模块

      socketserver模块:可以使tcp服务端同时与多个客户端进行数据传输工作

    import socketserver
    
    
    class MY_socket(socketserver.BaseRequestHandler):  # 创建一个自己的类,一定要继承socketserver.BaseRequestHandler
        def handle(self):  # 一定要创建一个handle函数
            while True:
                msg = self.request.recv(1024).decode('utf-8')   # 这里的self.request就是建立的连接
                if msg == 'q':
                    break
                print(msg)
                info = input('>>>')
                self.request.send(info.encode('utf-8'))
    
    
    if __name__ == '__main__':
        # 创建一个socketserver里TCP多线程服务的对象  参数为IP端口元组以及自己定义的类
        server = socketserver.ThreadingTCPServer(('127.0.0.1', 8080), MY_socket)
        # server.allow_reuse_address = True
        server.serve_forever()  # 永远启动服务,除非强制关闭或意外停止。
    server

      服务端要使用socketserver模块来处理,客户端则不需要,和正常的连接和数据传输方式一样。

    import socket
    sk = socket.socket()
    sk.connect(('127.0.0.1', 8080))
    
    while True:
        info = input('>>>')
        if info == 'q':
            sk.send(b'q')
            break
        sk.send(('client:'+info).encode('utf-8'))
        msg = sk.recv(1024).decode('utf-8')
        print(msg)
    sk.close()
    client
    import socket
    sk = socket.socket()
    sk.connect(('127.0.0.1', 8080))
    
    while True:
        info = input('>>>')
        if info == 'q':
            sk.send(b'q')
            break
        sk.send(('client1:'+info).encode('utf-8'))
        msg = sk.recv(1024).decode('utf-8')
        print(msg)
    sk.close()
    client1

      

      

  • 相关阅读:
    To do list
    2020 上半学期比赛记录
    板子
    Project Euler 1~10 野蛮题解
    卡常火车头
    防止unordered_map 被卡方法
    2019 香港区域赛 BDEG 题解
    2019徐州区域赛 ACEFM 题解 & pollard-rho & miller-rabin & 求出每个子树的重心 板子
    BST-splay板子
    ZJOI2017(2) 游记
  • 原文地址:https://www.cnblogs.com/zxc-Weblog/p/8360434.html
Copyright © 2011-2022 走看看