zoukankan      html  css  js  c++  java
  • 基于 TCP & UDP 协议的 socket 通信

    socket 通信

      TCP版本:

    # 最终版本,解决了 TCP 协议中的粘包问题
    # 客户端
    import socket
    import struct
    import json
    
    client = socket.socket()  # 先生成一个客户端对象
    client.connect(('127.0.0.1', 8080))  # 绑定服务端   # 里面接收一个值 - 元祖  元祖里面传两个值,分别为IP地址以及端口号 
    
    while True:
        msg = input('>>>   ').strip().encode('utf-8')  # 首先输入命令,并且转化为字节
        if len(msg) == 0:
            continue  # 如果字节长度为0 ,说明没有内容传入,就结束本次循环,重新输入指令
        client.send(msg)
        # 接收服务端传来的字典报头
        d_head = client.recv(4)
        # 解析字典报头,获取字典长度信息
        d_len = struct.unpack('i', d_head)[0]  # 解析报头必须后面添加索引[0]
        # 接收字典数据,并反序列化字典
        d_bytes_data = client.recv(d_len)
        d = json.loads(d_bytes_data)
    
        # 接收真实结果
        real_data = b''
        recv_size = 0
        while recv_size < d['file_size']:  # 字典d里面有真实结果的字节长度信息
            data = client.recv(1024)
            real_data += data  # 拼接真实结果
            recv_size += len(data)  # 因为最后接收的不一定是1024,所以以每次接收的长度增值运算
        print(real_data.decode('gbk'))  # 因为windows终端默认使用的是GBK格式,所以以GBK解码
    
    
    # 服务端
    import socket
    import subprocess
    import json
    import struct
    
    server = socket.socket()  # 先生成一个服务端对象
    server.bind(('127.0.0.1', 8080))  # 里面接收一个值 - 元祖  元祖里面传两个值,分别为IP地址以及端口号  绑定自身IP
    server.listen(5)  # 半连接池
    
    while True:  # 连接循环
        conn, addr = server.accept()  # 服务器一直处于等待用户访问状态
        while True:  # 通信循环
            try:
                cmd = conn.recv(4)  # 接收用户的传来的信息
                if len(cmd) == 0:
                    break  # 判断用户传来的指令是否为空,空的话不执行命令,继续等待新的命令
                # 通过subprocess模块在终端执行用户传来的命令
                obj = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                res = obj.stdout.read() + obj.stderr.read()  # 接收执行命令后的结果内容(包括正确的信息和错误的信息)
    
                d = {'file_size': len(res)}  # 将结果内容的长度封在字典中,可以避免数字太大不能接收的情况
                d_json = json.dumps(d)  # 基于网络传输必须是二进制数据,所以要先序列化
    
                # 要想传输字典,先告诉客户端字典的字节长度,所以先生成字典的报头,在传输字典,避免黏包问题
                header = struct.pack('i', len(d))
                conn.send(header)  # 传送报头
                conn.send(d_json)  # 传送序列化之后的字典
                conn.send(res)  # 客户端得到字典后就可以获取真实结果的字节长度了,所以此时再将真实结果传送过去
            except ConnectionResetError:
                break  # 报错则跳出通信循环
        conn.close()
    
    

       例子:用TCP协议往服务端上传一个本地文件

    # 以上传本地视频为例
    
    # 本地视频位置:r'C:Users赵帅平Desktopd
    # 客户端
    
    import os
    import socket
    import json
    import struct
    
    client = socket.socket()
    client.connect(('127.0.0.1', 8888))
    
    while True:
        # 循环打印电影列表
        MOVIE_DIR = r'C:Users赵帅平Desktopday28视频'
        movie_list = os.listdir(MOVIE_DIR)
        for index, movie in enumerate(movie_list, 1):
            print(index, movie)
    
        # 让用户进行选择
        choice = input('please choose a movie to upload >>>  ').strip()
        if choice.isdigit():
            choice = int(choice) - 1
            if choice in range(0, len(movie_list)):
                movie = movie_list[choice]
                # 拼接要上传电影的绝对路径
                movie_path = os.path.join(MOVIE_DIR, movie)
                # 获取电影的大小
                movie_size = os.path.getsize(movie_path)
                # 定义一个字典,将电影大小添加进去
                d = {
                    'file_name': '常山赵子龙.mp4',  # 自己定义的名字,可以不定义
                    'movie_size': movie_size
                }
                # 序列化字典,并生成一个字典报头
                d_json = json.dumps(d)
                d_bytes = d_json.encode('utf-8')
                header = struct.pack('i', len(d_bytes))
                # 发送字典报头
                client.send(header)
                # 发送序列化后的字典
                client.send(d_bytes)
                # 服务端接收到字典后就可以获取要长传的电影字节大小信息,所以上传电影
                with open(movie_path, 'rb') as f:
                    for line in f:
                        client.send(line)
    
                # 等待客户端的返回结果
                res = client.recv(17).decode('utf-8')
                a = 'upload complete !'
                if res == a:
                    print('%s 上传成功!' % d.get('file_name'))
            else:
                print('choice is out of movie_list, please try again...')
                continue
        else:  
        print('choice must be number...')
    
    
    # 服务端
    
    import socket
    import struct
    import json
    
    server = socket.socket()
    server.bind(('127.0.0.1', 8888))
    server.listen(5)  # 半连接池
    
    while True:
        conn, addr = server.accept()
        while True:
            try:
                # 接收字典报头
                head = conn.recv(4)
                # 解析字典报头,获取字典字节长度
                d_len = struct.unpack('i', head)[0]
                # 接收序列化后的字典
                d_bytes = conn.recv(d_len)
                # 反序列化字典
                d = json.loads(d_bytes.decode('utf-8'))
    
                # 循环写入
                recv_size = 0
                with open(d.get('file_name'), 'ab') as f:
                    while recv_size < d['movie_size']:
                        data = conn.recv(1024)
                        recv_size += len(data)
                        f.write(data)
                    print('%s 接收上传完毕!' % d.get('file_name'))
                    conn.send(b'upload complete !')
            except ConnectionResetError as e:
                print(e)
                break
        conn.close()

       

      UDP版本:

        UDP协议的特点:数据报协议(自带报头)

                基于UDP协议传输数据 数据是不安全的

                

        与TCP协议的区别:多个客户端可以实现并发的效果

                 服务端不存在,客户端也不会报错(sendto)

                 不会黏包

                 允许发空       

    # 客户端
    
    import socket
    
    client = socket.socket(type=socket.SOCK_DGRAM)
    # UDP协议没有双向通道,所以不需要建立连接,直接进行通信循环
    # 但需要指定要发送的地址:IP + port
    server_address = ('127.0.0.1', 9001)
    while True:
        client.sendto(b'hello', server_address)  # 发送两个参数,一个为消息内容,一个为地址信息  且用sendto 发送
        data, server_addr = client.recvfrom(1024)  # 接收两个值, 一个是服务端消息,一个是服务端地址
        print('来自服务端的消息:', data.decode('utf-8'))  # 来自服务端的消息: HELLO
        print('服务端地址: ', server_addr)  # 服务端地址:  ('127.0.0.1', 9001)
     
    # 服务端
    
    import socket
    
    server = socket.socket(type=socket.SOCK_DGRAM)  # ()内默认为TCP协议,传参以后就是UDP协议
    server.bind(('127.0.0.1', 9001))
    # UDP协议没有半连接池,所以不需要设置半连接池
    
    # UDP协议没有双向通道,所以不需要accept()
    
    # 直接进入通信循环
    while True:
        data, addr = server.recvfrom(1024)  # 接收两个值,一个为客户端数据,一个是客户端IP地址+端口  # 注意是recvfrom
        print(data.decode('utf-8'))  # hello
        print(addr)  # ('127.0.0.1', 58709)
        server.sendto(data.upper(), addr)  # 发送时候也是需要发送两个值,一个是要发送的内容,一个是要发去的地址+端口  注意是sendto
     

      例子:用UDP协议写一个简易的QQ聊天

    # 仅是本机客户端与本机服务端之间的交互
    # 客户端
    import socket
    
    client = socket.socket(type=socket.SOCK_DGRAM)
    server_address = ('127.0.0.1', 9990)
    
    while True:
        msg = input('>>>')
        msg = '''这是来自客户端的消息:
                %s''' % msg
        client.sendto(msg.encode('utf-8'), server_address)
        data, server_addr = client.recvfrom(1024)
        print(data.decode('utf-8'))
    
    
    # 服务端
    import socket
    
    server = socket.socket(type=socket.SOCK_DGRAM)
    server.bind(('127.0.0.1', 9990))
    
    while True:
        data, addr = server.recvfrom(1024)
        print(data.decode('utf-8'))
        msg = input('>>>')
        msg = '''这是来自服务端的消息:
                %s''' % msg
        server.sendto(msg.encode('utf-8'), addr)

       

      socketserver的模块用法(了解):

    TCP & UDP 协议使用socketserver模块
    # 客户端用不着此模块,所以还是按照之前的正常编码
    import socket
    import time
    
    # # TCP协议写法:
    # client = socket.socket()  # 括号内默认为TCP协议,不用指定
    # client.connect(('127.0.0.1', 8888))
    #
    # while True:
    #     client.send(b'hello')
    #     data = client.recv(1024)
    #     print(data.decode('utf-8'))
    
    
    # UDP协议写法:
    client = socket.socket(type=socket.SOCK_DGRAM)  # UDP协议里面括号内需要指定协议
    server_address = ('127.0.0.1', 8888)  # UDP协议不是基于通道通信,所以不用建立连接关系,直接定义IP地址以及port
    
    while True:
        client.sendto(b'hello', server_address)  # UDP协议里面sendto相当于TCP协议里面的send
        data, addr = client.recvfrom(1024)  # UDP协议里面 recvfrom 相当于TCP协议里面的 recv  并且接收的是两个值:一个为数据,一个为服务端地址
        print(data.decode('utf-8'))
        print(addr)
        time.sleep(1)  # 因为太快,所以让CPU暂停一秒
    # 服务端
    import socketserver
    
    # TCP协议服务端写法
    # class MyServer(socketserver.BaseRequestHandler):  # 继承
    #     def handle(self):  # 只能为handle
    #         while True:
    #             data = self.request.recv(1024)  # 接收一个值  用request.recv接收
    #             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', 8888), MyServer)
    #     server.serve_forever()  # 启动该服务对象
    
    
    # UDP协议服务端写法
    class MyServer(socketserver.BaseRequestHandler):
        def handle(self):
            while True:
                data, sock = self.request  #request,自带接收效果,同时接收两个值;一个为消息数据,一个类似于conn
                print(data.decode('utf-8'))  # 打印消息
                print(self.client_address)  # 内置有客户端的地址属性
              sock.sendto(data.upper(), self.client_address)  # 传两个值
    
    
    if __name__ == '__main__':
        """只要有客户端连接  会自动交给自定义类中的handle方法去处理"""
        server = socketserver.ThreadingUDPServer(('127.0.0.1', 8888), MyServer)
        server.serve_forever()  # 启动该服务对象
  • 相关阅读:
    CYQ.Data 轻量数据层之路 V4.0 版本发布
    基于MSAA的自动化封装和设计—python版(转)
    【自然框架】之鼠标点功能现(二):表单控件的“应用”—— 代码?只写需要的!
    论管理员的不作为!!!
    【自然框架】之通用权限的Demo(二):添加人员、添加账户、添加角色里面的账户以及列表的权限验证
    使用接口来统一控件的取值、赋值和初始化
    【自然框架】之通用权限(八):权限到字段(列表、表单、查询)
    辩论赛 VS 讨论组
    【自然框架】表单控件 之 一个表单修改多个表里的记录
    【自然框架】之“解耦”初探
  • 原文地址:https://www.cnblogs.com/pupy/p/11323741.html
Copyright © 2011-2022 走看看