基于 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\赵帅平\Desktop\d
# 客户端

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\赵帅平\Desktop\day28\视频'
    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()  # 启动该服务对象

 

 

posted @ 2019-08-08 21:01  速8赛亚人  阅读(235)  评论(0编辑  收藏  举报