Python之路(第三十二篇) 网络编程:udp套接字、简单文件传输
一、UDP套接字
服务端
- # udp是无链接的,先启动哪一端都不会报错
- # udp没有链接,与tcp相比没有链接循环,只有通讯循环
- server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #创建一个服务器的套接字
- server.bind() #绑定服务器套接字
- inf_loop: #服务器无限循环
- cs = server.recvfrom()/server.sendto() # 对话(接收与发送)
- server.close() # 关闭服务器套接字
客户端
- client = socket() # 创建客户套接字
- comm_loop: # 通讯循环
- client.sendto()/client.recvfrom() # 对话(发送/接收)
- client.close() # 关闭客户套接字
简单例子
服务端
- import socket
-
- ip_port = ('127.0.0.1',8081)
- server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
- server.bind(ip_port)
- while True:
- print('udp服务端开始运行了')
- data,addr = server.recvfrom(1024)
- print(data.decode('utf-8'))
- msg = input("请输入").strip()
- server.sendto(msg.encode("utf-8"),addr)
客户端
- import socket
-
- ip_port = ('127.0.0.1', 8081)
- server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- while True:
- print('udp客户端开始运行了')
- msg = input("请输入").strip()
- server.sendto(msg.encode("utf-8"), ip_port)
- data, addr = server.recvfrom(1024)
- print(data.decode("utf-8"))
注意:udp 可以发空 数据报协议 说是发空,其实不是空 ,还有一个IP 端口的信息,发空时 带个端口信息,
tcp:不是一一对应的,udp:是一一对应的 数据报完整的
用upd做一个ntp时间服务器
服务端
- import socket
- import time
-
- ip_port = ("127.0.0.1",8080)
- buffer_size = 1024
- ntp_server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
- ntp_server.bind(ip_port)
-
- while True:
- data,addr = ntp_server.recvfrom(buffer_size)
- print("收到客户端的命令是",data.decode("utf-8"))
- if not data:
- fmt = "%Y-%m-%d %X"
- else:
- fmt = data.decode("utf-8")
- time_now = time.strftime(fmt,time.localtime())
- ntp_server.sendto(time_now.encode("utf-8"),addr)
客户端
- import socket
-
- ip_port = ("127.0.0.1",8080)
- buffer_size = 1024
- ntp_client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
-
- while True:
- msg = input(">>>")
- ntp_client.sendto(msg.encode("utf-8"),ip_port)
- recv_msg,addr = ntp_client.recvfrom(buffer_size)
- print(recv_msg.decode("utf-8"))
基于udp简单实现QQ聊天
服务端
- from socket import *
- udp_server= socket(AF_INET,SOCK_DGRAM)
- udp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
- udp_server.bind(('127.0.0.1',8080))
- print('start running...')
-
- while True:
- qq_msg,addr = udp_server.recvfrom(1024)
- print('来自[%s:%s]的一条消息:\033[44m%s\033[0m'%(addr[0],addr[1],qq_msg.decode('utf-8')))
- back_msg = input('回复消息:>>').strip()
- udp_server.sendto(back_msg.encode('utf-8'),addr)
- udp_server.close()
客户端
- from socket import *
- udp_client = socket(AF_INET,SOCK_DGRAM)
- qq_name_dic = {
- 'pony':('127.0.0.1',8080),
- 'jack':('127.0.0.1',8080),
- 'charles':('127.0.0.1',8080),
- 'nick':('127.0.0.1',8080)
- }
- while True:
- print("QQ名单列表:")
- for i in qq_name_dic.keys():
- print(i)
- qq_name = input('请输入聊天对象:>>').strip()
- if qq_name not in qq_name_dic: continue
- while True:
- msg = input('请输入消息,回车发送:').strip()
- if msg=='quit':break
- if not msg or not qq_name or qq_name not in qq_name_dic:continue
- udp_client.sendto(msg.encode('utf-8'),qq_name_dic[qq_name])
- back_msg,addr = udp_client.recvfrom(1024)
- print('来自[%s:%s]的一条消息:\033[41m%s\033[0m'%(addr[0],addr[1],back_msg.decode('utf-8')))
- udp_client.close()
二、tcp与udp对比
tcp基于链接通信,数据流式协议
基于链接,则需要listen(backlog),指定连接池的大小
基于链接,必须先运行的服务端,然后客户端发起链接请求
对于mac/linux系统:如果客户端断开了链接,那服务端的链接recv将会阻塞,通讯循环收到的是一直空(解决方法是:服务端在收消息后加上if判断,空消息就break掉通信循环)
对于windows系统:如果一端断开了链接,那另外一端的链接也跟着出错,(解决方法是:服务端通信循环内加异常处理,捕捉到异常后就break掉通讯循环)
相对于upd传输速度慢
流式协议 会粘包 不可以发空 send recv 不是 一 一对应
tcp适用于:
数据一定要可靠
远程执行命令
下载文件
udp无链接,数据报式协议
无链接,因而无需listen(backlog),更加没有什么链接池
无链接,udp的sendto不用管是否有一个正在运行的服务端可以一直发消息,只不过数据可能会丢失
recvfrom收的数据小于sendto发送的数据时,在mac和linux系统上数据直接丢失,在windows系统上发送的比接收的大直接报错
只有sendto发送数据没有recvfrom收数据,数据丢失
数据报协议 不会粘包 可以发空 sendto recvfrom 一 一 对应 数据报协议 数据不安全 有可能发送数据 > 1024 或者网络网络异常 数据没了
udp适用于
QQ
查询操作 eg: ntp时间服务器 dns服务器(查域名,转ip) 能保证查询效率高,数据虽然不可靠,传输过程中可能会发生数据丢失
三、基于socket实现文件网络传输
简单版本
服务端
- import socket
- import os
- import hashlib
- import json
- import struct
-
- ip_port = ("127.0.0.1",9001)
- back_log = 5
- buffer_size = 1024
- base_path = os.path.dirname(os.path.abspath(__file__))
- share_path =os.path.join(base_path,"share")
-
- ftp_server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
- ftp_server.bind(ip_port)
- ftp_server.listen(back_log)
-
- def creat_md5(file):
- md5_value = hashlib.md5()
- with open(file,"rb") as f:
- while True:
- data = f.read(1024)
- if not data:
- break
- md5_value.update(data)
- return md5_value.hexdigest()
-
-
- while True:
- print("FTP服务器开始运行啦!")
- conn,address = ftp_server.accept()
- while True:
- try:
- # 第一步:收命令
- res = conn.recv(8096) #"get a.txt"
- if not res: continue
- # 第二步:解析命令, 提取相应的命令参数
- cmds = res.decode("utf-8").split()
- file_name = cmds[1]
- if cmds[0] == "get":
-
- file_path = os.path.join(share_path,file_name)
- file_md5 = creat_md5(file_path)
- file_size = os.path.getsize(file_path)
- #第三步:以读的方式打开文件,读取文件内容 发送给客户端,
- # 1、先自制报头,传递文件的相关信息
- header_dic = {
- "filename":file_name,
- "filemd5":file_md5,
- "filesize":file_size
- }
- header_json = json.dumps(header_dic).encode("utf-8")
- header_length = len(header_json)
- header_struct = struct.pack("i",header_length)
- # 2、发送报头的长度
- conn.send(header_struct)
- # 3、发送报头,传递文件的各种信息
- conn.send(header_json)
- # 4、打开文件,读取内容,一行一行的发送读取的内容给客户端
- with open(file_path,"rb") as f:
- for line in f:
- conn.send(line)
-
- except Exception as e:
- print(e)
- break
- conn.close()
客户端
- import socket
- import os
- import struct
- import json
- import time
-
- ip_port = ("127.0.0.1", 9001)
- buffer_size = 1024
- base_path = os.path.dirname(os.path.abspath(__file__))
- download_path = os.path.join(base_path,"download")
-
- ftp_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- ftp_client.connect(ip_port)
-
- while True:
- # 第一步:写命令,发送命令给服务端
- cmd = input("请输入命令: ")
- if not cmd: continue
- if cmd == "quit": break
- ftp_client.send(cmd.encode("utf-8"))
- # 第二步:收取自制报头的长度
- header_struct = ftp_client.recv(4)
- header_length = struct.unpack("i", header_struct)[0]
- print("报头长度",header_length)
- # 第三步:收取自制报头的信息
- header_json = ftp_client.recv(header_length).decode("utf-8")
- header_dic = json.loads(header_json)
- print("报头字典",header_dic)
- # 第四步:根据报头信息拼出文件的各种信息
- file_name = header_dic["filename"]
- file_md5 = header_dic["filemd5"]
- file_size = header_dic["filesize"]
- file_download_path = os.path.join(download_path,file_name)
- # 第五步:以写的方式打开一个新文件,接收服务端发来的文件内容写入客户的新文件
- with open(file_download_path,"wb") as f:
- data_size = 0
- start_time = time.perf_counter()
- while data_size < file_size:
- line = ftp_client.recv(buffer_size)
- f.write(line)
- data_size = data_size + len(line)
- # print("已经写入数据",data_size)
- download_percent = int((data_size/file_size)*100)
- # print("百分比",download_percent)
- a = "*" * download_percent
- # print(a)
- b = "." * (100 - download_percent)
- # print(b)
- c = (data_size/file_size)*100
- during_time = time.perf_counter() - start_time
- print("\r{:3.0f}%[{}-{}]共计用时:{:.3f}s".format(c,a,b,during_time),end="")
- # sys.stdout.flush()
- print("\n" + "执行结束")
- ftp_client.close()
基于类写的文件传输
服务端
- import socket
- import os
- import struct
- import pickle
-
-
- class TCPServer:
- address_family = socket.AF_INET
- socket_type = socket.SOCK_STREAM
- listen_count = 5
- max_recv_bytes = 8192
- coding = 'utf-8'
- allow_reuse_address = False
- # 下载的文件存放路径
- down_filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'share')
- # 上传的文件存放路径
- upload_filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'upload')
-
- def __init__(self,server_address,bind_and_listen=True):
- self.server_address = server_address
- self.socket = socket.socket(self.address_family,self.socket_type)
-
- if bind_and_listen:
- try:
- self.server_bind()
- self.server_listen()
- except Exception:
- self.server_close()
-
- def server_bind(self):
- if self.allow_reuse_address: #重用ip和端口
- self.socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
- self.socket.bind(self.server_address)
-
- def server_listen(self):
- self.socket.listen(self.listen_count)
-
- def server_close(self):
- self.socket.close()
-
- def server_accept(self):
- return self.socket.accept()
-
- def conn_close(self,conn):
- conn.close()
-
- def run(self):
- print('starting...')
- while True:
- self.conn,self.client_addr = self.server_accept()
- print(self.client_addr)
- while True:
- try:
- res = self.conn.recv(self.max_recv_bytes)
- if not res:continue
- cmds = res.decode(self.coding).split()
- if hasattr(self,cmds[0]):
- func = getattr(self,cmds[0])
- func(cmds)
- except Exception:
- break
- self.conn_close(self.conn)
-
- def get(self,cmds):
- """ 下载
- 1.找到下载的文件
- 2.发送 header_size
- 3.发送 header_bytes file_size
- 4.读文件 rb 发送 send(line)
- 5.若文件不存在,发送0 client提示:文件不存在
- :param cmds: 下载的文件 eg:['get','a.txt']
- :return:
- """
- filename = cmds[1]
- file_path = os.path.join(self.down_filepath, filename)
- if os.path.isfile(file_path):
- header = {
- 'filename': filename,
- 'md5': 'xxxxxx',
- 'file_size': os.path.getsize(file_path)
- }
- header_bytes = pickle.dumps(header) #直接用pickle转为bytes,不用json+encode转为bytes
- self.conn.send(struct.pack('i', len(header_bytes)))
- self.conn.send(header_bytes)
- with open(file_path, 'rb') as f:
- for line in f:
- self.conn.send(line)
- else:
- self.conn.send(struct.pack('i', 0))
-
- def put(self,cmds):
- """ 上传
- 1.接收4个bytes 得到文件的 header_size
- 2.根据 header_size 得到 header_bytes header_dic
- 3.根据 header_dic 得到 file_size
- 3.以写的形式 打开文件 f.write()
- :param cmds: 下载的文件 eg:['put','a.txt']
- :return:
- """
- obj = self.conn.recv(4)
- header_size = struct.unpack('i', obj)[0]
- header_bytes = self.conn.recv(header_size)
- header_dic = pickle.loads(header_bytes)
- print(header_dic)
- file_size = header_dic['file_size']
- filename = header_dic['filename']
-
- with open('%s/%s' % (self.upload_filepath, filename), 'wb') as f:
- recv_size = 0
- while recv_size < file_size:
- res = self.conn.recv(self.max_recv_bytes)
- f.write(res)
- recv_size += len(res)
-
-
- tcp_server = TCPServer(('127.0.0.1',8080))
- tcp_server.run()
- tcp_server.server_close()
客户端
- import socket
- import struct
- import pickle
- import os
- class FTPClient:
- address_family = socket.AF_INET
- socket_type = socket.SOCK_STREAM
- # 下载的文件存放路径
- down_filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'download')
- # 上传的文件存放路径
- upload_filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'share')
- coding = 'utf-8'
- max_recv_bytes = 8192
- def __init__(self, server_address, connect=True):
- self.server_address = server_address
- self.socket = socket.socket(self.address_family, self.socket_type)
- if connect:
- try:
- self.client_connect()
- except Exception:
- self.client_close()
- def client_connect(self):
- self.socket.connect(self.server_address)
- def client_close(self):
- self.socket.close()
- def run(self):
- while True:
- # get a.txt 下载 put a.txt 上传
- msg = input(">>>:").strip()
- if not msg: continue
- self.socket.send(msg.encode(self.coding))
- cmds = msg.split()
- if hasattr(self,cmds[0]):
- func = getattr(self,cmds[0])
- func(cmds)
- def get(self, cmds):
- """ 下载
- 1.得到 header_size
- 2.得到 header_types header_dic
- 3.得到 file_size file_name
- 4.以写的形式 打开文件
- :param cmds: 下载的内容 eg: cmds = ['get','a.txt']
- :return:
- """
- obj = self.socket.recv(4)
- header_size = struct.unpack('i', obj)[0]
- if header_size == 0:
- print('文件不存在')
- else:
- header_types = self.socket.recv(header_size)
- header_dic = pickle.loads(header_types)
- print(header_dic)
- file_size = header_dic['file_size']
- filename = header_dic['filename']
- with open('%s/%s' % (self.down_filepath, filename), 'wb') as f:
- recv_size = 0
- while recv_size < file_size:
- res = self.socket.recv(self.max_recv_bytes)
- f.write(res)
- recv_size += len(res)
- print('总大小:%s 已下载:%s' % (file_size, recv_size))
- else:
- print('下载成功!')
- def put(self, cmds):
- """ 上传
- 1.查看上传的文件是否存在
- 2.上传文件 header_size
- 3.上传文件 header_bytes
- 4.以读的形式 打开文件 send(line)
- :param cmds: 上传的内容 eg: cmds = ['put','a.txt']
- :return:
- """
- filename = cmds[1]
- file_path = os.path.join(self.upload_filepath, filename)
- if os.path.isfile(file_path):
- file_size = os.path.getsize(file_path)
- header = {
- 'filename': os.path.basename(filename),
- 'md5': 'xxxxxx',
- 'file_size': file_size
- }
- header_bytes = pickle.dumps(header)
- self.socket.send(struct.pack('i', len(header_bytes)))
- self.socket.send(header_bytes)
- with open(file_path, 'rb') as f:
- send_bytes = b''
- for line in f:
- self.socket.send(line)
- send_bytes += line
- print('总大小:%s 已上传:%s' % (file_size, len(send_bytes)))
- else:
- print('上传成功!')
- else:
- print('文件不存在')
- ftp_client = FTPClient(('127.0.0.1',8080))
- ftp_client.run()
- ftp_client.client_close()
Python之路(第三十二篇) 网络编程:udp套接字、简单文件传输的更多相关文章
- Python之路(第三十四篇) 网络编程:验证客户端合法性
一.验证客户端合法性 如果你想在分布式系统中实现一个简单的客户端链接认证功能,又不像SSL那么复杂,那么利用hmac+加盐的方式来实现. 客户端验证的总的思路是将服务端随机产生的指定位数的字节发送到客 ...
- Python之路(第三十六篇)并发编程:进程、同步异步、阻塞非阻塞
一.理论基础 进程的概念起源于操作系统,是操作系统最核心的概念,也是操作系统提供的最古老也是最重要的抽象概念之一.操作系统的其他所有内容都是围绕进程的概念展开的. 即使可以利用的cpu只有一个(早期的 ...
- Python之路(第三十八篇) 并发编程:进程同步锁/互斥锁、信号量、事件、队列、生产者消费者模型
一.进程锁(同步锁/互斥锁) 进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的, 而共享带来的是竞争,竞争带来的结果就是错乱,如何控制,就是加锁处理. 例 ...
- Python之路(第三十五篇) 并发编程:操作系统的发展史、操作系统的作用
一.操作系统发展史 第一阶段:手工操作 —— 真空管和穿孔卡片 第一代之前人类是想用机械取代人力,第一代计算机的产生是计算机由机械时代进入电子时代的标志,从Babbage失败之后一直到第二次世界大 ...
- Python之路【第十二篇】:JavaScrpt -暂无内容-待更新
Python之路[第十二篇]:JavaScrpt -暂无内容-待更新
- Python之路【第十二篇】:Python面向对象高级
一.反射 1 什么是反射 反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问.检测和修改它本身状态或行为的一种能力(自省).这一概念的提出很快引发了计算机科学领域关于应用反射性的研究 ...
- Python之路【第十二篇续】jQuery案例详解
jQuery 1.jQuery和JS和HTML的关系 首先了HTML是实际展示在用户面前的用户可以直接体验到的,JS是操作HTML的他能改变HTML实际展示给用户的效果! 首先了解JS是一门语言,他是 ...
- Python之路【第二十二篇】:Django之Model操作
Django之Model操作 一.字段 AutoField(Field) - int自增列,必须填入参数 primary_key=True BigAutoField(AutoField) - bi ...
- python之路【第十二篇】: MYSQL
一. 概述 Mysql是最流行的关系型数据库管理系统,在WEB应用方面MySQL是最好的RDBMS(Relational Database Management System:关系数据库管理系统)应用 ...
随机推荐
- apache_php_mysql
软件下载 目前,Apache和PHP均未出现官方的64位版本. Apache 64位: http://www.blackdot.be/?inc=apache/binaries 这个安装文件我已经上传到 ...
- Python第8天
zip() 拉链方法 max(字典) 默认比较字典的key,不同类型的数据不能比较,只要可以被for迭代即可 利用zip与max(字典)共同使用 ord() — chr() ascii码表数字与 ...
- centos mysql 修改mysql用户密码
查看服务器版本: cat /etc/redhat-release 查看mysql 版本: mysql -u root -p use mysql; ###mysql 5.7以上.. update use ...
- Dashboard安装配置
获取dashboard yaml文件 curl -O https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/ ...
- Linux命令学习——strings
strings命令的作用是打印文件中的可打印字符. 常用的参数有: -f --print-file-name,在每个输出的字符串前打印文件名. -t [radix] --radix=[radix],输 ...
- SQL Server扩展事件的使用ring_buffer target时“丢失”事件的原因分析以及ring_buffer target潜在的问题
事情起因: 排查SQL Server上的死锁问题,一开始想到的就是扩展事件, 第一种方案,开profile守株待兔吧,显得太low了,至于profile的变种trace吧,垂垂老矣,也一直没怎么用过. ...
- js生成二维码并保存成图片下载
我这里使用是jQuery,和jquery.qrcode.js,需要的可以自己找链接下载.示例代码仅做参考 html代码: <a id="downloadLink">&l ...
- 谈谈你对spring的理解?
spring么,就是春天了.春天,动物.....不可描述的季节……你懂得!!! 希望这么说能把面试官逗乐吧. spring可以实现java模块化开发,贯穿表现层,业务层,逻辑层,实现了各个层之间的解耦 ...
- 并发系列2:Java并发的基石,volatile关键字、synchronized关键字、乐观锁CAS操作
由并发大师Doug Lea操刀的并发包Concurrent是并发编程的重要包,而并发包的基石又是volatile关键字.synchronized关键字.乐观锁CAS操作这些基础.因此了解他们的原理对我 ...
- Java开发经常容易犯的错误
调用Set.addAll()方法时抛UnsupportedOperationException异常 上面的Set是Map中keySet的返回结果. 程序中这样两句代码运行时,抛UnsupportedO ...