项目笔记---C#异步Socket示例
概要
在C#领域或者说.net通信领域中有着众多的解决方案,WCF,HttpRequest,WebAPI,Remoting,socket等技术。这些技术都有着自己擅长的领域,或者被合并或者仍然应用于某些场合。本文主要介绍Socket通讯,因其有着跨平台、跨语言、高性能等优势,适合某些情况的应用以及性能优越的解决方案。
本文是基于一个小项目中的应用,使用了异步方式的Socket通讯,性能上达到多客户端多点通讯,大文件(M-G级别)的文件传输,异步长连接上的性能优势,但此项目也有些不足:未进行大量的外网长时间传输测试,对不稳定的网络状况未做很好的处理,例如加入断点续传功能,对Socket传输错误(其中微软提供了大量的Socket通讯错误代码指示错误类型,请参考http://www.cnblogs.com/Aricc/archive/2010/01/29/1659134.html)未做一个很好的分类处理,只简单的统一为一个类型的错误。所以,此项目介绍只是想抛砖引玉介绍异步Socket通讯,如果有不足或改进之处,还请各位不吝指出。
同步和异步区别
这里的同步和异步指的是服务端Accept接受客户端连接请求的方式。在同步模式下,服务端要开启一个Thread线程循环监听可能来自客户端的服务,如果没有则阻塞,如果有连接则接受连接并存入Connection Pool连接池,这样一个连接(或者说一个连接线程,一般占用系统内存约为2M,具体原因请参考《CLR via C#》中线程章节),这样32位系统下单一应用程序最大内存不超过2G,也就是说,单一连接服务端所能接受客户端最大的请求数为1000(实测下为700+);而异步连接则应用非阻塞式异步连接机制(http://www.cnblogs.com/2018/archive/2011/05/10/2040333.html)BeginAccept异步接受客户端请求并执行相应请求逻辑,在执行完毕后系统能自动优化,并当再次应用时唤醒,从而达到可接受大量的客户端请求,但也限于“同时”执行的客户端数量,对于某些长连接客户端巨大,但并发性小的情景适用。
自定义协议
众所周知,在Socket通讯中传输的普通的字符串或者二进制数据流,不适用于一些复杂的情况,例如约定一个可扩展的协议,可变长协议等,所以本项目采用自定义协议类型来满足可扩展需求:
|
Header |
协议头 |
命令 |
流1长度 |
流2长度 |
|
2字节 |
4字节 |
4字节 |
4字节 |
|
|
Body |
流1 |
流2 |
||
|
N字节 |
N字节 |
|||
说明:
协议头为:FF 7E ,2字节固定值
命令为:命令编号,如1001
流1长度:Int32型指示Body中的流1的长度
流2长度:Int32型指示Body中的流2的长度
流1:字节流
流2:字节流
这样,基于上述协议可自定义流1、流2的长度分别存放不同数据,基于协议还可以对数据协议进行封装,提供公共的解析方式。
/// <summary>
/// 通讯二进制协议,此协议基于变长的流传输。
/// 注:扩展此方法成员时,请重写相关方法。
/// </summary>
/// <remarks>
/// Create By CYS
/// </remarks>
public class CommunicateProtocol : IDisposable
{
#region Public Properties
/// <summary>
/// Byte array length of flag
/// </summary>
public const int ByteLength_HeaderFlag = ;
/// <summary>
/// Byte array length of command
/// </summary>
public const int ByteLength_HeaderCmd = ;
/// <summary>
/// Byte array length of header stream1
/// </summary>
public const int ByteLength_HeaderStream1Len = ;
/// <summary>
/// Byte array length of header stream2
/// </summary>
public const int ByteLength_HeaderStream2Len = ;
/// <summary>
/// 协议头长度
/// </summary>
public static int FlagLen
{
get { return ByteLength_HeaderFlag; }
}
/// <summary>
/// 命令(Int32)
/// </summary>
public int Command
{
get
{
return BitConverter.ToInt32(header_Cmd, );
}
set
{
BitConverter.GetBytes(value).CopyTo(header_Cmd, );
}
}
/// <summary>
/// 流1长度
/// </summary>
/// <returns></returns>
public int Stream1Len
{
get
{
return BitConverter.ToInt32(header_Stream1Len, );
}
set
{
BitConverter.GetBytes(value).CopyTo(header_Stream1Len, );
}
}
/// <summary>
/// 流2长度
/// </summary>
/// <returns></returns>
public int Stream2Len
{
get
{
return BitConverter.ToInt32(header_Stream2Len, );
}
set
{
BitConverter.GetBytes(value).CopyTo(header_Stream2Len, );
}
}
#endregion Public Properties #region Private Properties
private static byte[] header_Flag = new byte[ByteLength_HeaderFlag];
private byte[] header_Cmd = new byte[ByteLength_HeaderCmd];
private byte[] header_Stream1Len = new byte[ByteLength_HeaderStream1Len];
private byte[] header_Stream2Len = new byte[ByteLength_HeaderStream1Len];
private byte[] body_Stream1 = new byte[];
private Stream body_Stream2;
#endregion Private Properties #region Constructor
/// <summary>
/// Static constructor
/// </summary>
static CommunicateProtocol()
{
header_Flag = new byte[ByteLength_HeaderFlag] { 0xFF, 0x7E };
} #endregion Constructor #region Public Method
/// <summary>
/// 判断是否是协议头标志
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
public static bool CheckFlag(byte[] bytes)
{
if (bytes.Length != header_Flag.Length)
return false;
if (bytes.Length != )
return false;
if (!bytes[].Equals(header_Flag[]) || !bytes[].Equals(header_Flag[]))
return false;
return true;
}
/// <summary>
/// SetStream1
/// </summary>
/// <param name="sm"></param>
public void SetStream1(byte[] sm)
{
body_Stream1 = sm;
}
/// <summary>
/// GetStream1
/// </summary>
/// <returns></returns>
public byte[] GetStream1()
{
return body_Stream1;
}
/// <summary>
/// SetStream2
/// </summary>
/// <param name="sm"></param>
public void SetStream2(Stream sm)
{
body_Stream2 = sm;
}
/// <summary>
/// body_Stream2
/// </summary>
/// <returns></returns>
public Stream GetStream2()
{
return body_Stream2;
}
/// <summary>
/// GetHeaderBytes
/// </summary>
/// <returns></returns>
public byte[] GetHeaderBytes()
{
int offset = ;
byte[] bytes = new byte[ByteLength_HeaderFlag + ByteLength_HeaderCmd + ByteLength_HeaderStream1Len + ByteLength_HeaderStream2Len]; Array.Copy(header_Flag, , bytes, , ByteLength_HeaderFlag); offset += ByteLength_HeaderFlag;
Array.Copy(header_Cmd, , bytes, offset, ByteLength_HeaderCmd); offset += ByteLength_HeaderCmd;
Array.Copy(header_Stream1Len, , bytes, offset, ByteLength_HeaderStream1Len); offset += ByteLength_HeaderStream1Len;
Array.Copy(header_Stream2Len, , bytes, offset, ByteLength_HeaderStream2Len); offset += ByteLength_HeaderStream2Len; return bytes;
}
/// <summary>
/// InitProtocolHeader
/// </summary>
/// <returns></returns>
public static CommunicateProtocol InitProtocolHeader(byte[] source)
{
if (source.Length < ByteLength_HeaderCmd + ByteLength_HeaderStream1Len + ByteLength_HeaderStream2Len)
{
throw new Exception("byte length is illegal");
} byte[] header_cmd = new byte[ByteLength_HeaderCmd];
byte[] header_stream1len = new byte[ByteLength_HeaderStream1Len];
byte[] header_stream2len = new byte[ByteLength_HeaderStream2Len];
Array.Copy(source, , header_cmd, , ByteLength_HeaderCmd);
Array.Copy(source, ByteLength_HeaderCmd, header_stream1len, , ByteLength_HeaderStream1Len);
Array.Copy(source, ByteLength_HeaderCmd + ByteLength_HeaderStream1Len, header_stream2len, , ByteLength_HeaderStream2Len); return new CommunicateProtocol
{
Command = BitConverter.ToInt32(header_cmd, ),
Stream1Len = BitConverter.ToInt32(header_stream1len, ),
Stream2Len = BitConverter.ToInt32(header_stream2len, ),
};
}
#endregion Public Method #region Private Method #endregion Private Method #region IDisposable 成员
/// <summary>
/// Dispose
/// </summary>
public void Dispose()
{
header_Cmd = null;
header_Stream1Len = null;
header_Stream2Len = null;
body_Stream1 = null;
body_Stream2 = null;
} #endregion
}
通讯机制
传统意义上的上传与下载请求就是,一端发起Request请求,另一端接受并答复请求内容,这样就完成了一次请求应答。然而,如果要实现更多的控制功能,就要在这“一去一回”上增加通信应答次数,类似于TCP的三次握手请求。
其中,当请求被拒绝时,应向请求端发送错误答复998,这样就可以在这个过程中建立一个完善的请求答复机制。

public abstract class ContractAdapter
{
#region Public Method
/// <summary>
///
/// </summary>
/// <param name="p"></param>
/// <param name="s"></param>
protected void ExecuteProtocolCommand(CommunicateProtocol p, SocketState s)
{
if (p == null) throw new ArgumentNullException("CommunicateProtocol is null");
switch (p.Command)
{
case : CommandWrapper(((ICommandFunc)new Command0()), p, s); break;
case : CommandWrapper(((ICommandFunc)new Command1()), p, s); break;
case : CommandWrapper(((ICommandFunc)new Command2()), p, s); break;
case : CommandWrapper(((ICommandFunc)new Command998()), p, s); break;
case : CommandWrapper(((ICommandFunc)new Command999()), p, s); break;
//
case : CommandWrapper(((ICommandFunc)new Command1001()), p, s); break;
case : CommandWrapper(((ICommandFunc)new Command1002()), p, s); break;
//
case : CommandWrapper(((ICommandFunc)new Command2001()), p, s); break;
case : CommandWrapper(((ICommandFunc)new Command2002()), p, s); break;
//
case : CommandWrapper(((ICommandFunc)new Command3001()), p, s); break; default: throw new Exception("Protocol type does not exist.");
}
}
/// <summary>
///
/// </summary>
/// <param name="func"></param>
/// <param name="p"></param>
/// <param name="s"></param>
protected abstract void CommandWrapper(ICommandFunc func, CommunicateProtocol p, SocketState s);
#endregion Public Method
}
以及在“命令”中封装,下一个命令。
/// <summary>
///
/// </summary>
public class Command1002 : ICommandFunc
{
#region ICommandFunc 成员
public CommandProfile profile { get; set; } /// <summary>
///
/// </summary>
/// <param name="protocol"></param>
/// <param name="state"></param>
/// <param name="sobj"></param>
/// <returns></returns>
public SocketState Execute(CommunicateProtocol protocol, SocketState state, IHSSocket sobj)
{
state.IsReceiveThreadAlive = false; // Check File
if (!FileHelper.IsFileExist(profile.UpLoadPath + profile.UpLoadFName))
{
var p = ProtocolMgmt.InitProtocolHeader(, System.Text.Encoding.UTF8.GetBytes("服务端文件不存在"), null);
ProtocolMgmt.SendProtocol(state, p, sobj);
state.OutPutMsg = string.Format("Command 1002 :服务端文件不存在");
return state;
}
if (!FileHelper.CanRead(profile.UpLoadPath + profile.UpLoadFName))
{
var p = ProtocolMgmt.InitProtocolHeader(, System.Text.Encoding.UTF8.GetBytes("文件已被打开或占用,请稍后重试"), null);
ProtocolMgmt.SendProtocol(state, p, sobj);
state.OutPutMsg = string.Format("Command 1002 :文件已被打开或占用");
return state;
} FileInfo fi = new FileInfo(profile.UpLoadPath + profile.UpLoadFName);
using (FileStream fs = new FileStream(profile.UpLoadPath + profile.UpLoadFName, FileMode.Open))
{
var p = ProtocolMgmt.InitProtocolHeader(, System.Text.Encoding.UTF8.GetBytes(fi.Name), fs);
ProtocolMgmt.SendProtocol(state, p, sobj);
state.OutPutMsg = string.Format("Command 1002 :发送文件 {0} 成功。", fi.FullName);
} return state;
} #endregion
}
代码结构
项目分为客户端和服务端,其中都依赖于BLL和Core核心类库,Core中封装的是协议头的解析方式、抽象/接口方法、Socket缓冲读取、枚举委托、异步调用基类等,BLL中主要封装的是协议命令,如Command1、Command2001等的具体实现方式。
Core:

BLL:

UI.Client:

UI.Server:

核心思想是将Command的业务需求整合进BLL层次中,而Socket基本的通讯方式等公用功能整合进Core,将UI层释放开,在UI层用Log4net等开源插件进行组合。
总结
基于文字限制,不能讲代码中的每个细节都将到,分析到,还请各位谅解,其中如有不妥之处还请不吝赐教。没有质疑,就没有进步;只有不断的思考才能更好的掌握知识。
最后将程序的源码奉上,希望对您有帮助。
GitHub Project: Here
之前做项目时,项目参考的很多引用没有记住,希望以后有时间补上。
项目中的IP地址,需要根据您的本机IP进行配置,请在WinformServer和WinformClient的Setting文件中更改,同时,还要更改其默认的上传和下载文件,这里没有写成基于OpenFile的方式只是为了演示异步Socket通讯。
引用
Socket通信错误码:http://www.cnblogs.com/Aricc/archive/2010/01/29/1659134.html
异步通讯:http://www.cnblogs.com/2018/archive/2011/05/10/2040333.html
项目笔记---C#异步Socket示例的更多相关文章
- C#异步Socket示例
C#异步Socket示例 概要 在C#领域或者说.net通信领域中有着众多的解决方案,WCF,HttpRequest,WebAPI,Remoting,socket等技术.这些技术都有着自己擅长的领域, ...
- Django商城项目笔记No.6用户部分-注册接口-短信验证码实现celery异步
Django商城项目笔记No.4用户部分-注册接口-短信验证码实现celery异步 接上一篇,如何解决前后端请求跨域问题? 首先想一下,为什么图片验证码请求的也是后端的api.meiduo.site: ...
- 异步Socket服务器与客户端
本文灵感来自Andre Azevedo 在CodeProject上面的一片文章,An Asynchronous Socket Server and Client,讲的是异步的Socket通信. S ...
- 转 网络编程学习笔记一:Socket编程
题外话 前几天和朋友聊天,朋友问我怎么最近不写博客了,一个是因为最近在忙着公司使用的一些控件的开发,浏览器兼容性搞死人:但主要是因为这段时间一直在看html5的东西,看到web socket时觉得很有 ...
- Python简易聊天工具-基于异步Socket通信
继续学习Python中,最近看书<Python基础教程>中的虚拟茶话会项目,觉得很有意思,自己敲了一遍,受益匪浅,同时记录一下. 主要用到异步socket服务客户端和服务器模块asynco ...
- AndroidAsync :异步Socket,http(client+server),websocket和socket.io的Android类库
AndroidAsync是一个用于Android应用的异步Socket,http(client+server),websocket和socket.io的类库.基于NIO,没有线程.它使用java.ni ...
- 可扩展多线程异步Socket服务器框架EMTASS 2.0 续
转载自Csdn:http://blog.csdn.net/hulihui/article/details/3158613 (原创文章,转载请注明来源:http://blog.csdn.net/huli ...
- C# 实现的多线程异步Socket数据包接收器框架
转载自Csdn : http://blog.csdn.net/jubao_liang/article/details/4005438 几天前在博问中看到一个C# Socket问题,就想到笔者2004年 ...
- C# 异步Socket
C# 异步Socket (BeginXXXX)服务器代码 前言: 1.最近维护公司的一个旧项目,是Socket通讯的,主要用于接收IPC(客户端)发送上来的抓拍图像,期间要保持通讯,监测数据包并进行处 ...
随机推荐
- Python语言100例
Python版本:python 3.2.2 电脑系统:win7旗舰 实例来源:python菜鸟教程100例 #!/usr/bin/python # -*- coding: UTF-8 -*- impo ...
- scons使用
1.概述 scons是一个Python写的自动化构建工具,和GNU make相比优点明显: A.移植性:python能运行的地方,就能运行scons B. 扩展性:理论上scons只是提供 ...
- [麦先生]LINUX常用命令总结
在系统的学习了如何搭建和利用LINUX进行开发后,我利用xMind这一个强大的bug级软件制作了LINUX常见操作命令汇总,但是由于博客园并不支持xMind格式文件的上传,我只能将其做成图片进行分解上 ...
- A Personal Selection of Books on E lectromagnetics and Computational E lectromagnetics---David B. Davidson
链接. General Books on Electromagnetics When our department recently reviewed our junior-level text, w ...
- 【温故而知新-Javascript】窗口效果 (全屏显示窗口、定时关闭窗口)
1.全屏显示窗口 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://ww ...
- 我的opencv之旅:ios人脸识别
学习opencv有一年多了,这本来是我的毕业设计的一部分,但是因为不能突出专业重点,所以换了个课题. opencv在vc.android.ios下都能用,其中vc和android下的教程和主题贴最多, ...
- 利用WinPcap模拟网络包伪造飞秋闪屏报文
起因 不知道从什么时候开始,同事开始在飞秋上发闪屏振动了,后来变本加厉,成了每日一闪.老闪回去也比较麻烦,作为程序猿呢,有没有什么偷懒的办法呢?(同事负责用户体验,不大懂编程).然后尝试了以下思路: ...
- DataGridView 行、列的隐藏和删除
) 行.列的隐藏 [VB.NET] ' DataGridView1的第一列隐藏 DataGridView1.Columns(0).Visible = False ' DataGridView1的第一行 ...
- ubuntu手贱改了sudoers权限之后的恢复
sudo 这个命令是ubuntu系统是用的最多的(和其他大众版linux系统比起来),而一个普通用户是否有sudo权限来临时切换到root用户来执行“一行”命令取决于 /etc/sudoers的配置, ...
- 你会在C#的类库中添加web service引用吗?
本文并不是什么高深的文章,只是VS2008应用中的一小部分,但小部分你不一定会,要不你试试: 本人对于分布式开发应用的并不多,这次正好有一个项目要应用web service,我的开发环境是vs2008 ...