項目筆記---C#異步Socket示例

概要

在C#領域或者說.net通訊領域中有着衆多的解決方案,WCF,HttpRequest,WebAPI,Remoting,socket等技術。這些技術都有着本身擅長的領域,或者被合併或者仍然應用於某些場合。本文主要介紹Socket通信,因其有着跨平臺、跨語言、高性能等優點,適合某些狀況的應用以及性能優越的解決方案。html

本文是基於一個小項目中的應用,使用了異步方式的Socket通信,性能上達到多客戶端多點通信,大文件(M-G級別)的文件傳輸,異步長鏈接上的性能優點,但此項目也有些不足:未進行大量的外網長時間傳輸測試,對不穩定的網絡情況未作很好的處理,例如加入斷點續傳功能,對Socket傳輸錯誤(其中微軟提供了大量的Socket通信錯誤代碼指示錯誤類型,請參考http://www.cnblogs.com/Aricc/archive/2010/01/29/1659134.html)未作一個很好的分類處理,只簡單的統一爲一個類型的錯誤。因此,此項目介紹只是想拋磚引玉介紹異步Socket通信,若是有不足或改進之處,還請各位不吝指出。git

同步和異步區別

這裏的同步和異步指的是服務端Accept接受客戶端鏈接請求的方式。在同步模式下,服務端要開啓一個Thread線程循環監聽可能來自客戶端的服務,若是沒有則阻塞,若是有鏈接則接受鏈接並存入Connection Pool鏈接池,這樣一個鏈接(或者說一個鏈接線程,通常佔用系統內存約爲2M,具體緣由請參考《CLR via C#》中線程章節),這樣32位系統下單一應用程序最大內存不超過2G,也就是說,單一鏈接服務端所能接受客戶端最大的請求數爲1000(實測下爲700+);而異步鏈接則應用非阻塞式異步鏈接機制(http://www.cnblogs.com/2018/archive/2011/05/10/2040333.html)BeginAccept異步接受客戶端請求並執行相應請求邏輯,在執行完畢後系統能自動優化,並當再次應用時喚醒,從而達到可接受大量的客戶端請求,但也限於「同時」執行的客戶端數量,對於某些長鏈接客戶端巨大,但併發性小的情景適用。github

自定義協議

衆所周知,在Socket通信中傳輸的普通的字符串或者二進制數據流,不適用於一些複雜的狀況,例如約定一個可擴展的協議,可變長協議等,因此本項目採用自定義協議類型來知足可擴展需求:網絡

Header併發

協議頭app

命令異步

流1長度socket

流2長度ide

2字節性能

4字節

4字節

4字節

Body

流1

流2

N字節

N字節

說明:

協議頭爲:FF 7E ,2字節固定值

命令爲:命令編號,如1001

流1長度:Int32型指示Body中的流1的長度

流2長度:Int32型指示Body中的流2的長度

流1:字節流

流2:字節流

 

這樣,基於上述協議可自定義流一、流2的長度分別存放不一樣數據,基於協議還能夠對數據協議進行封裝,提供公共的解析方式。

  1     /// <summary>
  2     /// 通信二進制協議,此協議基於變長的流傳輸。
  3     /// 注:擴展此方法成員時,請重寫相關方法。
  4     /// </summary>
  5     /// <remarks>
  6     /// Create By CYS
  7     /// </remarks>
  8     public class CommunicateProtocol : IDisposable
  9     {
 10         #region Public Properties
 11         /// <summary>
 12         /// Byte array length of flag
 13         /// </summary>
 14         public const int ByteLength_HeaderFlag = 2;
 15         /// <summary>
 16         /// Byte array length of command
 17         /// </summary>
 18         public const int ByteLength_HeaderCmd = 4;
 19         /// <summary>
 20         /// Byte array length of header stream1
 21         /// </summary>
 22         public const int ByteLength_HeaderStream1Len = 4;
 23         /// <summary>
 24         /// Byte array length of header stream2
 25         /// </summary>
 26         public const int ByteLength_HeaderStream2Len = 4;
 27         /// <summary>
 28         /// 協議頭長度
 29         /// </summary>
 30         public static int FlagLen
 31         {
 32             get { return ByteLength_HeaderFlag; }
 33         }
 34         /// <summary>
 35         /// 命令(Int32)
 36         /// </summary>
 37         public int Command
 38         {
 39             get
 40             {
 41                 return BitConverter.ToInt32(header_Cmd, 0);
 42             }
 43             set
 44             {
 45                 BitConverter.GetBytes(value).CopyTo(header_Cmd, 0);
 46             }
 47         }
 48         /// <summary>
 49         /// 流1長度
 50         /// </summary>
 51         /// <returns></returns>
 52         public int Stream1Len
 53         {
 54             get
 55             {
 56                 return BitConverter.ToInt32(header_Stream1Len, 0);
 57             }
 58             set
 59             {
 60                 BitConverter.GetBytes(value).CopyTo(header_Stream1Len, 0);
 61             }
 62         }
 63         /// <summary>
 64         /// 流2長度
 65         /// </summary>
 66         /// <returns></returns>
 67         public int Stream2Len
 68         {
 69             get
 70             {
 71                 return BitConverter.ToInt32(header_Stream2Len, 0);
 72             }
 73             set
 74             {
 75                 BitConverter.GetBytes(value).CopyTo(header_Stream2Len, 0);
 76             }
 77         }
 78         #endregion Public Properties
 79 
 80         #region Private Properties
 81         private static byte[] header_Flag = new byte[ByteLength_HeaderFlag];
 82         private byte[] header_Cmd = new byte[ByteLength_HeaderCmd];
 83         private byte[] header_Stream1Len = new byte[ByteLength_HeaderStream1Len];
 84         private byte[] header_Stream2Len = new byte[ByteLength_HeaderStream1Len];
 85         private byte[] body_Stream1 = new byte[0];
 86         private Stream body_Stream2;
 87         #endregion Private Properties
 88 
 89         #region Constructor
 90         /// <summary>
 91         /// Static constructor
 92         /// </summary>
 93         static CommunicateProtocol()
 94         {
 95             header_Flag = new byte[ByteLength_HeaderFlag] { 0xFF, 0x7E };
 96         }
 97 
 98         #endregion Constructor
 99 
100         #region Public Method
101         /// <summary>
102         /// 判斷是不是協議頭標誌
103         /// </summary>
104         /// <param name="bytes"></param>
105         /// <returns></returns>
106         public static bool CheckFlag(byte[] bytes)
107         {
108             if (bytes.Length != header_Flag.Length)
109                 return false;
110             if (bytes.Length != 2)
111                 return false;
112             if (!bytes[0].Equals(header_Flag[0]) || !bytes[1].Equals(header_Flag[1]))
113                 return false;
114             return true;
115         }
116         /// <summary>
117         /// SetStream1
118         /// </summary>
119         /// <param name="sm"></param>
120         public void SetStream1(byte[] sm)
121         {
122             body_Stream1 = sm;
123         }
124         /// <summary>
125         /// GetStream1
126         /// </summary>
127         /// <returns></returns>
128         public byte[] GetStream1()
129         {
130             return body_Stream1;
131         }
132         /// <summary>
133         /// SetStream2
134         /// </summary>
135         /// <param name="sm"></param>
136         public void SetStream2(Stream sm)
137         {
138             body_Stream2 = sm;
139         }
140         /// <summary>
141         /// body_Stream2
142         /// </summary>
143         /// <returns></returns>
144         public Stream GetStream2()
145         {
146             return body_Stream2;
147         }
148         /// <summary>
149         /// GetHeaderBytes
150         /// </summary>
151         /// <returns></returns>
152         public byte[] GetHeaderBytes()
153         {
154             int offset = 0;
155             byte[] bytes = new byte[ByteLength_HeaderFlag + ByteLength_HeaderCmd + ByteLength_HeaderStream1Len + ByteLength_HeaderStream2Len];
156 
157             Array.Copy(header_Flag, 0, bytes, 0, ByteLength_HeaderFlag); offset += ByteLength_HeaderFlag;
158             Array.Copy(header_Cmd, 0, bytes, offset, ByteLength_HeaderCmd); offset += ByteLength_HeaderCmd;
159             Array.Copy(header_Stream1Len, 0, bytes, offset, ByteLength_HeaderStream1Len); offset += ByteLength_HeaderStream1Len;
160             Array.Copy(header_Stream2Len, 0, bytes, offset, ByteLength_HeaderStream2Len); offset += ByteLength_HeaderStream2Len;
161 
162             return bytes;
163         }
164         /// <summary>
165         /// InitProtocolHeader
166         /// </summary>
167         /// <returns></returns>
168         public static CommunicateProtocol InitProtocolHeader(byte[] source)
169         {
170             if (source.Length < ByteLength_HeaderCmd + ByteLength_HeaderStream1Len + ByteLength_HeaderStream2Len)
171             {
172                 throw new Exception("byte length is illegal");
173             }
174 
175             byte[] header_cmd = new byte[ByteLength_HeaderCmd];
176             byte[] header_stream1len = new byte[ByteLength_HeaderStream1Len];
177             byte[] header_stream2len = new byte[ByteLength_HeaderStream2Len];
178             Array.Copy(source, 0, header_cmd, 0, ByteLength_HeaderCmd);
179             Array.Copy(source, ByteLength_HeaderCmd, header_stream1len, 0, ByteLength_HeaderStream1Len);
180             Array.Copy(source, ByteLength_HeaderCmd + ByteLength_HeaderStream1Len, header_stream2len, 0, ByteLength_HeaderStream2Len);
181 
182             return new CommunicateProtocol
183             {
184                 Command = BitConverter.ToInt32(header_cmd, 0),
185                 Stream1Len = BitConverter.ToInt32(header_stream1len, 0),
186                 Stream2Len = BitConverter.ToInt32(header_stream2len, 0),
187             };
188         }
189         #endregion Public Method
190 
191         #region Private Method
192 
193         #endregion Private Method
194 
195         #region IDisposable 成員
196         /// <summary>
197         /// Dispose
198         /// </summary>
199         public void Dispose()
200         {
201             header_Cmd = null;
202             header_Stream1Len = null;
203             header_Stream2Len = null;
204             body_Stream1 = null;
205             body_Stream2 = null;
206         }
207 
208         #endregion
209     }
View Code

 

通信機制

傳統意義上的上傳與下載請求就是,一端發起Request請求,另外一端接受並答覆請求內容,這樣就完成了一次請求應答。然而,若是要實現更多的控制功能,就要在這「一去一回」上增長通訊應答次數,相似於TCP的三次握手請求。

其中,當請求被拒絕時,應向請求端發送錯誤答覆998,這樣就能夠在這個過程當中創建一個完善的請求答覆機制。

 1     public abstract class ContractAdapter
 2     {
 3         #region Public Method
 4         /// <summary>
 5         /// 
 6         /// </summary>
 7         /// <param name="p"></param>
 8         /// <param name="s"></param>
 9         protected void ExecuteProtocolCommand(CommunicateProtocol p, SocketState s)
10         {
11             if (p == null) throw new ArgumentNullException("CommunicateProtocol is null");
12             switch (p.Command)
13             {
14                 case 0: CommandWrapper(((ICommandFunc)new Command0()), p, s); break;
15                 case 1: CommandWrapper(((ICommandFunc)new Command1()), p, s); break;
16                 case 2: CommandWrapper(((ICommandFunc)new Command2()), p, s); break;
17                 case 998: CommandWrapper(((ICommandFunc)new Command998()), p, s); break;
18                 case 999: CommandWrapper(((ICommandFunc)new Command999()), p, s); break;
19                 // 
20                 case 1001: CommandWrapper(((ICommandFunc)new Command1001()), p, s); break;
21                 case 1002: CommandWrapper(((ICommandFunc)new Command1002()), p, s); break;
22                 //
23                 case 2001: CommandWrapper(((ICommandFunc)new Command2001()), p, s); break;
24                 case 2002: CommandWrapper(((ICommandFunc)new Command2002()), p, s); break;
25                 //
26                 case 3001: CommandWrapper(((ICommandFunc)new Command3001()), p, s); break;
27 
28                 default: throw new Exception("Protocol type does not exist.");
29             }
30         }
31         /// <summary>
32         /// 
33         /// </summary>
34         /// <param name="func"></param>
35         /// <param name="p"></param>
36         /// <param name="s"></param>
37         protected abstract void CommandWrapper(ICommandFunc func, CommunicateProtocol p, SocketState s);
38         #endregion Public Method
39     }
View Code

以及在「命令」中封裝,下一個命令。

 1     /// <summary>
 2     /// 
 3     /// </summary>
 4     public class Command1002 : ICommandFunc
 5     {
 6         #region ICommandFunc 成員
 7         public CommandProfile profile { get; set; }
 8 
 9         /// <summary>
10         /// 
11         /// </summary>
12         /// <param name="protocol"></param>
13         /// <param name="state"></param>
14         /// <param name="sobj"></param>
15         /// <returns></returns>
16         public SocketState Execute(CommunicateProtocol protocol, SocketState state, IHSSocket sobj)
17         {
18             state.IsReceiveThreadAlive = false;
19 
20             // Check File
21             if (!FileHelper.IsFileExist(profile.UpLoadPath + profile.UpLoadFName))
22             {
23                 var p = ProtocolMgmt.InitProtocolHeader(998, System.Text.Encoding.UTF8.GetBytes("服務端文件不存在"), null);
24                 ProtocolMgmt.SendProtocol(state, p, sobj);
25                 state.OutPutMsg = string.Format("Command 1002 :服務端文件不存在");
26                 return state;
27             }
28             if (!FileHelper.CanRead(profile.UpLoadPath + profile.UpLoadFName))
29             {
30                 var p = ProtocolMgmt.InitProtocolHeader(998, System.Text.Encoding.UTF8.GetBytes("文件已被打開或佔用,請稍後重試"), null);
31                 ProtocolMgmt.SendProtocol(state, p, sobj);
32                 state.OutPutMsg = string.Format("Command 1002 :文件已被打開或佔用");
33                 return state;
34             }
35 
36             FileInfo fi = new FileInfo(profile.UpLoadPath + profile.UpLoadFName);
37             using (FileStream fs = new FileStream(profile.UpLoadPath + profile.UpLoadFName, FileMode.Open))
38             {
39                 var p = ProtocolMgmt.InitProtocolHeader(2002, System.Text.Encoding.UTF8.GetBytes(fi.Name), fs);
40                 ProtocolMgmt.SendProtocol(state, p, sobj);
41                 state.OutPutMsg = string.Format("Command 1002 :發送文件 {0} 成功。", fi.FullName);
42             }
43 
44             return state;
45         }
46 
47 
48         #endregion
49     }
View Code

代碼結構

 

 項目分爲客戶端和服務端,其中都依賴於BLL和Core核心類庫,Core中封裝的是協議頭的解析方式、抽象/接口方法、Socket緩衝讀取、枚舉委託、異步調用基類等,BLL中主要封裝的是協議命令,如Command一、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

相關文章
相關標籤/搜索