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

      

      

  • 相关阅读:
    4 Apr 18 软件开发目录 logging模块的使用 序列化(Json, Pickle) os模块
    3 Apr 18 内置函数 列表生成式与生成器表达式 模块的使用之import 模块的使用之from…import…
    2 Apr 18 三元表达式 函数递归 匿名函数 内置函数
    30 Mar 18 迭代器 生成器 面向过程的编程
    29 Mar 18 函数 有参、无参装饰器
    28 Mar 18 函数
    27 Mar 18 函数的参数
    26 Mar 18 函数介绍
    23 Mar 18 文件处理
    22 Mar 18 补充数据类型+字符编码+文件处理
  • 原文地址:https://www.cnblogs.com/zxc-Weblog/p/8360434.html
Copyright © 2011-2022 走看看