一、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套接字、简单文件传输的更多相关文章

  1. Python之路(第三十四篇) 网络编程:验证客户端合法性

    一.验证客户端合法性 如果你想在分布式系统中实现一个简单的客户端链接认证功能,又不像SSL那么复杂,那么利用hmac+加盐的方式来实现. 客户端验证的总的思路是将服务端随机产生的指定位数的字节发送到客 ...

  2. Python之路(第三十六篇)并发编程:进程、同步异步、阻塞非阻塞

    一.理论基础 进程的概念起源于操作系统,是操作系统最核心的概念,也是操作系统提供的最古老也是最重要的抽象概念之一.操作系统的其他所有内容都是围绕进程的概念展开的. 即使可以利用的cpu只有一个(早期的 ...

  3. Python之路(第三十八篇) 并发编程:进程同步锁/互斥锁、信号量、事件、队列、生产者消费者模型

    一.进程锁(同步锁/互斥锁) 进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的, 而共享带来的是竞争,竞争带来的结果就是错乱,如何控制,就是加锁处理. 例 ...

  4. Python之路(第三十五篇) 并发编程:操作系统的发展史、操作系统的作用

    一.操作系统发展史 第一阶段:手工操作 —— 真空管和穿孔卡片 ​ 第一代之前人类是想用机械取代人力,第一代计算机的产生是计算机由机械时代进入电子时代的标志,从Babbage失败之后一直到第二次世界大 ...

  5. Python之路【第十二篇】:JavaScrpt -暂无内容-待更新

    Python之路[第十二篇]:JavaScrpt -暂无内容-待更新

  6. Python之路【第十二篇】:Python面向对象高级

    一.反射 1 什么是反射 反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问.检测和修改它本身状态或行为的一种能力(自省).这一概念的提出很快引发了计算机科学领域关于应用反射性的研究 ...

  7. Python之路【第十二篇续】jQuery案例详解

    jQuery 1.jQuery和JS和HTML的关系 首先了HTML是实际展示在用户面前的用户可以直接体验到的,JS是操作HTML的他能改变HTML实际展示给用户的效果! 首先了解JS是一门语言,他是 ...

  8. Python之路【第二十二篇】:Django之Model操作

    Django之Model操作   一.字段 AutoField(Field) - int自增列,必须填入参数 primary_key=True BigAutoField(AutoField) - bi ...

  9. python之路【第十二篇】: MYSQL

    一. 概述 Mysql是最流行的关系型数据库管理系统,在WEB应用方面MySQL是最好的RDBMS(Relational Database Management System:关系数据库管理系统)应用 ...

随机推荐

  1. ETL hive update 之 deltamerge 优化

    full join 横向join ,不能map join 走shuffle row_number() over ( partition by 主键 order by $flag desc) rank ...

  2. C罗转会尤文图斯

    皇家马德里头号球星C罗转会意甲尤文图斯,结束了9年的皇马生涯,已获得5座金球奖.

  3. EasyUI在window中使用kindeditor 4.1.10在IE9中不能回显、获得焦点编辑的问题

    描述 :kindeditor4.1.10版本是当前最新的版本,在浏览器兼容性和功能方面都是值得一赞的,在开发中能方便快捷的满足一些开发需求. 问题 :  问题总是有的.  在使用过程中,遇到EasyU ...

  4. 大数据架构工具hadoop

    Hadoop是一个开源框架,它允许在整个集群使用简单编程模型计算机的分布式环境存储并处理大数据.它的目的是从单一的服务器到上千台机器的扩展,每一个台机都可以提供本地计算和存储. “90%的世界数据在过 ...

  5. php 计算坐标点方圆周围多少米的坐标算法

    //地球半径 6371千米 const EARTH_ROUNT = 6371; /** * @param $distance 方圆多少千米 默认500米 */ private function _ge ...

  6. Game Engine Architecture 11

    [Game Engine Architecture 11] 1.three most-common techniques used in modern game engines. 1)Cel Anim ...

  7. centOS7 关闭swap

    [root@cdh- sbin]# free -g total used free shared buff/cache available Mem: Swap: [root@cdh- sbin]# c ...

  8. pandas操作行集锦

    pandas移花接木 数据准备两表: 我们接下来要进行的操作: 增 将两表进行合并 # 把两张表合并,但是这样有问题,索引会重复的进行0-19 students = page_001.append(p ...

  9. 微信小程序如何接入?

    1.线下扫码:小程序最基础的获取方式,是二维码.大家可以打开扫一扫,通过微信扫描线下二维码的方式进入小程序.这也是官方宣传中,最普遍的实用场景. 如何生成小程序导入码? 2.微信搜索 3.公众号关联 ...

  10. Java反转二叉树

    // 二叉树节点定义 public class BinaryTreefanzhuan { class TreeNode{ int value; TreeNode left; TreeNode righ ...