简单的异步Socket实现——SimpleSocket_V1.1
简单的异步Socket实现——SimpleSocket_V1.1
笔者在前段时间的博客中分享了一段简单的异步.net的Socket实现。由于是笔者自己测试使用的。写的很粗糙。很简陋。于是花了点时间自己去完善了一下
旧版本的SimpleSocket大致实现了异步socket的全部功能。但是代码扩展性较差。对很多事件都没有做出相对应的处理。在1.1版本进行了相对应的维护和更新。
SimpleSocket(简称:SS)是一个简单的.net原生的Socket简单封装。实现了异步操作。SS利用长度的解码器来解决和避免粘包等网络问题。
新增特性:
1.增加对.net原生的小端存储支持. 通过define是否是大端编码及可以切换存储形式
2.独立的Protocol Buffers编解码器工具. 通过define机可以开启关闭是否需要Protobuf支持
3.对Socket的部分管理。例如关闭和断开连接
4.增加消息发送完成事件,连接建立完成事件,消息接收完成事件
下面上代码: SimpleSocket.cs 1.1版本
// +------------------------+
// | Author : TinyZ |
// | Data : 2014-08-20 |
// |Ma-il : zou90512@126.com|
// | Version : 1.1 |
// +------------------------+
// 注释: 笔者这里实现了一个基于长度的解码器。用于避免粘包等问题。编码时候的长度描述数字的默认为short类型(长度2字节)。解码时候的长度描述数字默认为int类型(长度4字节) // GOOGLE_PROTOCOL_BUFFERS :
// 是否支持Google的Protocol Buffers. 作者自己使用的.
// Define request Google protocol buffers
// Example: #define GOOGLE_PROTOCOL_BUFFERS
// 相关资料:
// [推荐]protobuf-csharp-port:https://code.google.com/p/protobuf-csharp-port/ . PB最好,最完整的C#实现.使用.net 20版本即可以完美支持Unity3D 4.3x以上版本
// protobuf-net: https://code.google.com/p/protobuf-net/
//#define GOOGLE_PROTOCOL_BUFFERS
//
// BIG_ENDIANESS :
// 是否是大端存储. 是=>使用大端存储,将使用Misc类库提供的大端存储工具EndianBitConverter. 否=>使用.Net提供的BitConverter
// Define is socket endianess is big-endianess(大端) . If endianess is Big-endianess use EndianBitConverter , else use BitConverter
// 相关资料:
// Miscellaneous Utility Library类库官网: http://www.yoda.arachsys.com/csharp/miscutil/
#define BIG_ENDIANESS using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
#if BIG_ENDIANESS
using MiscUtil.Conversion;
#endif
#if GOOGLE_PROTOCOL_BUFFERS
using Google.ProtocolBuffers;
using Assets.TinyZ.Socket.Codec;
#endif namespace Assets.TinyZ.Socket
{
/// <summary>
/// 简单的异步Socket实现. 用于Unity3D客户端与JAVA服务端的数据通信.
///
/// <br/><br/>方法:<br/>
/// Connect:用于连接远程指定端口地址,连接成功后开启消息接收监听<br/>
/// OnSendMessage:用于发送字节流消息. 长度不能超过short[65535]的长度<br/>
/// <br/>事件:<br/>
/// ReceiveMessageCompleted: 用于回调. 返回接收到的根据基于长度的解码器解码之后获取的数据[字节流]
/// SendMessageCompleted: 用于回调. 当消息发送完成时
/// ConnectCompleted: 用于回调. 当成功连接到远程网络地址后调用
///
/// <br/><br/>
/// 服务器为JAVA开发。因此编码均为 BigEndian编码
/// 消息的字节流格式如下:<br/>
/// * +------------+-------------+ <br/>
/// * |消息程度描述| 内容 | <br/>
/// * | 0x04 | ABCD | <br/>
/// * +------------+-------------+ <br/>
/// 注释: 消息头为消息内容长度描述,后面是相应长度的字节内容.
/// <br/><br/>
/// </summary>
/// <example>
/// <code>
/// // Unity3D客户端示例代码如下:
/// var _simpleSocket = new SimpleSocket();
/// _simpleSocket.Connect("127.0.0.1", 9003);
/// _simpleSocket.ReceiveMessageCompleted += (s, e) =>
/// {
/// var rmc = e as SocketEventArgs;
/// if (rmc == null) return;
/// var data = rmc.Data as byte[];
/// if (data != null)
/// {
/// // 在Unity3D控制台输出接收到的UTF-8格式字符串
/// Debug.Log(Encoding.UTF8.GetString(data));
/// }
// _count++;
/// };
///
/// // Unity3D客户端发送消息:
/// _simpleSocket.OnSendMessage(Encoding.UTF8.GetBytes("Hello World!"));
/// </code>
/// </example>
public class SimpleSocket
{
#region Construct /// <summary>
/// Socket
/// </summary>
private readonly System.Net.Sockets.Socket _socket; /// <summary>
/// SimpleSocket的构造函数
/// </summary>
public SimpleSocket()
{
_socket = new System.Net.Sockets.Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
//_socket.Blocking = false; // ?
} /// <summary>
/// 初始化Socket, 并设置帧长度
/// </summary>
/// <param name="encoderLengthFieldLength">编码是消息长度数字的字节数长度. 1:表示1byte 2:表示2byte[Short类型] 4:表示4byte[int类型] 8:表示8byte[long类型]</param>
/// <param name="decoderLengthFieldLength">解码时消息长度数字的字节数长度. 1:表示1byte 2:表示2byte[Short类型] 4:表示4byte[int类型] 8:表示8byte[long类型]</param>
public SimpleSocket(int encoderLengthFieldLength, int decoderLengthFieldLength) : this()
{
_encoderLengthFieldLength = encoderLengthFieldLength;
_decoderLengthFieldLength = decoderLengthFieldLength;
} #endregion #region Connect to remote host /// <summary>
/// 连接远程地址完成事件
/// </summary>
public event EventHandler<SocketEventArgs> ConnectCompleted; /// <summary>
/// 是否连接状态
/// </summary>
/// <see cref="Socket.Connected"/>
public bool Connected
{
get { return _socket != null && _socket.Connected; }
} /// <summary>
/// 连接指定的远程地址
/// </summary>
/// <param name="host">远程地址</param>
/// <param name="port">端口</param>
public void Connect(string host, int port)
{
_socket.BeginConnect(host, port, OnConnectCallBack, this);
} /// <summary>
/// 连接指定的远程地址
/// </summary>
/// <param name="ipAddress">目标网络协议ip地址</param>
/// <param name="port">目标端口</param>
/// 查看:<see cref="IPAddress"/>
public void Connect(IPAddress ipAddress, int port)
{
_socket.BeginConnect(ipAddress, port, OnConnectCallBack, this);
} /// <summary>
/// 连接端点
/// </summary>
/// <param name="endPoint">端点, 标识网络地址</param>
/// 查看:<see cref="EndPoint"/>
public void Connect(EndPoint endPoint)
{
_socket.BeginConnect(endPoint, OnConnectCallBack, this);
} /// <summary>
/// 连接的回调函数
/// </summary>
/// <param name="ar"></param>
private void OnConnectCallBack(IAsyncResult ar)
{
if (!_socket.Connected) return;
_socket.EndConnect(ar);
if (ConnectCompleted != null)
{
ConnectCompleted(this, new SocketEventArgs());
}
StartReceive();
} #endregion #region Send Message /// <summary>
/// 发送消息完成
/// </summary>
public event EventHandler<SocketEventArgs> SendMessageCompleted; /// <summary>
/// 编码时长度描述数字的字节长度[default = 2 => 65535字节]
/// </summary>
private readonly int _encoderLengthFieldLength = ; /// <summary>
/// 发送消息
/// </summary>
/// <param name="data">要传递的消息内容[字节数组]</param>
public void OnSendMessage(byte[] data)
{
var stream = new MemoryStream();
switch (_encoderLengthFieldLength)
{
case :
stream.Write(new[] {(byte) data.Length}, , );
break;
#if BIG_ENDIANESS
case :
stream.Write(EndianBitConverter.Big.GetBytes((short) data.Length), , );
break;
case :
stream.Write(EndianBitConverter.Big.GetBytes(data.Length), , );
break;
case :
stream.Write(EndianBitConverter.Big.GetBytes((long) data.Length), , );
break;
#else
case :
stream.Write(BitConverter.GetBytes((short) data.Length), , );
break;
case :
stream.Write(BitConverter.GetBytes(data.Length), , );
break;
case :
stream.Write(BitConverter.GetBytes((long) data.Length), , );
break;
#endif default:
throw new Exception("unsupported decoderLengthFieldLength: " + _encoderLengthFieldLength +
" (expected: 1, 2, 3, 4, or 8)");
}
stream.Write(data, , data.Length);
var all = stream.ToArray();
stream.Close();
_socket.BeginSend(all, , all.Length, SocketFlags.None, OnSendMessageComplete, all);
} /// <summary>
/// 发送消息完成的回调函数
/// </summary>
/// <param name="ar"></param>
private void OnSendMessageComplete(IAsyncResult ar)
{
var data = ar.AsyncState as byte[];
SocketError socketError;
_socket.EndSend(ar, out socketError);
if (socketError != SocketError.Success)
{
_socket.Disconnect(false);
throw new SocketException((int)socketError);
}
if (SendMessageCompleted != null)
{
SendMessageCompleted(this, new SocketEventArgs(data));
}
//Debug.Log("Send message successful !");
} #endregion #region Receive Message /// <summary>
/// the length of the length field. 长度字段的字节长度, 用于长度解码
/// </summary>
private readonly int _decoderLengthFieldLength = ; /// <summary>
/// 事件消息接收完成
/// </summary>
public event EventHandler<SocketEventArgs> ReceiveMessageCompleted; /// <summary>
/// 开始接收消息
/// </summary>
private void StartReceive()
{
if (!_socket.Connected) return;
var buffer = new byte[_decoderLengthFieldLength];
_socket.BeginReceive(buffer, , _decoderLengthFieldLength, SocketFlags.None, OnReceiveFrameLengthComplete, buffer);
} /// <summary>
/// 实现帧长度解码.避免粘包等问题
/// </summary>
private void OnReceiveFrameLengthComplete(IAsyncResult ar)
{
var frameLength = (byte[]) ar.AsyncState;
// 帧长度
#if BIG_ENDIANESS
var length = EndianBitConverter.Big.ToInt32(frameLength, );
#else
var length = BitConverter.ToInt32(frameLength, );
#endif
var data = new byte[length];
_socket.BeginReceive(data, , length, SocketFlags.None, OnReceiveDataComplete, data);
} /// <summary>
/// 数据接收完成的回调函数
/// </summary>
private void OnReceiveDataComplete(IAsyncResult ar)
{
_socket.EndReceive(ar);
var data = ar.AsyncState as byte[];
// 触发接收消息事件
if (ReceiveMessageCompleted != null)
{
ReceiveMessageCompleted(this, new SocketEventArgs(data));
}
StartReceive();
} #endregion #region Close and Disconnect /// <summary>
/// 关闭 <see cref="Socket"/> 连接并释放所有关联的资源
/// </summary>
public void Close()
{
_socket.Close();
} /// <summary>
/// 关闭套接字连接并允许重用套接字。
/// </summary>
/// <param name="reuseSocket">如果关闭当前连接后可以重用此套接字,则为 true;否则为 false。 </param>
public void Disconnect(bool reuseSocket)
{
_socket.Disconnect(reuseSocket);
} #endregion #region Protocol Buffers Utility [Request : Google Protocol Buffers version 2.5] #if GOOGLE_PROTOCOL_BUFFERS /// <summary>
/// 发送消息
/// </summary>
/// <typeparam name="T">IMessageLite的子类</typeparam>
/// <param name="generatedExtensionLite">消息的扩展信息</param>
/// <param name="messageLite">消息</param>
public void OnSendMessage<T>(GeneratedExtensionLite<ServerMessage, T> generatedExtensionLite, T messageLite)
where T : IMessageLite
{
var data = ProtobufEncoder.ConvertMessageToByteArray(generatedExtensionLite, messageLite);
OnSendMessage(data);
} #endif #endregion
} #region Event /// <summary>
/// Simple socket event args
/// </summary>
public class SocketEventArgs : EventArgs
{ public SocketEventArgs()
{
} public SocketEventArgs(byte[] data) : this()
{
Data = data;
} /// <summary>
/// 相关的数据
/// </summary>
public byte[] Data { get; private set; } } #endregion }
下面是笔者自己使用的Protocol Buffers的编解码器。类库需求:protobuf-csharp-port 523版本。有兴趣的可以自己试试。
解码器是通用的解码器.是仿照Netty的ProtobufDecoder解码器C#实现。
// +------------------------+
// | Author : TinyZ |
// | Data : 2014-08-20 |
// |Ma-il : zou90512@126.com|
// +------------------------+
// 引用资料:
// [推荐]protobuf-csharp-port:https://code.google.com/p/protobuf-csharp-port/ . PB最好,最完整的C#实现.使用.net 20版本即可以完美支持Unity3D 4.3x以上版本
// protobuf-net: https://code.google.com/p/protobuf-net/ using System;
using Google.ProtocolBuffers; namespace Assets.TinyZ.Socket.Codec
{
/// <summary>
/// Protocol Buffers 解码器
/// </summary>
public class ProtobufDecoder
{
private readonly IMessageLite _prototype; /// <summary>
/// 扩展消息注册
/// </summary>
private readonly ExtensionRegistry _extensionRegistry; public ProtobufDecoder(IMessageLite prototype)
{
_prototype = prototype.WeakDefaultInstanceForType;
} public ProtobufDecoder(IMessageLite prototype, ExtensionRegistry extensionRegistry)
: this(prototype)
{
_extensionRegistry = extensionRegistry;
} /// <summary>
/// 注册扩展
/// </summary>
/// <param name="extension">protobuf扩展消息</param>
public void RegisterExtension(IGeneratedExtensionLite extension)
{
if (_extensionRegistry == null)
{
throw new Exception("ExtensionRegistry must using InitProtobufDecoder method to initialize. ");
}
_extensionRegistry.Add(extension);
} /// <summary>
/// 解码
/// </summary>
/// <param name="data">protobuf编码字节数组</param>
/// <returns>返回解码之后的消息</returns>
public IMessageLite Decode(byte[] data)
{
if (_prototype == null)
{
throw new Exception("_prototype must using InitProtobufDecoder method to initialize.");
}
IMessageLite message;
if (_extensionRegistry == null)
{
message = (_prototype.WeakCreateBuilderForType().WeakMergeFrom(ByteString.CopyFrom(data))).WeakBuild();
}
else
{
message =
(_prototype.WeakCreateBuilderForType().WeakMergeFrom(ByteString.CopyFrom(data), _extensionRegistry))
.WeakBuild();
}
if (message == null)
{
throw new Exception("Decode message failed");
}
return message;
}
}
}
编码器。方法ConvertMessageToByteArray是笔者自己写来测试使用的。大家可以无视之
// +------------------------+
// | Author : TinyZ |
// | Data : 2014-08-20 |
// |Ma-il : zou90512@126.com|
// +------------------------+
// 引用资料:
// [推荐]protobuf-csharp-port:https://code.google.com/p/protobuf-csharp-port/ . PB最好,最完整的C#实现.使用.net 20版本即可以完美支持Unity3D 4.3x以上版本
// protobuf-net: https://code.google.com/p/protobuf-net/ using Google.ProtocolBuffers; namespace Assets.TinyZ.Socket.Codec
{
/// <summary>
/// Protocol Buffers 编码器
/// </summary>
public class ProtobufEncoder
{
/// <summary>
/// [自用]Message转换为byte[]
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="generatedExtensionLite"></param>
/// <param name="messageLite"></param>
/// <returns></returns>
public static byte[] ConvertMessageToByteArray<T>(GeneratedExtensionLite<ServerMessage, T> generatedExtensionLite, T messageLite) where T : IMessageLite
{
ServerMessage.Builder builder = ServerMessage.CreateBuilder();
builder.SetMsgId("" + generatedExtensionLite.Number);
builder.SetExtension(generatedExtensionLite, messageLite);
ServerMessage serverMessage = builder.Build();
return serverMessage.ToByteArray();
} public static byte[] Encode(IMessageLite messageLite)
{
return messageLite.ToByteArray();
} public static byte[] Encode(IBuilder builder)
{
return builder.WeakBuild().ToByteArray();
}
}
}
源码下载地址:http://pan.baidu.com/s/1pJz7Tv9
ps:因为笔者最近使用Unity3D。所以示例源码是Unity3D的。假如你没有安装过Unity3D。也没关系。笔者同时提供了zip压缩包。包含cs源文件
作者:TinyZ
出处:http://www.cnblogs.com/zou90512/
关于作者:努力学习,天天向上。不断探索学习,提升自身价值。记录经验分享。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接
如有问题,可以通过 zou90512@126.com 联系我,非常感谢。
笔者网店: http://aoleitaisen.taobao.com. 欢迎围观。O(∩_∩)O哈哈~
简单的异步Socket实现——SimpleSocket_V1.1的更多相关文章
- 使用 boost.asio 简单实现 异步Socket 通信
客户端: class IPCClient { public: IPCClient(); ~IPCClient(); bool run(); private: bool connect(); bool ...
- Unity3D中简单的C#异步Socket实现
Unity3D中简单的C#异步Socket实现 简单的异步Socket实现..net框架自身提供了很完善的Socket底层.笔者在做Unity3D小东西的时候需要使用到Socket网络通信.于是决定自 ...
- 异步Socket服务器与客户端
本文灵感来自Andre Azevedo 在CodeProject上面的一片文章,An Asynchronous Socket Server and Client,讲的是异步的Socket通信. S ...
- GJM :异步Socket [转载]
原帖地址:http://blog.csdn.net/awinye/article/details/537264 原文作者:Awinye 目录(?)[-] 转载请原作者联系 Overview of So ...
- Python简易聊天工具-基于异步Socket通信
继续学习Python中,最近看书<Python基础教程>中的虚拟茶话会项目,觉得很有意思,自己敲了一遍,受益匪浅,同时记录一下. 主要用到异步socket服务客户端和服务器模块asynco ...
- 项目笔记---C#异步Socket示例
概要 在C#领域或者说.net通信领域中有着众多的解决方案,WCF,HttpRequest,WebAPI,Remoting,socket等技术.这些技术都有着自己擅长的领域,或者被合并或者仍然应用于某 ...
- Socket&GCDAsyncSocket(异步Socket)
Socket ********************************************* 简单理解Socket 就是网络连接,可以实现两个点之间的数据通讯. •Socket允许使用长连 ...
- C#异步Socket示例
C#异步Socket示例 概要 在C#领域或者说.net通信领域中有着众多的解决方案,WCF,HttpRequest,WebAPI,Remoting,socket等技术.这些技术都有着自己擅长的领域, ...
- 一个高性能异步socket封装库的实现思路 (c#)
前言 socket是软件之间通讯最常用的一种方式.c#实现socket通讯有很多中方法,其中效率最高就是异步通讯. 异步通讯实际是利用windows完成端口(IOCP)来处理的,关于完成端口实现原理, ...
随机推荐
- PowerShell中的一个switch的例子
在这个例子中, 应该注意 Switch语句里对数字范围条件的使用 break的使用 字符串的拼接 数组的声明 ) foreach ($element in $array) { switch($el ...
- UML分析设计顺序
1.用例图:最简单的模型,与设计无关 2.活动图:类似流程图 3.用例图:对1的细化,分解后的Actor及Use Case 4.用例图:分解后的Actor及抽取的数据实体 5.类图:数据结构图 6.顺 ...
- Maven C盘用户文件下没有.m2
在配置好Maven的环境之后,先运行一条命令: mvn help:system 该命令会打印出所有的Java系统属性和环境变量. 运行这条命令的目的是让Maven执行一个真正的任务.可以从命令行上看到 ...
- Ubuntu切换root用户权限
其实方法很简单,就是需要选对自己使用的linux系统,不同分支的系统切换root的方法不一定一样. Ubuntu切换root的方法很简单,首先一档钱管理员命令执行: sudo passwd root ...
- 云计算之路-试用Azure:一次失败的SQL Server向SQL Azure的迁移尝试
如果数据库用的是SQL Server,那SQL Azure无疑是最吸引人的地方之一.在测试了虚拟机磁盘IO之后,我们迫不急待地进行了SQL Azure的测试. (一) 首先进入manage.windo ...
- Django 学习记录
这是我自己理解并自己画的,django 请求示意图,表示了它的组织方式. project manage.py: 主要工具文件 settings.py: 配置文件 urls.py: url 定义及其指向 ...
- 代码可读性艺术在Andorid中的体现
前言 最近接手的一些项目,不同的人编码风格迥异,类里的变量.方法的定义穿插,注释极为稀少,更有一些变量和方法的命名非常近似,例如表示播放队列的"playQueue"和表示歌单的&q ...
- 【Statistics】CAP曲线
功能描述 CAP曲线(Cumulative Accuracy Profile)/Power Curve(准确率/AR)是描述整个评级结果下,累计违约客户比例与累计客户比例的关系. 在完美的模型下,CA ...
- 学会Git玩转Github笔记(二)——Git使用
一.Git基本工作流程 Git工作区域 向仓库中添加文件流程 二. Git初始化及仓库创建和操作 基本信息设置 . 设置用户名 git config --global user.name 'itcas ...
- XMind--用他来理清自己的思路
背景 一图胜千言,多年以前阅读了<图谋职场>后,深刻体会了这一点.工作学习,有效利用各种图,事半功倍. 简介 XMIND不仅可以绘制思维导图,还能绘制鱼骨图.二维图.树形图.逻辑图.组织结 ...