zoukankan      html  css  js  c++  java
  • Socket(套接字)

     

    Socket 层概念

    理解socket

    Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

    TCP 协议下的 Socket 通信

    TCP(Transmission Control Protocol)可靠的、面向连接的协议(eg:打电话)、传输效率低全双工通信(发送缓存&接收缓存)、面向字节流。使用TCP的应用:Web浏览器;电子邮件、文件传输程序。

    tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端

    客户端

    import socket
    
    
    client = socket.socket()  # 以默认的TCP协议建立双向通道
    client.connect(('127.0.0.1', 8080))  # 服务端的ip 和 port
    
    client.send(b'hello')  # 向服务端发送信息,二进制格式
    data = client.recv(1024)  # 接收服务端传来的信息
    print(data)
    
    client.close()  # 关闭服务端

    服务端

    import socket
    
    
    
    server = socket.socket()  # 实例化socket对象,不传参数默认TCP协议
    server.bind(('127.0.0.1', 8080))  # bind((host, prot)) 绑定端口和协议
    server.listen()  #
    
    conn, addr = server.accept()  # 等待接听信息,  conn:双向传输通道  addr:客户端地址
    data = conn.recv(1024)  # 将客户端传输过来的内容赋值给data   接受1024个字节数据
    print(data)
    conn.send(b'hello word')  # 向客户端传输内容,只能是二进制格式
    
    conn.close()  # 关闭通道
    server.close()  # 关闭服务端

    连接循环+通信循环

    客户端

    import socket
    
    
    client = socket.socket()
    client.connect(('127.0.0.1', 8080))
    
    while True:
        msg = input('>>>:').encode('utf-8')
        if len(msg) == 0:continue
        client.send(msg)
        data = client.recv(1027)
        print(data)

    服务端

    import socket
    
    
    '''
    服务端有固定的ip和port,要24小时不间断服务客户端
    
    '''
    server = socket.socket()  # 生成一个对象
    server.bind(('127.0.0.1', 8080))  # 绑定ip和port
    server.listen(5)  # 半连接池
    
    while True:  # 连接循环
        conn, addr = server.accept()  # 当一个客户端断开通道后,等待下一个客户端的连接
        while True:  # 通信循环
            try:
                data = conn.recv(1024)
                print(data)  # mac与linux 客户端异常退出后,服务端并不会报错,会循环打印 b''
                if len(data) == 0:break  # 用户兼容mac与linux
                conn.send(data.upper())  # 将数据大写发送给客户端
            except ConnectionAbortedError:  # 捕捉 客户端异常退出 的错误信息
                break  # 当客户端异常退出后结束通信循环
        conn.close()  # 当客户端异常退出后关闭通道,进入下一次连接循环

    struct模块

    struct模块的作用是将数据长度转换成固定长度的内容
    '''
    
    struct模块的作用是将数据长度转换成固定长度的内容
    
    '''
    import struct
    
    
    res = 'asdfghjkl'
    print('装包前长度', len(res))  # >>> 装包前长度 9
    # 装包 成固定长度为4
    res1 = struct.pack('i', len(res))
    print('装包后长度', len(res1))  # >>> 装包后长度 4
    # 解包
    res2 = struct.unpack('i', res1)[0]
    print('解包后长度', res2)  # 解包后长度 9
    
    
    
    
    
    d = {
        'name':'waller',
        'file_size': 33335555555444444446666666,
        's':1
    }
    print(len(d))  # 字典键值对个数
    
    import json
    d_size = json.dumps(d)
    print(len(d_size))  # 字典转字符转后的字符个数
    
    # 装包 报头 成固定长度为4
    msg1 = struct.pack('i', len(d_size))
    print(msg1)  # >>> b'Cx00x00x00'
    print(len(msg1))
    # 解包 报头 获得原长度
    msg2 = struct.unpack('i', msg1)[0]
    print(msg2)

     解决粘包问题

    服务端
      1.先制作一个发送给客户端的字典
      2.制作字典的报头
      3.发送字典的报头
      4.发送字典
      5.再发真实数据

    客户端
      1.先接受字典的报头
      2.解析拿到字典的数据长度
      3.接受字典
      4.从字典中获取真实数据的长度
      5.接受真实数据

    服务端

    import socket
    import subprocess
    import struct
    import json
    
    
    server = socket.socket()
    server.bind(('127.0.0.1',8080))
    server.listen(5)
    
    
    while True:
        conn, addr = server.accept()
        while True:
            try:
                cmd = conn.recv(1024)
                if len(cmd) == 0:break
                cmd = cmd.decode('utf-8')
                obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
                res = obj.stdout.read() + obj.stderr.read()
                print(res)  # res 是二进制格式
                d = {'name':'jason','file_size':len(res),'info':'asdhjkshasdad'}
                json_d = json.dumps(d)  # 将字典序列化成字符串,便于编码传输
                # 1.先制作一个字典的报头
                header = struct.pack('i',len(json_d))  # 报头自动被编码成二进制
                print(header)  # >>> b'<x00x00x00'
                # 2.发送字典报头
                conn.send(header)
                # 3.发送字典
                conn.send(json_d.encode('utf-8'))  # 将字典编码发送
                # 4.再发真实数据
                conn.send(res)
                # conn.send(obj.stdout.read())
                # conn.send(obj.stderr.read())
            except ConnectionResetError as e:
                print(e)
                break
        conn.close()

    客户端

    import socket
    import struct
    import json
    
    client = socket.socket()
    client.connect(('127.0.0.1',8080))
    
    while True:
        msg = input('>>>:').encode('utf-8')
        if len(msg) == 0:continue
        client.send(msg)
        # 1.先接受字典报头
        header_dict = client.recv(4)  # 接收的报头是二进制格式
        # 2.解析报头 获取字典的长度
        dict_size = struct.unpack('i',header_dict)[0]  # 解包的时候一定要加上索引0
        print(dict_size)
        # 3.接收字典数据
        dict_bytes = client.recv(dict_size)  # 按照字典的长度接收字典
        dict_json = json.loads(dict_bytes.decode('utf-8'))  # 将字典反序列化并解码出来
        print(dict_json)
        # 4.从字典中获取信息
        recv_size = 0
        real_data = b''  # 初始化二进制
        while recv_size < dict_json.get('file_size'):  # dict_json.get('file_size') = len(res)
            data = client.recv(1024)   # 接收1024个字节
            real_data += data  # 每读取一次二进制数据拼接一次
            recv_size += len(data)  # 每次读取的长度相加,当总长度和len(res)向同时,结束
        print(real_data.decode('gbk'))  # 将读取的二进制数据解码出来

    UDP协议下的 socket 通信

    UDP通信

    UDP(User Datagram Protocol)不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文,尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)。

    udp是无链接的,启动服务之后可以直接接受消息不需要提前建立链接


    数据报协议(自带报头)

    没有双向通道 通信类似于发短信

    1.udp协议客户端允许发空
    2.udp协议不会粘包
    3.udp协议服务端不存在的情况下,客户端不会报错
    4.udp协议支持并发

     基本使用

    import socket
    
    # 创建套接字对象 UDP协议
    s = socket.socket(type=socket.SOCK_DGRAM)
    # 绑定ip+port
    s.bind(('127.0.0.1', 8080))
    # UDP 协议不需要双向通道, 所以不需要accept 直接进入通信循环
    while True:
        # 接收客户端信息和地址
        data, addr = s.recvfrom(1024)
        print('客户端发来的信息', data.decode('utf-8'))
        print('客户端发来的地址', addr)
        # 将客户端发来的信息大写后发给客户端
        s.sendto(data.upper(), addr)
    服务端
    import socket
    
    # 创建套接字对象 修改为UDP协议
    c = socket.socket(type=socket.SOCK_DGRAM)
    # 服务端ip+port
    s_addr = ('127.0.0.1', 8080)
    # UDP协议通信部需要创建通道连接 直接进入通信循环
    while True:
        # 向指定的服务端发送信息
        c.sendto(b'hello', s_addr)
        # 接收服务端信息 解压赋值
        msg, addr = c.recvfrom(1024)
        print('服务端发来的数据', msg.decode('utf-8'))
        print('服务端发来的地址', addr)
    客户端

     图解

    socketserver 模块

    服务端

    import socketserver
    
    
    class MyServer(socketserver.BaseRequestHandler):
        def handle(self):
            # print('来啦 老弟')
            while True:
                data = self.request.recv(1024)
                print(self.client_address)  # 客户端地址
                print(data.decode('utf-8'))
                self.request.send(data.upper())
    
    
    if __name__ == '__main__':
        """只要有客户端连接  会自动交给自定义类中的handle方法去处理"""
        server = socketserver.ThreadingTCPServer(('127.0.0.1',8080),MyServer)  # 创建一个基于TCP的对象
        server.serve_forever()  # 启动该服务对象

    客户端

    import socket
    
    client = socket.socket()
    client.connect(('127.0.0.1',8080))
    
    while True:
        client.send(b'hello')
        data = client.recv(1024)
        print(data.decode('utf-8'))

     文件上传实例

    import socket
    import os
    import struct
    import json
    
    # 创建套接字对象
    c = socket.socket()  # 默认TCP协议
    # 连接服务端ip+port
    c.connect(('127.0.0.1', 8080))
    # 通信循环
    while True:
        file_path = r'D:OldBoy-py网路编程TCP协议socket套接字上传文件file_movie'
        file_list = os.listdir(file_path)
        for index, movie in enumerate(file_list):
            print(index+1, movie)
        choice = input('请选择要上传的视频>>>:').strip()
        if not choice.isdigit():
            print('请输入数字')
        choice = int(choice)
        if not (choice > 0 and choice <= len(file_list)):
            print('请选择正确编号')
        movie_name = file_list[choice-1]
        # 获得文件路径
        movie_path = os.path.join(file_path, movie_name)
        # 获得文件大小
        movie_size = os.path.getsize(movie_path)
        # 制作文件信息字典
        d = {
            'name': '电影',
            'file_size': movie_size
        }
        # 序列化字典
        d_json = json.dumps(d)
        # 将序列化后的字典转为二进制类型
        d_bytes = d_json.encode('utf-8')
        # 制作报头
        header = struct.pack('i', len(d_bytes))
        # 向服务端发送报头
        c.send(header)
        # 向服务端发送字典
        c.send(d_bytes)
        # 发送真实数据 读取文件信息,循环发送
        with open(movie_path, 'rb') as f:
            for line in f:
                # 发送
                c.send(line)  # rb 模式读出就是二进制格式
    客户端
    import socket
    import struct
    import json
    
    s = socket.socket()
    # 绑定ip+port
    s.bind(('127.0.0.1', 8080))
    # 监听
    s.listen(5)
    # 连接循环
    while True:
        # 建立双向通道 获取通道地址
        conn, addr = s.accept()
        # 通信循环
        while True:
            try:
                # 获取客户端信息(字典报头)
                header_len = conn.recv(4)
                # 解析报头 拆包 得到字典长度
                d_len = struct.unpack('i', header_len)[0]
                # 按字典长度接收字典
                d_json = conn.recv(d_len)
                # 解码字典
                d_bytes = d_json.decode('utf-8')
                # 把字典反序列化
                d = json.loads(d_bytes)
                # 获得真是数据长度
                movie_size = d.get('file_size')
                # 循环接收真实数据并写入文件
                start_size = 0
                with open(d.get('name'), 'wb') as f:
                    while start_size < movie_size:
                        # 按1024字节接收数据
                        data = conn.recv(1024)
                        # 写入文件
                        f.write(data)
                        start_size += len(data)
                    print('上传成功')
            except ConnectionResetError as e:
                print(e)
                break
        # 关闭通道
        conn.close()
    服务端

    简易版QQ

    import socket
    
    c = socket.socket(type=socket.SOCK_DGRAM)
    
    s_d = ('127.0.0.1', 8080)
    
    while True:
        msg = input('>>>')
        c.sendto(msg.encode('utf-8'), s_d)
        data, addr = c.recvfrom(1024)
        print(data.decode('utf-8'))
    客户端
    import socket
    
    s = socket.socket(type=socket.SOCK_DGRAM)
    
    s.bind(('127.0.0.1', 8080))
    
    while True:
        data, addr = s.recvfrom(1024)
        print(data.decode('utf-8'))
        msg = input('>>>:')
        s.sendto(msg.encode('utf-8'), addr)
    服务端
  • 相关阅读:
    dev 调用汉化资源
    GridViewDataHyperLinkColumn 显示与内容分离
    gmail 邮箱找回方法
    C# 拼接 in 查询字符串
    asp.net ajax
    解决vmware 因为网络问题而引起的异常
    从改工具到改模型
    Orchard源码:EventBus&EventHandler
    AutoResetEvent和ManualResetEvent理解
    缓存设计
  • 原文地址:https://www.cnblogs.com/waller/p/11318306.html
Copyright © 2011-2022 走看看