首页 Python教程正文

【教程】网络编程(三)

從出茳湖 Python教程 2019-08-08 26 0

网络编程(三).png网络编程(三)    >>>思维导图>>>博客园

基于TCP协议使用socket分发大文件

案例:电影上传

思路:客户端端循环一行一行读文件并一行一行传输,服务端循环接收并写入文件

客户端代码

import socket,json,os,struct
client = cocket.socket()
client.connet(('127.0.0.1',8080))
while True:
    # 获取电影类表,循环展示
    MOVIE_DIR = r'D:\路径\视频'
    movie_list = os.listdir(MOVIE_DIR)
    for i,movie in enumerate(movie_list,1):
        print(i,movie)
    # 用户选择
    choice = input('please choice movie to upload>>>:')
    # 判断是否是数字
    if choice.isdigit():
        # 将字符串转为int
        choice = in(choice) - 1
        # 判断用户选择在不在列表范围内
        if choice in range(0,len(movie_list))
           # 获取用户想上传的文件路径
            path = movie_list[choice]
           # 拼接文件的绝对路径
           file_path = os.path.join(MOVIE_DIR,path)
           # 获取文件的大小
           file_size = os.path.getsize(file_path)
           # 定义一个字典
           res_d = {
                      'file_name':'性感荷官在线发牌.,mp4',
                      'file_size':file_size,
                      'msg':'注意身体,多喝营养快线'
           }
           # 序列话字典
           json_d = json.dunps(res_d)
           json_bytes = json_d.encode('utf-8')
           # 1.先制作字典格式的报头
           header = struct.pack('i',len(json_bytes))
           # 2.发送字典的报头
           client.send(header)
           # 3.再发字典
           client.send(json_bytes)
           # 4.再发文件数据(打开文件循环发送)
           with open(file_path,r'b') as f:
               for line in f:
                   client.send(line)
        else:
           print('not in rang')
    else:
        print('must be a number')

服务端代码

import socket,os,json,struct
server = socket,socket()
sever.bind(('127.0.0.1',8080))
server.listen(5)
while True:
    conn,addr = server.accept()
    while True:
        try:
            header_len = conn.recv(4)
            # 解析字典报头
            header_len = struct.unpack('i',hendaer_len)[0]
            # 再接收字典数据
            header_dic = conn.recv(header_len)
            real_dic = json.loads(header_dic.decode('utf-8'))
            # 获取数据长度
            total_size = real_dic.get('file_size')
            # 循环接受并写入文件
            recv_size = 0
            with open(real_dic.get('file_size'),'wb;) as f:
                while recv_size < total_size:
                    data = conn.recv(1024)
                    f.write(data)
                    recv_size += len(data)
                print('上传成功!')
        except ConnetionResetError as e:
            print(e)
            break
    conn.close()

UDP协议

UDP是一个简单的传输层协议。和TCP相比,UDP有下面几个显著特性:

1.UDP缺乏可靠性

UDP 本身不提供确认,序列号,超时重传等机制。UDP 数据报可能在网络中被复制,被重新排序。即 UDP 不保证数据报会到达其最终目的地,也不保证各个数据报的先后顺序,也不保证每个数据报只到达一次

2.UDP 数据报是有长度的

每个 UDP 数据报都有长度,如果一个数据报正确地到达目的地,那么该数据报的长度将随数据一起传递给接收方。而 TCP 是一个字节流协议,没有任何(协议上的)记录边界。

3.UDP 是无连接的

UDP 客户和服务器之前不必存在长期的关系。UDP 发送数据报之前也不需要经过握手创建连接的过程。

4.UDP 支持多播和广播

基于UDP协议的socket

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

server端

import socket
udp_sk = socket.socket(type=socket.SOCK_DGRAM)   # 创建一个服务器的套接字
udp_sk.bind(('127.0.0.1',9000))        # 绑定服务器套接字
msg,addr = udp_sk.recvfrom(1024)
print(msg)
udp_sk.sendto(b'hi',addr)                 # 对话(接收与发送)
udp_sk.close()                         # 关闭服务器套接字

client端

import socket
ip_port=('127.0.0.1',9000)
udp_sk=socket.socket(type=socket.SOCK_DGRAM)
udp_sk.sendto(b'hello',ip_port)
back_msg,addr=udp_sk.recvfrom(1024)
print(back_msg.decode('utf-8'),addr)

UDP不会发生黏包

UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。 
不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。 
对于空消息:tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),也可以被发送,udp协议会帮你封装上消息头发送过去。 
不可靠不黏包的udp协议:udp的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y),收完了x个字节的数据就算完成,若是y;x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠。

粘包现象只发生在tcp协议中

1.从表面上看,黏包问题主要是因为发送方和接收方的缓存机制、tcp协议面向流通信的特点。
2.实际上,主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的

udp和tcp一次发送数据长度的限制

用UDP协议发送时,用sendto函数最大能发送数据的长度为:65535- IP头(20) – UDP头(8)=65507字节。用sendto函数发送数据时,如果发送数据长度大于该值,则函数会返回错误。(丢弃这个包,不进行发送) 
用TCP协议发送时,由于TCP是数据流协议,因此不存在包大小的限制(暂不考虑缓冲区的大小),这是指在用send函数时,数据长度参数不受限制。而实际上,所指定的这段数据并不一定会一次性发送出去,如果这段数据比较长,会被分段发送,如果比较短,可能会等待和下一次数据一起发送。

socketserver

server端

import socketserver
class Myserver(socketserver.BaseRequestHandler):
    def handle(self):
        self.data = self.request.recv(1024).strip()
        print("{} wrote:".format(self.client_address[0]))
        print(self.data)
        self.request.sendall(self.data.upper())
if __name__ == "__main__":
    HOST, PORT = "127.0.0.1", 9999
    # 设置allow_reuse_address允许服务器重用地址
    socketserver.TCPServer.allow_reuse_address = True
    # 创建一个server, 将服务地址绑定到127.0.0.1:9999
    server = socketserver.TCPServer((HOST, PORT),Myserver)
    # 让server永远运行下去,除非强制停止程序
    server.serve_forever()

client端

import socket
HOST, PORT = "127.0.0.1", 9999
data = "hello"
# 创建一个socket链接,SOCK_STREAM代表使用TCP协议
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
    sock.connect((HOST, PORT))          # 链接到客户端
    sock.sendall(bytes(data + "\n", "utf-8")) # 向服务端发送数据
    received = str(sock.recv(1024), "utf-8")# 从服务端接收数据
print("Sent:     {}".format(data))
print("Received: {}".format(received))


本文标题:【教程】网络编程(三)
本文链接:https://zhong-er.com/post/59.html
作者授权:除特别说明外,本文由 從出茳湖 原创编译并授权 中二青年 刊载发布。
版权声明:本文使用「署名-非商业性使用-相同方式共享 4.0 国际」创作共享协议,转载或使用请遵守署名协议。

评论

本站会员尊享VIP特权,现在就加入我们吧!登录注册
登录
用户名
密码
注册
用户名
密码
确认密码
邮箱
获取邀请码
邀请码
验证码
找回密码
用户名
邮箱
※ 重置链接将发送到邮箱