在这里,我们将给大家分享关于CocosCreator2.0使用protobufjs6.8,及web的html页面使用protobufjs6.8的知识,让您更了解cocoscreator嵌入网页的本质,
在这里,我们将给大家分享关于Cocos Creator 2.0 使用 protobufjs6.8,及 web 的 html 页面使用 protobufjs6.8的知识,让您更了解cocos creator 嵌入网页的本质,同时也会涉及到如何更有效地C#使用Protocol Buffer(ProtoBuf)进行Unity中的Socket通信、C#语言使用gRPC、protobuf(Google Protocol Buffers)实现文件传输功能、Centos6.4 下安装 protobuf 及简单使用、cocos Creator protobuf接入的内容。
本文目录一览:- Cocos Creator 2.0 使用 protobufjs6.8,及 web 的 html 页面使用 protobufjs6.8(cocos creator 嵌入网页)
- C#使用Protocol Buffer(ProtoBuf)进行Unity中的Socket通信
- C#语言使用gRPC、protobuf(Google Protocol Buffers)实现文件传输功能
- Centos6.4 下安装 protobuf 及简单使用
- cocos Creator protobuf接入
Cocos Creator 2.0 使用 protobufjs6.8,及 web 的 html 页面使用 protobufjs6.8(cocos creator 嵌入网页)
最近用 golang 写斗地主游戏客户端用的是 Cocos Creator 因为之前一直使用的是 tcp 协议与客户端 Cocos (lua) 使用 protobuf 通信,切到 js 上还是遇到了点麻烦。因为不懂 nodejs,一直以为 Cocos Creator 使用的就是 js 所以遇到了点麻烦。
先说一下在 web 页面使用 protobufjs 与服务器之间的通信吧!(直接用 web 方式在 Cocos Creator 里一直找不到 proto 文件,可能还没搞清楚 Cocos Creator 加载资源的原理跟路径映射)
通信包的格式:前 8 位 (包大小)、2 位 (主命令)、2 位 (子命令)、包体;
先引入所需要的 js 文件:
<script type="text/javascript" src="./dist/long.js"></script>
<script type="text/javascript" src="./dist/bytebuffer.min.js"></script>
<script type="text/javascript" src="./dist/protobuf.min.js"></script>
所以需要两个函数把数值类型转换为所需要的字节类型,一个是转 64, 一个是转 16 位
function int64ToByte(num) {
//只有int类型,小于256点1字节,大于256点两字节,所有只能返过来
var bytes = new Array();
//没有64位只能前4位补0
bytes.push(0);
bytes.push(0);
bytes.push(0);
bytes.push(0);
bytes.push(num >> 24 & 0xff)
bytes.push(num >> 16 & 0xff)
bytes.push(num >> 8 & 0xff)
bytes.push(num & 0xff)
return bytes;
}
function int16ToByte(num) {
//只有int类型,小于256点1字节,大于256点两字节,所有只能返过来
var bytes = new Array();
bytes.push(num >> 8 & 0xff)
bytes.push(num & 0xff)
return bytes;
}
编写解码函数:
function bytes2int16(arr) {
return ((arr[0] & 0xFF)<<8)|(arr[1]&0xFF);
}
function bytes2int64(arr) {
//没有64位只能取后四位
return ((arr[4] & 0xFF)<<24)|((arr[5] & 0xFF)<<16)|((arr[6] & 0xFF)<<8)|(arr[7]&0xFF);
}
然后就是包体的封装:
class Packet{
constructor(options){
this.mainId=options.mainId||0;
this.subId=options.subId||0;
this.sendData=options.sendData||new Uint8Array(0);
}
encode(){
//包的总长度
var all_length = 12 + this.sendData.byteLength;
//返回的数组
var myArray = new ArrayBuffer(all_length);
//以byte形式的数组返回
var resData = new Uint8Array(myArray);
//转换成服务器uint64的包头类型
var all_length_byte = int64ToByte(all_length)
//添加包头
for (let i = 0; i < all_length_byte.length; i++) {
resData[i] = all_length_byte[i];
}
//主命令
var mainId_byte = int16ToByte(this.mainId);
for (let i = 0; i < mainId_byte.length; i++) {
resData[i + 8] = mainId_byte[i];
}
//子命令
var subId_byte = int16ToByte(this.subId);
for (let i = 0; i < subId_byte.length; i++) {
resData[i + 10] = subId_byte[i];
}
//内容
for (let i = 0; i < this.sendData.length; i++) {
resData[i + 12] = this.sendData[i];
}
return myArray;
}
}
封装发送数据:
var buffer;
var ModelRegGameServerInfo;
protobuf.load("reg_rpc_model.proto", function (err, root) {
if (err)
throw err;
ModelRegGameServerInfo = root.lookupType("grpcmodel.ModelRegGameServerInfo");
var server = ModelRegGameServerInfo.create({
serverId: 23,
addr: "abc",
count: 1,
serverType: 2,
gameName: "kwo",
gameType: 3
});
//编码
buffer = ModelRegGameServerInfo.encode(server).finish();
//解码
// var svr=ModelRegGameServerInfo.decode(buffer);
// console.log(svr);
});
然后就是发送数据与解析数据:
ws = new WebSocket("ws://localhost:7026/GameWs");
ws.binaryType = "arraybuffer";
// ws.binaryType = "blob";
ws.onopen = function (event) {
console.log("Send Text WS was opened.");
};
ws.onmessage = function (event) {
var arr = new Uint8Array(event.data);
// 包大小
// console.log(bytes2int64(arr.slice(0, 8)));
let mainId = bytes2int16(arr.slice(8, 10));
let subId = bytes2int16(arr.slice(10, 12));
if (mainId==22&&subId==3&&arr.byteLength > 12) {
var svr=ModelRegGameServerInfo.decode(arr.slice(12, arr.byteLength));
console.log(svr);
}
};
ws.onerror = function (event) {
console.log("Send Text fired an error");
};
ws.onclose = function (event) {
console.log("WebSocket instance closed.");
};
//写个按钮调用这个方法
function send() {
if (ws.readyState == WebSocket.OPEN) {
var pck = new Packet({
mainId: 22,
subId: 3,
sendData: buffer
});
ws.send(pck.encode());
}
else {
console.log("WebSocket instance wasn''t ready...");
}
}
后来这种方式在 Cocos Creator 里没法使用,查了好多资料才搞定,便还是需要用到上面包体的封装跟数值与字节的转换。
首先要装的是 nodejs 在 https://nodejs.org/zh-cn/download/ 下载,我下载的 zip 版本
将文件解压到要安装的位置,并新建两个目录:node-global :npm 全局安装位置、node-cache:npm 缓存路径。
把解压的根目录配置到环境变量里。
输入:node -v 与 npm -v 来测试安装是否成功
配置全局安装位置:npm config set prefix "D:\dev\node\node-global"
配置全局缓存位置:npm config set cache "D:\dev\node\node-cache"
安装 protobufjs:npm install -g protobufjs
进入到 node-cache\node_modules\protobufjs\dist 目录把 protobuf.js 复制到 Cocos Creator 里然后导入为插件
进入到 node-cache 目录下,把写好的 proto 文件复制一份到此目录下
然后执行:pbjs -t static-module -w commonjs -o reg_rpc_model.js reg_rpc_model.proto
生成所需要的 js 文件,把 js 文件复制到 Cocos Creator 里;把生成的 js 文件第一行:var $protobuf = require ("protobufjs/minimal"); 改为 var $protobuf = protobuf;
然后开始写与服务器通信的代码:
// { grpcmodel }这个是proto文件里的包名 from后面是路径
import { grpcmodel } from "..model/reg_rpc_model"
ws = new WebSocket("ws://localhost:7026/GameWs");
ws.binaryType = "arraybuffer";
// ws.binaryType = "blob";
ws.onopen = function (event) {
console.log("Send Text WS was opened.");
};
ws.onmessage = function (event) {
var arr = new Uint8Array(event.data);
// 包大小
// console.log(bytes2int64(arr.slice(0, 8)));
let mainId = bytes2int16(arr.slice(8, 10));
let subId = bytes2int16(arr.slice(10, 12));
if (mainId==22&&subId==3&&arr.byteLength > 12) {
console.log(arr);
let msg=grpcmodel.ModelRegGameServerInfo.decode(arr.slice(12, arr.byteLength));
console.log(msg);
}
};
ws.onerror = function (event) {
console.log("Send Text fired an error");
};
ws.onclose = function (event) {
console.log("WebSocket instance closed.");
};
function send(){
let message = grpcmodel.ModelRegGameServerInfo.create({
serverId: 23,
addr: "abc",
count: 1,
serverType: 2,
gameName: "kwo",
gameType: 3
});
let msg_data = grpcmodel.ModelRegGameServerInfo.encode(message).finish();
if (ws.readyState == WebSocket.OPEN) {
var pck = new Packet({
mainId: 22,
subId: 3,
sendData: msg_data
});
ws.send(pck.encode());
} else {
console.log("web socket not open")
}
}
原文地址:http://www.1025m.com/37.html
C#使用Protocol Buffer(ProtoBuf)进行Unity中的Socket通信
首先来说一下本文中例子所要实现的功能:
- 基于ProtoBuf序列化对象
- 使用Socket实现时时通信
- 数据包的编码和解码
下面来看具体的步骤:
一、Unity中使用ProtoBuf
导入DLL到Unity中,
创建网络传输的模型类:
using System; using ProtoBuf; //添加特性,表示可以被ProtoBuf工具序列化 [ProtoContract] public class NetModel { //添加特性,表示该字段可以被序列化,1可以理解为下标 [ProtoMember(1)] public int ID; [ProtoMember(2)] public string Commit; [ProtoMember(3)] public string Message; } using System; using ProtoBuf; //添加特性,表示可以被ProtoBuf工具序列化 [ProtoContract] public class NetModel { //添加特性,表示该字段可以被序列化,1可以理解为下标 [ProtoMember(1)] public int ID; [ProtoMember(2)] public string Commit; [ProtoMember(3)] public string Message; }
在Unity中添加测试脚本,介绍ProtoBuf工具的使用。
using System; using System.IO; public class Test : MonoBehavIoUr { void Start () { //创建对象 NetModel item = new NetModel(){ID = 1,Commit = "LanOu",Message = "Unity"}; //序列化对象 byte[] temp = Serialize(item); //ProtoBuf的优势一:小 Debug.Log(temp.Length); //反序列化为对象 NetModel result = DeSerialize(temp); Debug.Log(result.Message); } // 将消息序列化为二进制的方法 // < param name="model">要序列化的对象< /param> private byte[] Serialize(NetModel model) { try { //涉及格式转换,需要用到流,将二进制序列化到流中 using (MemoryStream ms = new MemoryStream()) { //使用ProtoBuf工具的序列化方法 ProtoBuf.Serializer.Serialize<NetModel> (ms,model); //定义二级制数组,保存序列化后的结果 byte[] result = new byte[ms.Length]; //将流的位置设为0,起始点 ms.Position = 0; //将流中的内容读取到二进制数组中 ms.Read (result,result.Length); return result; } } catch (Exception ex) { Debug.Log ("序列化失败: " + ex.ToString()); return null; } } // 将收到的消息反序列化成对象 // < returns>The serialize.< /returns> // < param name="msg">收到的消息.</param> private NetModel DeSerialize(byte[] msg) { try { using (MemoryStream ms = new MemoryStream()) { //将消息写入流中 ms.Write (msg,msg.Length); //将流的位置归0 ms.Position = 0; //使用工具反序列化对象 NetModel result = ProtoBuf.Serializer.Deserialize<NetModel> (ms); return result; } } catch (Exception ex) { Debug.Log("反序列化失败: " + ex.ToString()); return null; } } } using System; using System.IO; public class Test : MonoBehavIoUr { void Start () { //创建对象 NetModel item = new NetModel(){ID = 1,Message = "Unity"}; //序列化对象 byte[] temp = Serialize(item); //ProtoBuf的优势一:小 Debug.Log(temp.Length); //反序列化为对象 NetModel result = DeSerialize(temp); Debug.Log(result.Message); } // 将消息序列化为二进制的方法 // < param name="model">要序列化的对象< /param> private byte[] Serialize(NetModel model) { try { //涉及格式转换,需要用到流,将二进制序列化到流中 using (MemoryStream ms = new MemoryStream()) { //使用ProtoBuf工具的序列化方法 ProtoBuf.Serializer.Serialize<NetModel> (ms,result.Length); return result; } } catch (Exception ex) { Debug.Log ("序列化失败: " + ex.ToString()); return null; } } // 将收到的消息反序列化成对象 // < returns>The serialize.< /returns> // < param name="msg">收到的消息.</param> private NetModel DeSerialize(byte[] msg) { try { using (MemoryStream ms = new MemoryStream()) { //将消息写入流中 ms.Write (msg,msg.Length); //将流的位置归0 ms.Position = 0; //使用工具反序列化对象 NetModel result = ProtoBuf.Serializer.Deserialize<NetModel> (ms); return result; } } catch (Exception ex) { Debug.Log("反序列化失败: " + ex.ToString()); return null; } } }
二、Unity中使用Socket实现时时通信
通信应该实现的功能:
- 服务器可以时时监听多个客户端
- 服务器可以时时监听某一个客户端消息
- 服务器可以时时给某一个客户端发消息
- 首先我们需要定义一个客户端对象
using System; using System.Net.sockets; // 表示一个客户端 public class NetUserToken { //连接客户端的Socket public Socket socket; //用于存放接收数据 public byte[] buffer; public NetUserToken() { buffer = new byte[1024]; } // 接受消息 // < param name="data">Data.< /param> public void Receive(byte[] data) { UnityEngine.Debug.Log("接收到消息!"); } // 发送消息 //< param name="data">Data.< /param> public void Send(byte[] data) { } } using System; using System.Net.sockets; // 表示一个客户端 public class NetUserToken { //连接客户端的Socket public Socket socket; //用于存放接收数据 public byte[] buffer; public NetUserToken() { buffer = new byte[1024]; } // 接受消息 // < param name="data">Data.< /param> public void Receive(byte[] data) { UnityEngine.Debug.Log("接收到消息!"); } // 发送消息 //< param name="data">Data.< /param> public void Send(byte[] data) { } }
然后实现我们的服务器代码
using System.Collections; using System.Collections.Generic; using System.Net; using System; using System.Net.sockets; public class NetServer{ //单例脚本 public static readonly NetServer Instance = new NetServer(); //定义tcp服务器 private Socket server; private int maxClient = 10; //定义端口 private int port = 35353; //用户池 private Stack<NetUserToken> pools; private NetServer() { //初始化socket server = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp); server.Bind(new IPEndPoint(IPAddress.Any,port)); } //开启服务器 public void Start() { server.Listen(maxClient); UnityEngine.Debug.Log("Server OK!"); //实例化客户端的用户池 pools = new Stack<NetUserToken>(maxClient); for(int i = 0; i < maxClient; i++) { NetUserToken usertoken = new NetUserToken(); pools.Push(usertoken); } //可以异步接受客户端,BeginAccept函数的第一个参数是回调函数,当有客户端连接的时候自动调用 server.BeginAccept (AsyncAccept,null); } //回调函数, 有客户端连接的时候会自动调用此方法 private void AsyncAccept(IAsyncResult result) { try { //结束监听,同时获取到客户端 Socket client = server.EndAccept(result); UnityEngine.Debug.Log("有客户端连接"); //来了一个客户端 NetUserToken userToken = pools.Pop(); userToken.socket = client; //客户端连接之后,可以接受客户端消息 BeginReceive(userToken); //尾递归,再次监听是否还有其他客户端连入 server.BeginAccept(AsyncAccept,null); } catch (Exception ex) { UnityEngine.Debug.Log(ex.ToString()); } } //异步监听消息 private void BeginReceive(NetUserToken userToken) { try { //异步方法 userToken.socket.BeginReceive(userToken.buffer,userToken.buffer.Length,SocketFlags.None,EndReceive,userToken); } catch (Exception ex) { UnityEngine.Debug.Log(ex.ToString()); } } //监听到消息之后调用的函数 private void EndReceive(IAsyncResult result) { try { //取出客户端 NetUserToken userToken = result.AsyncState as NetUserToken; //获取消息的长度 int len = userToken.socket.EndReceive(result); if(len > 0) { byte[] data = new byte[len]; Buffer.Blockcopy(userToken.buffer,data,len); //用户接受消息 userToken.Receive(data); //尾递归,再次监听客户端消息 BeginReceive(userToken); } } catch (Exception ex) { UnityEngine.Debug.Log(ex.ToString()); } } } using System.Collections; using System.Collections.Generic; using System.Net; using System; using System.Net.sockets; public class NetServer{ //单例脚本 public static readonly NetServer Instance = new NetServer(); //定义tcp服务器 private Socket server; private int maxClient = 10; //定义端口 private int port = 35353; //用户池 private Stack<NetUserToken> pools; private NetServer() { //初始化socket server = new Socket(AddressFamily.InterNetwork,port)); } //开启服务器 public void Start() { server.Listen(maxClient); UnityEngine.Debug.Log("Server OK!"); //实例化客户端的用户池 pools = new Stack<NetUserToken>(maxClient); for(int i = 0; i < maxClient; i++) { NetUserToken usertoken = new NetUserToken(); pools.Push(usertoken); } //可以异步接受客户端,null); } //回调函数, 有客户端连接的时候会自动调用此方法 private void AsyncAccept(IAsyncResult result) { try { //结束监听,同时获取到客户端 Socket client = server.EndAccept(result); UnityEngine.Debug.Log("有客户端连接"); //来了一个客户端 NetUserToken userToken = pools.Pop(); userToken.socket = client; //客户端连接之后,可以接受客户端消息 BeginReceive(userToken); //尾递归,再次监听是否还有其他客户端连入 server.BeginAccept(AsyncAccept,null); } catch (Exception ex) { UnityEngine.Debug.Log(ex.ToString()); } } //异步监听消息 private void BeginReceive(NetUserToken userToken) { try { //异步方法 userToken.socket.BeginReceive(userToken.buffer,userToken); } catch (Exception ex) { UnityEngine.Debug.Log(ex.ToString()); } } //监听到消息之后调用的函数 private void EndReceive(IAsyncResult result) { try { //取出客户端 NetUserToken userToken = result.AsyncState as NetUserToken; //获取消息的长度 int len = userToken.socket.EndReceive(result); if(len > 0) { byte[] data = new byte[len]; Buffer.Blockcopy(userToken.buffer,len); //用户接受消息 userToken.Receive(data); //尾递归,再次监听客户端消息 BeginReceive(userToken); } } catch (Exception ex) { UnityEngine.Debug.Log(ex.ToString()); } } }
在Unity中开启服务器,并使用C#控制台模拟客户端连接、发送消息操作。测试OK了,Unity中可以时时监听到消息。
using UnityEngine; using System.Collections; public class CreateServer : MonoBehavIoUr { void StartServer () { NetServer.Instance.Start(); } } //C#控制台工程 using System; using System.Net; using System.Net.sockets; using System.IO; using System.Text; namespace Temp { class MainClass { public static void Main (string[] args) { TcpClient tc = new TcpClient(); IPEndPoint ip = new IPEndPoint(IPAddress.Parse("127.0.0.1"),35353); tc.Connect(ip); if(tc.Connected) { while(true) { string msg = Console.ReadLine(); byte[] result = Encoding.UTF8.GetBytes(msg); tc.GetStream().Write(result,result.Length); } } Console.ReadLine(); } } } using UnityEngine; using System.Collections; public class CreateServer : MonoBehavIoUr { void StartServer () { NetServer.Instance.Start(); } } //C#控制台工程 using System; using System.Net; using System.Net.sockets; using System.IO; using System.Text; namespace Temp { class MainClass { public static void Main (string[] args) { TcpClient tc = new TcpClient(); IPEndPoint ip = new IPEndPoint(IPAddress.Parse("127.0.0.1"),35353); tc.Connect(ip); if(tc.Connected) { while(true) { string msg = Console.ReadLine(); byte[] result = Encoding.UTF8.GetBytes(msg); tc.GetStream().Write(result,result.Length); } } Console.ReadLine(); } } }
三、数据包的编码和解码
首先,举个例子,这个月信用卡被媳妇刷爆了,面对房贷车贷的压力,我只能选择分期付款。。。
那么OK了,现在我想问一下,当服务器向客户端发送的数据过大时怎么办呢?
当服务器需要向客户端发送一条很长的数据,也会“分期付款!”,服务器会把一条很长的数据分成若干条小数据,多次发送给客户端。
可是,这样就又有另外一个问题,客户端接受到多条数据之后如何解析?
这里其实就是客户端的解码。server发数据一般采用“长度+内容”的格式,Client接收到数据之后,先提取出长度来,然后根据长度判断内容是否发送完毕。
再次重申,用户在发送序列化好的消息的前,需要先编码后再发送消息;用户在接受消息后,需要解码之后再解析数据(反序列化)。
using UnityEngine; using System.Collections.Generic; using System.IO; // 编码和解码 public class NetEncode { // 将数据编码 长度+内容 /// < param name="data">内容< /param> public static byte[] Encode(byte[] data) { //整形占四个字节,所以声明一个+4的数组 byte[] result = new byte[data.Length + 4]; //使用流将编码写二进制 MemoryStream ms = new MemoryStream(); BinaryWriter br = new BinaryWriter(ms); br.Write(data.Length); br.Write(data); //将流中的内容复制到数组中 System.Buffer.Blockcopy(ms.ToArray(),result,(int)ms.Length); br.Close(); ms.Close(); return result; } // 将数据解码 // < param name="cache">消息队列< /param> public static byte[] Decode(ref List<byte> cache) { //首先要获取长度,整形4个字节,如果字节数不足4个字节 if(cache.Count < 4) { return null; } //读取数据 MemoryStream ms = new MemoryStream(cache.ToArray()); BinaryReader br = new BinaryReader(ms); int len = br.ReadInt32(); //根据长度,判断内容是否传递完毕 if(len > ms.Length - ms.Position) { return null; } //获取数据 byte[] result = br.ReadBytes(len); //清空消息池 cache.Clear(); //讲剩余没处理的消息存入消息池 cache.AddRange(br.ReadBytes((int)ms.Length - (int)ms.Position)); return result; } } using UnityEngine; using System.Collections.Generic; using System.IO; // 编码和解码 public class NetEncode { // 将数据编码 长度+内容 /// < param name="data">内容< /param> public static byte[] Encode(byte[] data) { //整形占四个字节,所以声明一个+4的数组 byte[] result = new byte[data.Length + 4]; //使用流将编码写二进制 MemoryStream ms = new MemoryStream(); BinaryWriter br = new BinaryWriter(ms); br.Write(data.Length); br.Write(data); //将流中的内容复制到数组中 System.Buffer.Blockcopy(ms.ToArray(),(int)ms.Length); br.Close(); ms.Close(); return result; } // 将数据解码 // < param name="cache">消息队列< /param> public static byte[] Decode(ref List<byte> cache) { //首先要获取长度,整形4个字节,如果字节数不足4个字节 if(cache.Count < 4) { return null; } //读取数据 MemoryStream ms = new MemoryStream(cache.ToArray()); BinaryReader br = new BinaryReader(ms); int len = br.ReadInt32(); //根据长度,判断内容是否传递完毕 if(len > ms.Length - ms.Position) { return null; } //获取数据 byte[] result = br.ReadBytes(len); //清空消息池 cache.Clear(); //讲剩余没处理的消息存入消息池 cache.AddRange(br.ReadBytes((int)ms.Length - (int)ms.Position)); return result; } }
用户接受数据代码如下:
using System; using System.Collections.Generic; using System.Net.sockets; // 表示一个客户端 public class NetUserToken { //连接客户端的Socket public Socket socket; //用于存放接收数据 public byte[] buffer; //每次接受和发送数据的大小 private const int size = 1024; //接收数据池 private List<byte> receiveCache; private bool isReceiving; //发送数据池 private Queue<byte[]> sendCache; private bool isSending; //接收到消息之后的回调 public Action<NetModel> receiveCallBack; public NetUserToken() { buffer = new byte[size]; receiveCache = new List<byte>(); sendCache = new Queue<byte[]>(); } // 服务器接受客户端发送的消息 // < param name="data">Data.< /param> public void Receive(byte[] data) { UnityEngine.Debug.Log("接收到数据"); //将接收到的数据放入数据池中 receiveCache.AddRange(data); //如果没在读数据 if(!isReceiving) { isReceiving = true; ReadData(); } } // 读取数据 private void ReadData() { byte[] data = NetEncode.Decode(ref receiveCache); //说明数据保存成功 if(data != null) { NetModel item = NetSerilizer.DeSerialize(data); UnityEngine.Debug.Log(item.Message); if(receiveCallBack != null) { receiveCallBack(item); } //尾递归,继续读取数据 ReadData(); } else { isReceiving = false; } } // 服务器发送消息给客户端 public void Send() { try { if (sendCache.Count == 0) { isSending = false; return; } byte[] data = sendCache.Dequeue (); int count = data.Length / size; int len = size; for (int i = 0; i < count + 1; i++) { if (i == count) { len = data.Length - i * size; } socket.Send (data,i * size,len,SocketFlags.None); } UnityEngine.Debug.Log("发送成功!"); Send (); } catch (Exception ex) { UnityEngine.Debug.Log(ex.ToString()); } } public void WriteSendDate(byte[] data){ sendCache.Enqueue(data); if(!isSending) { isSending = true; Send(); } } } using System; using System.Collections.Generic; using System.Net.sockets; // 表示一个客户端 public class NetUserToken { //连接客户端的Socket public Socket socket; //用于存放接收数据 public byte[] buffer; //每次接受和发送数据的大小 private const int size = 1024; //接收数据池 private List<byte> receiveCache; private bool isReceiving; //发送数据池 private Queue<byte[]> sendCache; private bool isSending; //接收到消息之后的回调 public Action<NetModel> receiveCallBack; public NetUserToken() { buffer = new byte[size]; receiveCache = new List<byte>(); sendCache = new Queue<byte[]>(); } // 服务器接受客户端发送的消息 // < param name="data">Data.< /param> public void Receive(byte[] data) { UnityEngine.Debug.Log("接收到数据"); //将接收到的数据放入数据池中 receiveCache.AddRange(data); //如果没在读数据 if(!isReceiving) { isReceiving = true; ReadData(); } } // 读取数据 private void ReadData() { byte[] data = NetEncode.Decode(ref receiveCache); //说明数据保存成功 if(data != null) { NetModel item = NetSerilizer.DeSerialize(data); UnityEngine.Debug.Log(item.Message); if(receiveCallBack != null) { receiveCallBack(item); } //尾递归,继续读取数据 ReadData(); } else { isReceiving = false; } } // 服务器发送消息给客户端 public void Send() { try { if (sendCache.Count == 0) { isSending = false; return; } byte[] data = sendCache.Dequeue (); int count = data.Length / size; int len = size; for (int i = 0; i < count + 1; i++) { if (i == count) { len = data.Length - i * size; } socket.Send (data,SocketFlags.None); } UnityEngine.Debug.Log("发送成功!"); Send (); } catch (Exception ex) { UnityEngine.Debug.Log(ex.ToString()); } } public void WriteSendDate(byte[] data){ sendCache.Enqueue(data); if(!isSending) { isSending = true; Send(); } } }
ProtoBuf网络传输到这里就全部完成了。
C#语言使用gRPC、protobuf(Google Protocol Buffers)实现文件传输功能
初识gRPC还是一位做JAVA的同事在项目中用到了它,为了C#的客户端程序和java的服务器程序进行通信和数据交换,当时还是对方编译成C#,我直接调用。
后来,自己下来做了C#版本gRPC编写,搜了很多资料,但许多都是从入门开始?调用说“Say Hi!”这种官方标准的入门示例,然后遇到各种问题……
关于gRPC和Protobuf介绍,就不介绍了,网络上一搜一大把,随便一抓都是标准的官方,所以直接从使用说起。
gPRC源代码:https://github.com/grpc/grpc;
protobuf的代码仓库:
github仓库地址:https://github.com/google/protobuf;
Google下载protobuff下载地址:https://developers.google.com/protocol-buffers/docs/downloads。
1、新建解决方案
分别在VS中新建解决方案:GrpcTest;再在解决方案中新建三个项目:GrpcClient、GrpcServer、GrpcService,对应的分别是客户端(wpf窗体程序)、服务端(控制台程序)、gRPC服务者(控制台程序)。在GrpcClient和GrpcServer项目中添加对GrpcService的引用。
在VS中对3个项目添加工具包引用:右键点击“解决方案gRPCDemo”,点击“管理解决方案的NuGet程序包”,在浏览中分别搜索"Grpc"、"Grpc.Tools"、"Google.Protobuf",然后点击右面项目,全选,再点击安装(也可以用视图 -> 窗口 -> 程序包管理器控制台 中的"Install-Package Grpc"进行这一步,这里不提供这种方法,有兴趣自己百度)。
2、proto文件的语法
对于使用gRPC的通信框架,需要使用到对应的通信文件。在gRPC中,使用到的是proto格式的文件,对应的自然有其相应的语法。本文不详细阐述该文件的语法,感兴趣可以去官网看标准的语法,这儿有一个链接,中文翻译比较全的https://www.codercto.com/a/45372.html。需要对其文章内的1.3进行补充下:
- required:一个格式良好的消息一定要含有1个这种字段。表示该值是必须要设置的。
- optional:消息格式中该字段可以有0个或1个值(不超过1个)。
- repeated:在一个格式良好的消息中,这种字段可以重复任意多次(包括0次)。重复的值的顺序会被保留。表示该值可以重复,相当于java中的List。
本示例项目实现文件传输,因此在项目GrpcService中添加一个FileTransfer.proto文件,文件内容如下:
syntax = "proto3"; package GrpcService; service FileTransfer{ rpc FileDownload (FileRequest) returns (stream FileReply); rpc FileUpload (stream FileReply) returns(stream FileReturn); } //请求下载文件时,所需下载文件的文件名称集合 message FileRequest{ repeated string FileNames=1;//文件名集合 //repeated重复字段 类似链表;optional可有可无的字段;required必要设置字段 string Mark = 2;//携带的包 } //下载和上传文件时的应答数据 message FileReply{ string FileName=1;//文件名 int32 Block = 2;//标记---第几个数据 bytes Content = 3;//数据 string Mark = 4;//携带的包 } //数据上传时的返回值 message FileReturn{ string FileName=1;//文件名 string Mark = 2;//携带的包 }
3、编译proto文件为C#代码
proto文件仅仅只是定义了相关的数据,如果需要在代码中使用该格式,就需要将它编译成C#代码文件。
PS:网上可以找到的编译,需要下载相关的代码,见博文。其他的也较为繁琐,所以按照自己理解的来写了。注意,我的项目是放在D盘根目录下的。
首先打开cmd窗口,然后在窗口中输入:D:\GrpcTest\packages\Grpc.Tools.2.32.0\tools\windows_x86\protoc.exe -ID:\GrpcTest\GrpcService --csharp_out D:\GrpcTest\GrpcService D:\GrpcTest\GrpcService\FileTransfer.proto --grpc_out D:\GrpcTest\GrpcService --plugin=protoc-gen-grpc=D:\GrpcTest\packages\Grpc.Tools.2.32.0\tools\windows_x86\grpc_csharp_plugin.exe
输入上文后,按enter键,回车编译。
命令解读:
- D:\GrpcTest\packages\Grpc.Tools.2.32.0\tools\windows_x86\protoc.exe :调用的编译程序路径,注意版本不同路径稍有不一样。
- -ID:\GrpcTest\GrpcService :-I 指定一个或者多个目录,用来搜索.proto文件的。所以上面那行的D:\GrpcTest\GrpcService\FileTransfer.proto 已经可以换成FileTransfer.proto了,因为-I已经指定了。注意:如果不指定,那就是当前目录。
- --csharp_out D:\GrpcTest\GrpcService D:\GrpcTest\GrpcService\FileTransfer.proto :(--csharp_out)生成C#代码、存放路径、文件。当然还能cpp_out、java_out、javanano_out、js_out、objc_out、php_out、python_out、ruby_out 这时候你就应该知道,可以支持多语言的,才用的,生成一些文件,然后给各个语言平台调用。参数1(D:\GrpcTest\GrpcService)是输出路径,参数2(D:\GrpcTest\GrpcService\FileTransfer.proto)是proto的文件名或者路径。
- --grpc_out D:\GrpcTest\GrpcService :grpc_out是跟服务相关,创建,调用,绑定,实现相关。生成的玩意叫xxxGrpc.cs。与前面的区别是csharp_out是输出类似于咱们平时写的实体类,接口,定义之类的。生成的文件叫xxx.cs
- --plugin=protoc-gen-grpc=D:\GrpcTest\packages\Grpc.Tools.2.32.0\tools\windows_x86\grpc_csharp_plugin.exe :这个就是csharp的插件,python有python的,java有java的。
编译后,会在新增两个文件(文件位置与你的输出位置有关),并将两个文件加入到GrpcService项目中去:
4、编写服务端的文件传输服务
在GrpcServer项目中,新建一个FileImpl并继承自GrpcService.FileTransfer.FileTransferBase,然后复写其方法FileDownload和FileUpload方法,以供客户端进行调用。
/// <summary> /// 文件传输类 /// </summary> class FileImpl:GrpcService.FileTransfer.FileTransferBase { /// <summary> /// 文件下载 /// </summary> /// <param name="request">下载请求</param> /// <param name="responseStream">文件写入流</param> /// <param name="context">站点上下文</param> /// <returns></returns> public override async Task FileDownload(FileRequest request, global::Grpc.Core.IServerStreamWriter<FileReply> responseStream, global::Grpc.Core.ServerCallContext context) { List<string> lstSuccFiles = new List<string>();//传输成功的文件 DateTime startTime = DateTime.Now;//传输文件的起始时间 int chunkSize = 1024 * 1024;//每次读取的数据 var buffer = new byte[chunkSize];//数据缓冲区 FileStream fs = null;//文件流 try { //reply.Block数字的含义是服务器和客户端约定的 for (int i = 0; i < request.FileNames.Count; i++) { string fileName = request.FileNames[i];//文件名 string filePath = Path.GetFullPath($".//Files\\{fileName}");//文件路径 FileReply reply = new FileReply { FileName = fileName, Mark = request.Mark };//应答数据 Console.WriteLine($"{request.Mark},下载文件:{filePath}");//写入日志,下载文件 if (File.Exists(filePath)) { fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, chunkSize, useAsync: true); //fs.Length 可以告诉客户端所传文件大小 int readTimes = 0;//读取次数 while (true) { int readSise = fs.Read(buffer, 0, buffer.Length);//读取数据 if (readSise > 0)//读取到了数据,有数据需要发送 { reply.Block = ++readTimes; reply.Content = Google.Protobuf.ByteString.CopyFrom(buffer, 0, readSise); await responseStream.WriteAsync(reply); } else//没有数据了,就告诉对方,读取完了 { reply.Block = 0; reply.Content = Google.Protobuf.ByteString.Empty; await responseStream.WriteAsync(reply); lstSuccFiles.Add(fileName); Console.WriteLine($"{request.Mark},完成发送文件:{filePath}");//日志,记录发送成功 break;//跳出去 } } fs?.Close(); } else { Console.WriteLine($"文件【{filePath}】不存在。");//写入日志,文件不存在 reply.Block = -1;//-1的标记为文件不存在 await responseStream.WriteAsync(reply);//告诉客户端,文件状态 } } //告诉客户端,文件传输完成 await responseStream.WriteAsync(new FileReply { FileName = string.Empty, Block = -2,//告诉客户端,文件已经传输完成 Content = Google.Protobuf.ByteString.Empty, Mark = request.Mark }); } catch(Exception ex) { Console.WriteLine($"{request.Mark},发生异常({ex.GetType()}):{ex.Message}"); } finally { fs?.Dispose(); } Console.WriteLine($"{request.Mark},文件传输完成。共计【{lstSuccFiles.Count / request.FileNames.Count}】,耗时:{DateTime.Now - startTime}"); } /// <summary> /// 上传文件 /// </summary> /// <param name="requestStream">请求流</param> /// <param name="responseStream">响应流</param> /// <param name="context">站点上下文</param> /// <returns></returns> public override async Task FileUpload(global::Grpc.Core.IAsyncStreamReader<FileReply> requestStream, global::Grpc.Core.IServerStreamWriter<FileReturn> responseStream, global::Grpc.Core.ServerCallContext context) { List<string> lstFilesName = new List<string>();//文件名 List<FileReply> lstContents = new List<FileReply>();//数据集合 FileStream fs = null; DateTime startTime = DateTime.Now;//开始时间 string mark = string.Empty; string savePath = string.Empty; try { //reply.Block数字的含义是服务器和客户端约定的 while (await requestStream.MoveNext())//读取数据 { var reply = requestStream.Current; mark = reply.Mark; if (reply.Block == -2)//传输完成 { Console.WriteLine($"{mark},完成上传文件。共计【{lstFilesName.Count}】个,耗时:{DateTime.Now-startTime}"); break; } else if (reply.Block == -1)//取消了传输 { Console.WriteLine($"文件【{reply.FileName}】取消传输!");//写入日志 lstContents.Clear(); fs?.Close();//释放文件流 if (!string.IsNullOrEmpty(savePath) && File.Exists(savePath))//如果传输不成功,删除该文件 { File.Delete(savePath); } savePath = string.Empty; break; } else if(reply.Block==0)//文件传输完成 { if (lstContents.Any())//如果还有数据,就写入文件 { lstContents.OrderBy(c => c.Block).ToList().ForEach(c => c.Content.WriteTo(fs)); lstContents.Clear(); } lstFilesName.Add(savePath);//传输成功的文件 fs?.Close();//释放文件流 savePath = string.Empty; //告知客户端,已经完成传输 await responseStream.WriteAsync(new FileReturn { FileName= reply.FileName, Mark=mark }); } else { if(string.IsNullOrEmpty(savePath))//有新文件来了 { savePath = Path.GetFullPath($".//Files\\{reply.FileName}");//文件路径 fs = new FileStream(savePath, FileMode.Create, FileAccess.ReadWrite); Console.WriteLine($"{mark},上传文件:{savePath},{DateTime.UtcNow.ToString("HH:mm:ss:ffff")}"); } lstContents.Add(reply);//加入链表 if (lstContents.Count() >= 20)//每个包1M,20M为一个集合,一起写入数据。 { lstContents.OrderBy(c => c.Block).ToList().ForEach(c => c.Content.WriteTo(fs)); lstContents.Clear(); } } } } catch(Exception ex) { Console.WriteLine($"{mark},发生异常({ex.GetType()}):{ex.Message}"); } finally { fs?.Dispose(); } } }
在main函数中添加服务:
class Program { static void Main(string[] args) { //提供服务 Server server = new Server() { Services = {GrpcService.FileTransfer.BindService(new FileImpl())}, Ports = {new ServerPort("127.0.0.1",50000,ServerCredentials.Insecure)} }; //服务开始 server.Start(); while(Console.ReadLine().Trim().ToLower()!="exit") { } //结束服务 server.ShutdownAsync(); } }
5、编写客户端的文件传输功能
首先定义一个文件传输结果类TransferResult<T>,用于存放文件的传输结果。
/// <summary> /// 传输结果 /// </summary> /// <typeparam name="T"></typeparam> class TransferResult<T> { /// <summary> /// 传输是否成功 /// </summary> public bool IsSuccessful { get; set; } /// <summary> /// 消息 /// </summary> public string Message { get; set; } /// <summary> /// 标记类型 /// </summary> public T Tag { get; set; } = default; }
然后在GrpcClinet项目中添加一个FileTransfer的类,并实现相关方法:
class FileTransfer { /// <summary> /// 获取通信客户端 /// </summary> /// <returns>通信频道、客户端</returns> static (Channel, GrpcService.FileTransfer.FileTransferClient) GetClient() { //侦听IP和端口要和服务器一致 Channel channel = new Channel("127.0.0.1", 50000, ChannelCredentials.Insecure); var client = new GrpcService.FileTransfer.FileTransferClient(channel); return (channel, client); } /// <summary> /// 下载文件 /// </summary> /// <param name="fileNames">需要下载的文件集合</param> /// <param name="mark">标记</param> /// <param name="saveDirectoryPath">保存路径</param> /// <param name="cancellationToken">异步取消命令</param> /// <returns>下载任务(是否成功、原因、失败文件名)</returns> public static async Task<TransferResult<List<string>>> FileDownload(List<string> fileNames, string mark, string saveDirectoryPath, System.Threading.CancellationToken cancellationToken = new System.Threading.CancellationToken()) { var result = new TransferResult<List<string>>() { Message = $"文件保存路径不正确:{saveDirectoryPath}" }; if (!System.IO.Directory.Exists(saveDirectoryPath)) { return await Task.Run(() => result);//文件路径不存在 } if (fileNames.Count == 0) { result.Message = "未包含任何文件"; return await Task.Run(() => result);//文件路径不存在 } result.Message = "未能连接到服务器"; FileRequest request = new FileRequest() { Mark = mark };//请求数据 request.FileNames.AddRange(fileNames);//将需要下载的文件名赋值 var lstSuccFiles = new List<string>();//传输成功的文件 string savePath = string.Empty;//保存路径 System.IO.FileStream fs = null; Channel channel = null;//申明通信频道 GrpcService.FileTransfer.FileTransferClient client = null; DateTime startTime = DateTime.Now; try { (channel, client) = GetClient(); using (var call = client.FileDownload(request)) { List<FileReply> lstContents = new List<FileReply>();//存放接收的数据 var reaponseStream = call.ResponseStream; //reaponseStream.Current.Block数字的含义是服务器和客户端约定的 while (await reaponseStream.MoveNext(cancellationToken))//开始接收数据 { if (cancellationToken.IsCancellationRequested) { break; } if (reaponseStream.Current.Block == -2)//说明文件已经传输完成了 { result.Message = $"完成下载任务【{lstSuccFiles.Count}/{fileNames.Count}】,耗时:{DateTime.Now - startTime}"; result.IsSuccessful = true; break; } else if (reaponseStream.Current.Block == -1)//当前文件传输错误 { Console.WriteLine($"文件【{reaponseStream.Current.FileName}】传输失败!");//写入日志 lstContents.Clear(); fs?.Close();//释放文件流 if (!string.IsNullOrEmpty(savePath) && File.Exists(savePath))//如果传输不成功,删除该文件 { File.Delete(savePath); } savePath = string.Empty; } else if (reaponseStream.Current.Block == 0)//当前文件传输完成 { if (lstContents.Any())//如果还有数据,就写入文件 { lstContents.OrderBy(c => c.Block).ToList().ForEach(c => c.Content.WriteTo(fs)); lstContents.Clear(); } lstSuccFiles.Add(reaponseStream.Current.FileName);//传输成功的文件 fs?.Close();//释放文件流 savePath = string.Empty; } else//有文件数据过来 { if (string.IsNullOrEmpty(savePath))//如果字节流为空,则说明时新的文件数据来了 { savePath = Path.Combine(saveDirectoryPath, reaponseStream.Current.FileName); fs = new FileStream(savePath, FileMode.Create, FileAccess.ReadWrite); } lstContents.Add(reaponseStream.Current);//加入链表 if (lstContents.Count() >= 20)//每个包1M,20M为一个集合,一起写入数据。 { lstContents.OrderBy(c => c.Block).ToList().ForEach(c => c.Content.WriteTo(fs)); lstContents.Clear(); } } } } fs?.Close();//释放文件流 if (!result.IsSuccessful &&!string.IsNullOrEmpty(savePath)&& File.Exists(savePath))//如果传输不成功,那么久删除该文件 { File.Delete(savePath); } } catch (Exception ex) { if (cancellationToken.IsCancellationRequested) { fs?.Close();//释放文件流 result.IsSuccessful = false; result.Message = $"用户取消下载。已完成下载【{lstSuccFiles.Count}/{fileNames.Count}】,耗时:{DateTime.Now - startTime}"; } else { result.Message = $"文件传输发生异常:{ex.Message}"; } } finally { fs?.Dispose(); } result.Tag = fileNames.Except(lstSuccFiles).ToList();//获取失败文件集合 //关闭通信、并返回结果 return await channel?.ShutdownAsync().ContinueWith(t => result); } /// <summary> /// 文件上传 /// </summary> /// <param name="filesPath">文件路径</param> /// <param name="mark">标记</param> /// <param name="cancellationToken">异步取消命令</param> /// <returns>下载任务(是否成功、原因、成功的文件名)</returns> public static async Task<TransferResult<List<string>>> FileUpload(List<string> filesPath, string mark, System.Threading.CancellationToken cancellationToken=new System.Threading.CancellationToken()) { var result = new TransferResult<List<string>> { Message = "没有文件需要下载" }; if (filesPath.Count == 0) { return await Task.Run(() => result);//没有文件需要下载 } result.Message = "未能连接到服务器。"; var lstSuccFiles = new List<string>();//传输成功的文件 int chunkSize = 1024 * 1024; byte[] buffer = new byte[chunkSize];//每次发送的大小 FileStream fs = null;//文件流 Channel channel = null;//申明通信频道 GrpcService.FileTransfer.FileTransferClient client = null; DateTime startTime = DateTime.Now; try { (channel, client) = GetClient(); using(var stream=client.FileUpload())//连接上传文件的客户端 { //reply.Block数字的含义是服务器和客户端约定的 foreach (var filePath in filesPath)//遍历集合 { if(cancellationToken.IsCancellationRequested) break;//取消了传输 FileReply reply = new FileReply() { FileName=Path.GetFileName(filePath), Mark=mark }; if(!File.Exists(filePath))//文件不存在,继续下一轮的发送 { Console.WriteLine($"文件不存在:{filePath}");//写入日志 continue; } fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, chunkSize, useAsync: true); int readTimes = 0; while(true) { if (cancellationToken.IsCancellationRequested) { reply.Block = -1;//取消了传输 reply.Content = Google.Protobuf.ByteString.Empty; await stream.RequestStream.WriteAsync(reply);//发送取消传输的命令 break;//取消了传输 } int readSize = fs.Read(buffer, 0, buffer.Length);//读取数据 if(readSize>0) { reply.Block = ++readTimes;//更新标记,发送数据 reply.Content = Google.Protobuf.ByteString.CopyFrom(buffer, 0, readSize); await stream.RequestStream.WriteAsync(reply); } else { Console.WriteLine($"完成文件【{filePath}】的上传。"); reply.Block = 0;//传送本次文件发送结束的标记 reply.Content = Google.Protobuf.ByteString.Empty; await stream.RequestStream.WriteAsync(reply);//发送结束标记 //等待服务器回传 await stream.ResponseStream.MoveNext(cancellationToken); if(stream.ResponseStream.Current!=null&&stream.ResponseStream.Current.Mark==mark) { lstSuccFiles.Add(filePath);//记录成功的文件 } break;//发送下一个文件 } } fs?.Close(); } if (!cancellationToken.IsCancellationRequested) { result.IsSuccessful = true; result.Message = $"完成文件上传。共计【{lstSuccFiles.Count}/{filesPath.Count}】,耗时:{DateTime.Now - startTime}"; await stream.RequestStream.WriteAsync(new FileReply { Block = -2,//传输结束 Mark = mark }) ;//发送结束标记 } } } catch(Exception ex) { if (cancellationToken.IsCancellationRequested) { fs?.Close();//释放文件流 result.IsSuccessful = false; result.Message = $"用户取消了上传文件。已完成【{lstSuccFiles.Count}/{filesPath.Count}】,耗时:{DateTime.Now - startTime}"; } else { result.Message = $"文件上传发生异常({ex.GetType()}):{ex.Message}"; } } finally { fs?.Dispose(); } Console.WriteLine(result.Message); result.Tag = lstSuccFiles; //关闭通信、并返回结果 return await channel?.ShutdownAsync().ContinueWith(t => result); } }
现在可以在客户端窗体内进行调用了:
private string GetFilePath() { // Create OpenFileDialog Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog(); // Set filter for file extension and default file extension dlg.Title = "选择文件"; dlg.Filter = "所有文件(*.*)|*.*"; dlg.FileName = "选择文件夹."; dlg.FilterIndex = 1; dlg.ValidateNames = false; dlg.CheckFileExists = false; dlg.CheckPathExists = true; dlg.Multiselect = false;//允许同时选择多个文件 // Display OpenFileDialog by calling ShowDialog method Nullable<bool> result = dlg.ShowDialog(); // Get the selected file name and display in a TextBox if (result == true) { // Open document return dlg.FileName; } return string.Empty; } // 打开文件 private void btnOpenUpload_Click(object sender, RoutedEventArgs e) { lblUploadPath.Content = GetFilePath(); } CancellationTokenSource uploadTokenSource; //上传 private async void btnUpload_Click(object sender, RoutedEventArgs e) { lblMessage.Content = string.Empty; uploadTokenSource = new CancellationTokenSource(); List<string> fileNames = new List<string>(); fileNames.Add(lblUploadPath.Content.ToString()); var result = await ServerNet.FileTransfer.FileUpload(fileNames, "123", uploadTokenSource.Token); lblMessage.Content = result.Message; uploadTokenSource = null; } //取消上传 private void btnCancelUpload_Click(object sender, RoutedEventArgs e) { uploadTokenSource?.Cancel(); } //打开需要下载的文件 private void btnOpenDownload_Click(object sender, RoutedEventArgs e) { txtDownloadPath.Text = GetFilePath(); } //下载文件 private async void btnDownload_Click(object sender, RoutedEventArgs e) { lblMessage.Content = string.Empty; downloadTokenSource = new CancellationTokenSource(); List<string> fileNames = new List<string>(); fileNames.Add(System.IO.Path.GetFileName(txtDownloadPath.Text)); var result= await ServerNet.FileTransfer.FileDownload(fileNames, "123", Environment.CurrentDirectory, downloadTokenSource.Token); lblMessage.Content = result.Message; downloadTokenSource = null; } CancellationTokenSource downloadTokenSource; //下载取消 private void btnCancelDownload_Click(object sender, RoutedEventArgs e) { downloadTokenSource?.Cancel(); }
6、源代码
https://files.cnblogs.com/files/pilgrim/GrpcTest.rar
总结
到此这篇关于C#语言使用gRPC、protobuf(Google Protocol Buffers)实现文件传输功能的文章就介绍到这了,更多相关c#文件传输内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!
- golang在GRPC中设置client的超时时间
- golang 微服务之gRPC与Protobuf的使用
- golang grpc 负载均衡的方法
- 详解golang consul-grpc 服务注册与发现
- go grpc安装使用教程
- go实现grpc四种数据流模式
Centos6.4 下安装 protobuf 及简单使用
1、protobuf 是 google 公司提出的数据存储格式,详细介绍可以参考:https://code.google.com/p/protobuf/
2、下载最新的 protobuf,下载地址:https://code.google.com/p/protobuf/downloads/list
3、下载 protobuf2.5.o 版本,protobuf-2.5.0.tar.gz 解压并进行安装。
解压:tar xvf protobuf-2.5.0.tar.gz
安装步骤:(1)./configure (2)make (3)make check (4)make install
注意:安装成功后,将它的 bin 和 lib 目录分别加入到 PATH 和 LD_LIBRARY_PATH 环境变量,以方便直接调用。
通常建议安装到 /usr/local 目录下,执行 configure 时,指定 --prefix=/usr/local/protobuf 即可
设置环境变量过程:编辑 /etc/profile,在文件末尾添加:
export PATH=$PATH:/usr/local/protobuf/bin export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/protobuf/lib
4、测试例子:
创建一个.proto 文件,文件为:x.proto

1 package x; 2 message person 3 { 4 required string name = 1; 5 required int32 id = 2; 6 optional string email = 3; 7 }

编译成目标语言: protoc -I=src_dir --cpp_out=dst_dir $src_dir/addressbook.proto
5、C++语言编译命令如下: g++ -Wall -g ./dst_dir/x.pb.cc x.cpp -o x -I. -I/usr/local/protobuf/include -L/usr/local/protobuf/lib -lprotobuf -pthread 参考: http://www.ibm.com/developerworks/cn/linux/l-cn-gpb/ http://www.hadoopor.com/thread-1837-1-1.html
cocos Creator protobuf接入
工具:
cocos creator
node.js
npm
browserify
操作:
安装node.js
利用 npm安装 browserify npm installbrowserify
打包 protobuf : brwaserify main.js -o bundle.js
关于Cocos Creator 2.0 使用 protobufjs6.8,及 web 的 html 页面使用 protobufjs6.8和cocos creator 嵌入网页的介绍已经告一段落,感谢您的耐心阅读,如果想了解更多关于C#使用Protocol Buffer(ProtoBuf)进行Unity中的Socket通信、C#语言使用gRPC、protobuf(Google Protocol Buffers)实现文件传输功能、Centos6.4 下安装 protobuf 及简单使用、cocos Creator protobuf接入的相关信息,请在本站寻找。
本文标签: