C#使用ProtocolBuffer(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, 0, 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, 0, 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, 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, 0, 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, 0, 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, 0, 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, 0, data, 0, 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, 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, 0, 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, 0, data, 0, 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, 0, 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, 0, 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(), 0, result, 0, (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(), 0, result, 0, (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, 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();
}
}
}
ProtoBuf网络传输到这里就全部完成了。
C#使用ProtocolBuffer(ProtoBuf)进行Unity中的Socket通信的更多相关文章
- java中有关socket通信的学习笔记
最近做的项目中使用到了一些基于java的socket长连接的一些功能,用来穿透有关行业的网闸.用到了也就学习了一下,下面是对学习内容的一个笔记,记录一下也希望有兴趣的同学可以参考一下,加深对javas ...
- Java中的socket通信
socket又称套接字,应用程序通常通过"套接字"向网络发出请求或应答网络请求 建立过程:服务器监听-客户端发出请求-建立链接-通信 特点:1.基于tcp链接,数据传输有保障,2. ...
- Java网络编程和NIO详解1:JAVA 中原生的 socket 通信机制
Java网络编程和NIO详解1:JAVA 中原生的 socket 通信机制 JAVA 中原生的 socket 通信机制 摘要:本文属于原创,欢迎转载,转载请保留出处:https://github.co ...
- Android中的常见通信机制和Linux中的通信机制
Handler Handler是Android系统中的一种消息传递机制,起作用是应对多线程场景.将A进程的消息传递给B线程,实现异步消息处理.很多情况是将工作线程中需要更新UI的操作消息传递给UI主线 ...
- (转)Unity中protobuf的使用方法
在移动手机游戏开发中,目前Unity3D已成为比较主流的开发技术. 那么对于客户端服务器协议的打解包,我们有3中常用的处理方式: 1.自定义结构体:在协议中直接传输代码中自定义的结构体:这种方式的坏处 ...
- unity 中 protobuff 的用法 一句话攻略
(一)unity 添加 pb 的 dll 支持.1,从GitHub上下载protobuf源码 (源码链接:https://github.com/google/protobuf),找到 csharp 分 ...
- Unity中使用Attribute
Attribute是c#的语言特性 msdn说明如下: The Attribute class associates predefined system information or user-def ...
- 在Unity中使用UGUI修改Mesh绘制几何图形
在商店看到这样一个例子,表示很有兴趣,他们说是用UGUI做的.我想,像这种可以随便变形的图形,我第一个就想到了网格变形. 做法1: 细心的朋友应该会发现,每个UGUI可见元素,都有一个‘Canvas ...
- Unity中使用WebView
Unity中使用WebView @(设计) 需求,最近游戏中需要引入H5直播页面和更新比较频繁的赛事页面,需求包括:加密传参数.和Unity交互,在Unity框架下其实有几种方案: 内置函数Appli ...
随机推荐
- 输出联系变化的数字seq
主要作用:输出联系变化的数字格式:Seq 分割符号 开始 间隔 结束开始默认是1,间隔默认是1,分隔符默认回车一位是结束,两位首末,三位首间隔末,没有四位,开始可以是负数主要参数:-f 指定格式打印- ...
- SURF 特征匹配
参考:http://www.cnblogs.com/ronny/p/4045979.html,博主对源码进行了分析,不过很多没看明白. 分为几个部分.积分图:借助积分图像,图像与高斯二阶微分模板的滤波 ...
- 为什么尽量别用 setInterval
为什么尽量别用setInterval 在开发一个在线聊天工具时,经常会有过多少毫秒就重复执行一次某操作的需求.“没问题”,大家都说,“用setInterval好了.”我觉得这个点子很糟糕. 原因之 ...
- 4.自定义数据《jquery实战》
4.4 元素中的存储自定义数据 data([key],[value]) 在元素上存放数据,返回jQuery对象. key (String) 存储的数据名. key,value (String,Any) ...
- 【Codechef】BB-Billboards
题解 传说中的--半标准杨表(行单调不增,列单调减) 如果N能整除M,我们把序列分成\(\frac{N}{M}\)段 然后里面要填K个1,显然我每一段必须填K个1,且可以构造出合法的序列,所以最少要填 ...
- win10无线网连接 提示无法连接到此网络
一.Win10无法连接此网络是怎么回事 对于大多数遇到无法连接此网络问题的,主要是Win10笔记本电脑用户,使用的是无线网络.而出现Win10连接其他无线网络正常,但是就是某个无线网络无法正常连接的时 ...
- lvs+keepalived 02
LVS keepalived 高可用负载均衡 环境 IP HOSTNAME Describe 192.168.100.30 lvs01 主负载 192.168.100.31 lvs02 备负载 192 ...
- [Java]进程与线程的区别(转)
线程是指进程内的一个执行单元,也是进程内的可调度实体. 与进程的区别: (1)地址空间:进程内的一个执行单元;进程至少有一个线程;它们共享进程的地址空间;而进程有自己独立的地址空间; (2)资源拥有: ...
- JavaScript基础之运算符及全面的运算符优先级总结
算数运算符: 加+,减—,乘*,除/,求余%,加加++,减减——, 加减乘除求余运算与数学上的用法完全一样. 不过,加号+还有连接字符串的作用,其他运算符还可以将字符串数字转换成数值型,参见JavaS ...
- svn简单记录
记录一下工作中常用到的svn命令 一.文件的提交流程 1.svn up // 先更新本地文件 2.svn st // svn status 查看要提交的文件 3.#svn ci -m &quo ...