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()  # 启动该服务对象
  • 相关阅读:
    Palindrome Partitioning
    Minimum Path Sum
    Maximum Depth of Binary Tree
    Minimum Depth of Binary Tree
    Unique Binary Search Trees II
    Unique Binary Search Trees
    Merge Intervals
    Merge Sorted Array
    Unique Paths II
    C++ Primer Plus 笔记第九章
  • 原文地址:https://www.cnblogs.com/pupy/p/11323741.html
Copyright © 2011-2022 走看看