简单的异步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)来处理的,关于完成端口实现原理, ...
随机推荐
- 在做了 BasePage 时: 只有在配置文件或 Page 指令中将 enableSessionState 设置为 true 时,才能使用会话状态。还请确保在应用程序配置的 / / 节中包括
摘自: http://lichengguizy.blog.163.com/blog/static/11771858620122342749552/ 只有在配置文件或 Page 指令中将 enableS ...
- svn取消文件夹关联的方法(svn取消关联)
新建个记事本,贴入以下代码,保存后重命名后缀为reg,然后在目标文件夹右键就出现了删除SVN的选项了. 复制代码 代码如下: Windows Registry Editor Version 5.00[ ...
- spfile 和用户环境变量 和export
RAC数据库环境变量设置 [oracle@rac1 ~]$ sqlplus / as sysdba SQL*Plus: Release 10.2.0.1.0 - Production on Fri ...
- 转: codereview 工具平台建设
一. Rietveld code review (一套工具系统与平台) 1. http://www.cnblogs.com/fang9159/archive/2012/07/20/2591690 ...
- Rails 枚举
Rails Model中使用枚举有两种方案,一种是rails内置的enum,一种使用enumerize这个gem,不管哪种都能达到相同的目的. 首先介绍第一种: 一. enum 基本使用方法,以一个案 ...
- 解决Android NDK 报jxxx编译找不到
如题 解决: 引入NDK对应的arm或者x86库 如果你用的是GinyMotion模拟器,那就引入x86库 不多说了,见截图 1.选择Propertities->C/C++ General-&g ...
- 疯狂java学习路线图
- MyBatis SpringBoot2.0 数据库读写分离
1.自定义DataSource import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * @ ...
- 使用js+Ajax请求API接口数据-带请求头方式
C# http请求带请求头部分 先上代码: <script type="text/javascript"> function zLoginCheck() { var A ...
- Mybatis 插入后返回数据库自动增长ID
MySQL和MSSQL返回主键方法 在personMap.xml中 <insert id="addPerson" parameterType="orm.Person ...