自己寫Web服務器(續)


前幾天寫了篇有關寫Web服務器的博文,寫得不好,多虧園友們的意見,給了我繼續探究的動力。這篇就關於上次做的Web服務器做了些更改。

  1.支持ASPX頁面的訪問

多虧了園友的提點,使用了ApplicationHost類,使得宿主程序能夠處理ASP.NET請求。后來上網搜了一下,原來就是寫一個ASP.NET的宿主程序。上MSDN看了一下還不怎么明白,終究還是找了一些博文來看才明白。

ApplicationHost屬於System.Web.Hosting命名空間,要使用這個類要添加System.Web.dll引用。先上一點代碼再解釋吧

1              private AspxCreator _aspxHost;
2             _aspxHost = (AspxCreator)ApplicationHost.CreateApplicationHost
3                 (typeof(AspxCreator), "/",
4                 AppDomain.CurrentDomain.BaseDirectory);

ApplicationHost通過調用CreateApplication方法構造一個Object的實例,這個實例實際上是AspxCreator類型的,CreateApplication方法有三個參數,第一個是AspxCreater的Type類型的對象,第二個是虛擬目錄,一般是”/”,第三個是物理路徑,這個參數跟配置在IIS配置網站是一樣的。當初不明白的就是第一個參數,那個Type究竟是什么東西?其實它是一個自定義的類而已,不過這個類是繼承MarshalByRefObject這個類的,通過aspx生成html的方法就定義在AspxCreater里面。還是看看AspxCreator的定義吧

 1     internal class AspxCreator:MarshalByRefObject
 2     {       
 3         public byte[] CreateAspxPage(string fileName,string qs)
 4         {
 5             byte[] datas=null;
 6             MemoryStream ms = null;
 7             StreamWriter sw = null;
 8             try
 9             {
10                 ms = new MemoryStream();
11                 sw = new StreamWriter(ms, Encoding.UTF8);
12                 sw.AutoFlush = true;
13                 HttpWorkerRequest worker = new ChineseRequest(fileName, qs, sw);
14                 HttpRuntime.ProcessRequest(worker);
15                 datas = new byte[ms.Length];
16                 ms.Position = 0;
17                 sw.BaseStream.Read(datas, 0, datas.Length);
18             }
19             catch (Exception e) 
20             {
21             
22             }
23             finally
24             {
25                 if (sw != null)
26                 {
27                     sw.Close();
28                     sw.Dispose();
29                     sw = null;
30                 }
31                 if (ms != null)
32                 {
33                     ms.Close();
34                     ms.Dispose();
35                     ms = null;
36                 }
37             }
38             return datas;
39         }
40     }

整個類就定義了AspxCreator就只定義了一個方法,而在這個方法里,核心的就是這兩行

1                 HttpWorkerRequest worker = new ChineseRequest(fileName, qs, sw);
2                 HttpRuntime.ProcessRequest(worker);

把一個aspx頁面轉成html,其余的都是流的操作。這里考慮到內容里頭有中文就會造成亂碼,就繼承了一下SimpleWorkerRequest這個類,把里面的方法重寫一下,改成UTF8的編碼。使得偉大的中國方體字能正確的在瀏覽器中顯示。

 1     internal class ChineseRequest:SimpleWorkerRequest
 2     {
 3         private TextWriter Output;
 4         public ChineseRequest(string fileName, string queryString, TextWriter textWirter)
 5             : base(fileName, queryString, textWirter)
 6         {
 7             Output = textWirter;
 8         }
 9         public override void SendResponseFromMemory(byte[] data, int length)
10         {
11             Output.Write(System.Text.Encoding.UTF8.GetChars(data, 0, length));
12         }
13     }

在瀏覽器發了一個ASP.NET請求時,利用之前構造的_aspxHost實例,調用CreateAspxPage方法,把相關的aspx頁面轉成html,通過byte[]返回回來,傳到瀏覽器中去

1                 byte[] aspxHtml = _aspxHost.CreateAspxPage(requestFile, queryString);
2                 SendResponse(uid, aspxHtml, response);

結果如下圖

生成成功之后,要在程序所在的目錄下新建一個bin文件夾,把程序的一個副本放到bin文件夾里,這樣程序才能正常運行。這樣就可讓aspx頁面脫離IIS運行了。

  2.對URL進行解碼

這個問題是后來自己發現的。在發送請求的URL中,如果帶有中文字符的時候,一律會自動轉碼的,轉成一串又有百分號又有字母的字符串。如果服務器單純用正則提取了請求的URL,不對其解碼的話,后果不用我說都知道了吧,之前一直沒考慮這方面的問題。

想改也不難,一行代碼即可,調用System.Web.HttpUtility類的UrlDecode方法,就可以得到正常的中文字符了。在虛擬目錄下的確有個 “我.htm”的文件

這里有幾對編碼/解碼的方法。

UrlDecode和UrlEncode,用於URL的解碼和編碼

HtmlDecode和HtmlEncode用於對Html的解碼和編碼

而對於js,只有編碼一個方法

  3.稍提高了響應的速度

我之前也說過我這個Web服務器的速度要比IIS的慢,也有園友指出我的線程沒處理好,對象構造了過多,但以我現時的水平我看不出有什么對象可以削減的。我最近也查閱過有關GC的文章和一些.NET性能優化的文章。其實找到要更改的地方不多,而且本人認為響應慢的原因是我的連接池的效率問題,故找問題都在連接池里找。

最后只找到了這個地方:在接收成功綁定的方法里頭,每接收完一次數據之后,我都會調用兩個方法

1                     buffer.FreeBuffer(unit.RecArg);
2                     buffer.SetBuffer(unit.RecArg);

 

作用分別是釋放緩沖區和設置緩沖區,方法的內部機制是先清空緩沖區的數據,把緩沖區的偏移量放入空閑棧中供下次調用,然后馬上又從棧把空閑的緩沖區取出來(方法的定義在在Socket連接池一文中有源碼,本文底端也有源碼),這樣的方法不是不好,但在這里調用先得不合適,反正都是繼續用回那個緩沖區,干脆直接把緩沖區的內容清空了就可以了。

 1         /// <summary>
 2         /// 清除緩沖區里的數據
 3         /// </summary>
 4         internal void RefreshBuffer(SocketAsyncEventArgs e)
 5         {
 6             for (int i = e.Offset; i < e.Offset + bufferSize; i++)
 7             {
 8                 if (buffers[i] == 0) break;
 9                 buffers[i] = 0;
10             }
11         }

 

然后就調用了這個方法,不知是否這里的原因,經測試這個Web服務的速度不會比IIS慢一截了,有時還比IIS快。經過本地訪問的測試和局域網內測試的結果圖

  4.信號量及阻塞

  之前對信號量和各種實現互斥的方法不是太理解,某些地方使用互斥體和信號量沒什么作用。現在更改過來了。

  在開啟服務和停止服務,一個服務開啟了不能再開啟的,要關閉了之后才能開啟,所以這里我認為應該使用信號量才對。

 1         public void RunPool(string ipAddress, int port)
 2         {
 3             IPEndPoint endpoint = new IPEndPoint(IPAddress.Parse(ipAddress), port);
 4             server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
 5             server.Bind(endpoint);
 6             server.Listen(100);
 7 
 8             //調用方法異步Accept客戶端的連接
 9             MyAsyncAccept();
10             //設置信號,防止再池在已經啟動的情況下再次啟動
11             //mutex.WaitOne();
12             semaphoreRun.WaitOne();
13         }
14         public void StopPool()
15         {
16             //把服務端的socket關了
17             if (server != null)
18                 server.Close();
19             //釋放互斥信號,等待下次啟動
20             //mutex.ReleaseMutex();
21             semaphoreRun.Release();
22             //釋放資源
23             Dispose();
24         }

 

  此外,異步發送數據在發送成功之前不能再次發送別的數據,否則會拋異常,由於之前的程序在這個地方沒有處理好,導致服務器在運行過程中會不穩定。但是如果這里用了信號量的話會導致整個服務器的數據發送只能以排着隊逐條發送的形式進行發送,延長了響應時間,於是考慮使用Monitor。

 1         public void SendMessage(string uid, byte[] messageByteArray)
 2         {
 3             ConnectionUnit unit = pool.GetConnectionUnitByUID(uid); 
 4 
 5             if (unit == null)
 6             {
 7                 if (OnSend != null) OnSend(uid, "100");
 8                 return;
 9             }
10             unit.SendArg.SetBuffer(messageByteArray, 0, messageByteArray.Length);
11             unit.client.SendAsync(unit.SendArg);
12             lock (uid)
13             {
14                 Monitor.Wait(uid);
15             }
16         }
17 
18         void SendArg_Completed(object sender, SocketAsyncEventArgs e)
19         {
20             Socket client = sender as Socket;
21             ConnectionUnit unit = e.UserToken as ConnectionUnit;
22             //這里的消息碼有三個,2字頭的是成功的,1字頭是不成功的
23             //101是未知錯誤,100是客戶端不在線
24             lock (unit.Uid)
25             {
26                 Monitor.Pulse(unit.Uid);
27                 if (e.SocketError == SocketError.Success)
28                 {
29                     if (OnSend != null) OnSend(unit.Uid, "200");
30                 }
31                 else if (OnSend != null) OnSend(unit.Uid, "101");
32                 Monitor.Wait(unit.Uid, 1);
33             }
34         }

 

  可是這回真發現這個服務器的性能與IIS有天淵之別了,在瀏覽器輸入一個URL之后狂按F5,IIS的響應很快的,可我這個服務器的響應這個這堆請求顯得力不從心,提高性能的辦法還沒想到,不過在這里同時也發現了另一個問題。

  5.關閉連接

在狂按F5之后,服務器接收了不少連接,可是這些連接基本上是無效了,就只有最后的連接才是有效的。可是關閉連接的方法只會在接收數據的方法里調用,其實這樣去回收空閑連接遠遠不夠,經檢查這樣服務器中有大量未經回收的空閑連接,這樣遲早會白白耗盡服務器的連接資源。因此每次異步方法調用之后都會進行一次判定,如果客戶端已經斷開了,就把連接關掉,回收。

我會繼續探究,有新的改動會追加到本文中去。還請各位園友多給些意見,多指點一下。謝謝。下面則是Web服務器和連接池的最新源碼

 

Socket連接池
  1     /// <summary>
  2     /// 連接單元
  3     /// </summary>
  4     class ConnectionUnit:IDisposable
  5     {
  6         private string _uid;//單元的編號,默認為-1
  7         private bool _state;//單元的狀態,true表示使用,false表示空閑
  8         private SocketAsyncEventArgs _sendArg;//專用於發送
  9         private SocketAsyncEventArgs _recArg;//專用於接收
 10         internal Socket client { get; set; }//客戶端的socket實例
 11         internal List<byte> tempArray { get; set; }//暫存已接收的數據,避免粘包用的
 12 
 13         public string Uid
 14         {
 15             get { return _uid; }
 16             set { _uid = value; }
 17         }
 18 
 19         public ConnectionUnit(string UID)
 20         {
 21             _uid = UID;
 22             tempArray = new List<byte>();
 23         }
 24 
 25         public ConnectionUnit() : this("-1") { }
 26 
 27         public ConnectionUnit(int defaultSiez)
 28         {
 29             _uid = "-1";
 30             tempArray = new List<byte>(defaultSiez);
 31         }
 32 
 33         public bool State
 34         {
 35             get { return _state; }
 36             set { _state = value; }
 37         }
 38 
 39         public SocketAsyncEventArgs SendArg
 40         {
 41             get { return _sendArg; }
 42             set { _sendArg = value; }
 43         }
 44 
 45         public SocketAsyncEventArgs RecArg
 46         {
 47             get { return _recArg; }
 48             set { _recArg = value; }
 49         }
 50 
 51         public void Dispose()
 52         {
 53             if (_sendArg != null)
 54                 _sendArg.Dispose();
 55             if (_recArg != null)
 56                 _recArg.Dispose();
 57 
 58             _sendArg = null;
 59             _recArg = null;
 60         }
 61     }
 62 
 63     class BufferManager:IDisposable
 64     {
 65         private byte[] buffers;
 66         private int bufferSize;
 67         private int allSize;
 68         private int currentIndex;
 69         private Stack<int> freeIndexs;
 70 
 71         /// <summary>
 72         /// 構造緩存池
 73         /// </summary>
 74         /// <param name="buffersSize">池總大小</param>
 75         /// <param name="defaultSize">默認單元大小</param>
 76         internal BufferManager(int buffersSize, int defaultSize) 
 77         {
 78             this.bufferSize=defaultSize;
 79             this.allSize=buffersSize;
 80             currentIndex=0;
 81             this.buffers = new byte[allSize];
 82             freeIndexs = new Stack<int>(buffersSize/defaultSize);
 83         }
 84 
 85         /// <summary>
 86         /// 
 87         /// </summary>
 88         /// <param name="e"></param>
 89         /// <param name="offSet"></param>
 90         /// <returns></returns>
 91         internal bool SetBuffer(SocketAsyncEventArgs e)
 92         {
 93             if (freeIndexs.Count > 0)
 94             {
 95                 e.SetBuffer(buffers, freeIndexs.Pop(), bufferSize);
 96             }
 97             else
 98             {
 99                 if ((allSize - currentIndex) < bufferSize) return false;
100                 e.SetBuffer(buffers, currentIndex, bufferSize);
101                 currentIndex += bufferSize;
102             }
103             return true;
104         }
105 
106         /// <summary>
107         /// 
108         /// </summary>
109         /// <param name="e"></param>
110         internal void FreeBuffer(SocketAsyncEventArgs e)
111         {
112             freeIndexs.Push(e.Offset);
113             for (int i = e.Offset; i < e.Offset + bufferSize; i++)
114             {
115                 if (buffers[i] == 0) break;
116                 buffers[i] = 0;
117             }
118             e.SetBuffer(null, 0, 0);
119         }
120 
121         /// <summary>
122         /// 清除緩沖區里的數據
123         /// </summary>
124         /// <param name="e"></param>
125         internal void RefreshBuffer(SocketAsyncEventArgs e)
126         {
127             for (int i = e.Offset; i < e.Offset + bufferSize; i++)
128             {
129                 if (buffers[i] == 0) break;
130                 buffers[i] = 0;
131             }
132         }
133 
134         public void Dispose()
135         {
136             buffers = null;
137             freeIndexs = null;
138         }
139     }
140 
141     class SocketAsyncEventArgsPool:IDisposable
142     {
143         private Dictionary<string, ConnectionUnit> busyCollection;
144         private Stack<ConnectionUnit> freeCollecton;
145 
146         internal SocketAsyncEventArgsPool(int maxConnect)
147         {
148             busyCollection = new Dictionary<string, ConnectionUnit>(maxConnect);
149             freeCollecton = new Stack<ConnectionUnit>(maxConnect);
150         }
151 
152         /// <summary>
153         /// 取出
154         /// </summary>
155         internal ConnectionUnit Pop(string uid)
156         {
157             ConnectionUnit unit = freeCollecton.Pop();
158             unit.State = true;
159             unit.Uid = uid;
160             busyCollection.Add(uid, unit);
161             return unit;
162         }
163 
164         /// <summary>
165         /// 放回
166         /// </summary>
167         internal void Push(ConnectionUnit unit)
168         {
169             if (!string.IsNullOrEmpty(unit.Uid) && unit.Uid != "-1")
170                 busyCollection.Remove(unit.Uid);
171             unit.Uid = "-1";
172             unit.State = false;
173             freeCollecton.Push(unit);
174         }
175 
176         /// <summary>
177         /// 獲取
178         /// </summary>
179         internal ConnectionUnit GetConnectionUnitByUID(string uid)
180         {
181             if (busyCollection.ContainsKey(uid))
182                 return busyCollection[uid];
183             return null;
184         }
185 
186         /// <summary>
187         /// 
188         /// </summary>
189         internal string[] GetOnLineList()
190         {
191             return busyCollection.Keys.ToArray();
192         }
193 
194         public void Dispose()
195         {
196             foreach (KeyValuePair<string,ConnectionUnit> item in busyCollection)
197                 item.Value.Dispose();
198 
199             busyCollection.Clear();
200 
201             while (freeCollecton.Count > 0)
202                 freeCollecton.Pop().Dispose();
203         }
204     }
205 
206     public class SocketPoolController:IDisposable
207     {
208 
209         #region 字段
210 
211         /// <summary>
212         /// 初始化池的互斥體
213         /// </summary>
214         private Mutex mutex = new Mutex();
215 
216         /// <summary>
217         /// Accept限制信號
218         /// </summary>
219         private Semaphore semaphoreAccept;
220 
221         /// <summary>
222         /// Accept信號
223         /// </summary>
224         private static ManualResetEvent acceptLock = new ManualResetEvent(false);
225 
226         /// <summary>
227         /// Send信號
228         /// </summary>
229         //private static ManualResetEvent sendLock = new ManualResetEvent(false);
230 
231         private Semaphore semaphoreRun;
232 
233         /// <summary>
234         /// 最大並發數(連接數)
235         /// </summary>
236         private int maxConnect;
237 
238         /// <summary>
239         /// 當前連接數(並發數)
240         /// </summary>
241         private int currentConnect;
242 
243         /// <summary>
244         /// 緩沖區單元大小
245         /// </summary>
246         private int defaultSize;
247 
248         /// <summary>
249         /// 緩沖池
250         /// </summary>
251         private BufferManager buffer;
252 
253         /// <summary>
254         /// SocketasyncEventArgs池
255         /// </summary>
256         private SocketAsyncEventArgsPool pool;
257 
258         /// <summary>
259         /// 服務端Socket
260         /// </summary>
261         private Socket server;
262 
263         /// <summary>
264         /// 完成接受的委托
265         /// </summary>
266         public delegate void AcceptHandler(string uid);
267 
268         /// <summary>
269         /// 完成發送的委托
270         /// </summary>
271         public delegate void SendHandler(string uid, string result);
272 
273         /// <summary>
274         /// 完成接收的委托
275         /// </summary>
276         public delegate void RecevieHandler(string uid, string data);
277 
278         /// <summary>
279         /// 完成接受事件
280         /// </summary>
281         public event AcceptHandler OnAccept;
282 
283         /// <summary>
284         /// 完成發送事件
285         /// </summary>
286         public event SendHandler OnSend;
287 
288         /// <summary>
289         /// 完成接收事件
290         /// </summary>
291         public event RecevieHandler OnReceive;
292 
293         #endregion
294 
295         #region 構造函數
296 
297         /// <summary>
298         /// 構造函數
299         /// </summary>
300         /// <param name="buffersize">單元緩沖區大小</param>
301         /// <param name="maxCount">並發總數</param>
302         public SocketPoolController(int buffersize, int maxCount)
303         {
304             buffer = new BufferManager(buffersize * maxCount,buffersize);
305             this.currentConnect = 0;
306             this.maxConnect = maxCount;
307             this.currentConnect = 0;
308             this.defaultSize = buffersize;
309             this.pool = new SocketAsyncEventArgsPool(maxConnect);
310             //設置並發數信號,經試驗過是並發數-1才對
311             this.semaphoreAccept = new Semaphore(maxCount-1, maxCount-1);
312             this.semaphoreRun = new Semaphore(1, 1);
313             InitPool();
314         }
315 
316         #endregion
317 
318         #region 公共方法
319 
320         /// <summary>
321         /// 啟動池
322         /// </summary>
323         /// <param name="ipAddress">服務端的IP</param>
324         /// <param name="port">端口</param>
325         public void RunPool(string ipAddress, int port)
326         {
327             IPEndPoint endpoint = new IPEndPoint(IPAddress.Parse(ipAddress), port);
328             server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
329             server.Bind(endpoint);
330             server.Listen(100);
331 
332             //調用方法異步Accept客戶端的連接
333             MyAsyncAccept();
334             //設置信號,防止再池在已經啟動的情況下再次啟動
335             //mutex.WaitOne();
336             semaphoreRun.WaitOne();
337         }
338 
339         /// <summary>
340         /// 停止池
341         /// </summary>
342         public void StopPool()
343         {
344             //把服務端的socket關了
345             if (server != null)
346                 server.Close();
347             //釋放互斥信號,等待下次啟動
348             //mutex.ReleaseMutex();
349             semaphoreRun.Release();
350             //釋放資源
351             Dispose();
352         }
353 
354         /// <summary>
355         /// 發送消息
356         /// </summary>
357         /// <param name="uid"></param>
358         /// <param name="message"></param>
359         public void SendMessage(string uid, string message)
360         {
361             //sendLock.Reset();
362             //ConnectionUnit unit=pool.GetConnectionUnitByUID(uid);
363             ////如果獲取不了連接單元就不發送了,
364             //if (unit == null)
365             //{ 
366             //    if(OnSend!=null) OnSend(uid,"100");
367             //    sendLock.Set();
368             //    return;
369             //}
370             //byte[] datas = Encoding.ASCII.GetBytes(message);
371             //unit.SendArg.SetBuffer(datas, 0, datas.Length);
372             //unit.client.SendAsync(unit.SendArg);
373             ////阻塞當前線程,等到發送完成才釋放
374             //sendLock.WaitOne();
375             byte[] datas = Encoding.ASCII.GetBytes(message);
376             SendMessage(uid, datas);
377         }
378 
379         public void SendMessage(string uid, byte[] messageByteArray)
380         {
381             //sendLock.Reset();
382             ConnectionUnit unit = pool.GetConnectionUnitByUID(uid);
383             //如果獲取不了連接單元就不發送了,
384 
385             if (unit == null)
386             {
387                 if (OnSend != null) OnSend(uid, "100");
388                 //sendLock.Set();
389                 return;
390             }
391             unit.SendArg.SetBuffer(messageByteArray, 0, messageByteArray.Length);
392             bool sendResult= unit.client.SendAsync(unit.SendArg);
393             if (!sendResult)
394             {
395                 CloseSocket(unit);
396                 if (OnSend != null) OnSend(uid, "100");
397                 return;
398             }
399             //阻塞當前線程,等到發送完成才釋放
400             Console.WriteLine("wait--------------"+uid);
401             //sendLock.WaitOne();
402             lock (uid)
403             {
404                 Monitor.Wait(uid);
405             }
406         }
407 
408         #endregion
409 
410         #region 異步事件回調
411 
412         void SendArg_Completed(object sender, SocketAsyncEventArgs e)
413         {
414             Socket client = sender as Socket;
415             ConnectionUnit unit = e.UserToken as ConnectionUnit;
416             //這里的消息碼有三個,2字頭的是成功的,1字頭是不成功的
417             //101是未知錯誤,100是客戶端不在線
418             lock (unit.Uid)
419             {
420                 Monitor.Pulse(unit.Uid);
421                 if (e.SocketError == SocketError.Success)
422                 {
423                     if (OnSend != null) OnSend(unit.Uid, "200");
424                 }
425                 else if (OnSend != null) OnSend(unit.Uid, "101");
426                 //釋放信號,以便下次發送消息執行
427                 Console.WriteLine("set>>>>>>>" + unit.Uid);
428                 //sendLock.Set();
429                 Monitor.Wait(unit.Uid, 1);
430             }
431         }
432 
433         void RecArg_Completed(object sender, SocketAsyncEventArgs e)
434         {
435             bool recResult = true;
436             Socket client = sender as Socket;
437             ConnectionUnit unit = e.UserToken as ConnectionUnit;
438             //這里大致與上一篇異步通信的一樣,只是對緩沖區的處理有一點差異
439             if (e.SocketError == SocketError.Success)
440             {
441                 int rec = e.BytesTransferred;
442                 if (rec == 0)
443                 {
444                     CloseSocket(unit);
445                     return;
446                 }
447                 if (client.Available > 0)
448                 {
449                     unit.tempArray.AddRange(e.Buffer);
450                     //buffer.FreeBuffer(unit.RecArg);
451                     //buffer.SetBuffer(unit.RecArg);
452                     buffer.RefreshBuffer(unit.RecArg);
453                     recResult = client.SendAsync(unit.RecArg);
454                     if (!recResult) CloseSocket(unit);
455                     return;
456                 }
457                 byte[] data = e.Buffer;
458                 int len = rec;
459                 int offset = e.Offset;
460                 if (unit.tempArray.Count != 0)
461                 {
462                     foreach (byte item in data)
463                     {
464                         if (item == 0) break;
465                         unit.tempArray.Add(item);
466                     }
467                     data = unit.tempArray.ToArray();
468                     rec = data.Length;
469                     offset = 0;
470                     unit.tempArray.Clear();
471                 }
472 
473                 string dataStr = Encoding.ASCII.GetString(data,offset,len);
474                 if (OnReceive != null)
475                     OnReceive(unit.Uid, dataStr);
476 
477                 if (!unit.State) return;
478                 //buffer.FreeBuffer(e);
479                 //buffer.SetBuffer(e);
480                 buffer.RefreshBuffer(e);
481                 recResult = client.ReceiveAsync(e);
482                 if (!recResult) CloseSocket(unit);
483             }
484             //這里還多個了一個關閉當前連接
485             else
486             {
487                 CloseSocket(unit);
488             }
489         }
490 
491         void Accept_Completed(object sender, SocketAsyncEventArgs e)
492         {
493             Socket client = e.AcceptSocket;
494             try
495             {
496                 if (client.Connected)
497                 {
498                     IPEndPoint point = client.RemoteEndPoint as IPEndPoint;
499                     string uid = point.Address + ":" + point.Port;
500                     ConnectionUnit unit = pool.Pop(uid);
501                     unit.client = client;
502                     unit.State = true;
503                     unit.Uid = uid;
504                     unit.RecArg.UserToken = unit;
505                     unit.SendArg.UserToken = unit;
506                     buffer.SetBuffer(unit.RecArg);
507 
508                     //在接受成功之后就開始接收數據了
509                     bool recResult = client.ReceiveAsync(unit.RecArg);
510                     //設置並發限制信號和增加當前連接數
511                     semaphoreAccept.WaitOne();
512                     Interlocked.Increment(ref currentConnect);
513                     if (!recResult)
514                     {
515                         CloseSocket(unit);
516                         return;
517                     }
518                     if (OnAccept != null) OnAccept(uid);
519                 }
520                 else if (client != null)
521                 {
522                     client.Close();
523                     client.Dispose();
524                     client = null;
525                 }
526             }
527             catch (Exception ex) { Console.WriteLine(ex.ToString()); }
528             //設置Accept信號,以便下次Accept的執行
529             acceptLock.Set();
530             e.Dispose();
531         }
532 
533         #endregion
534 
535         #region 內部輔助方法
536 
537         /// <summary>
538         /// 初始化SocketAsyncEventArgs池
539         /// 這里主要是給空閑棧填充足夠的實例
540         /// </summary>
541         private void InitPool()
542         {
543             ConnectionUnit unit = null;
544             for (int i = 0; i < maxConnect; i++)
545             {
546                 unit = new ConnectionUnit(defaultSize);
547                 unit.Uid = "-1";
548                 unit.RecArg = new SocketAsyncEventArgs();
549                 unit.RecArg.Completed += new EventHandler<SocketAsyncEventArgs>(RecArg_Completed);
550                 unit.SendArg = new SocketAsyncEventArgs();
551                 unit.SendArg.Completed += new EventHandler<SocketAsyncEventArgs>(SendArg_Completed);
552                 this.pool.Push(unit);
553             }
554         }
555 
556         /// <summary>
557         /// 異步Accept客戶端的連接
558         /// </summary>
559         void MyAsyncAccept()
560         {
561             //這里使用Action的方式異步循環接受客戶端的連接
562             //模仿同事的做法沒開線程,不知這種方式是好是壞
563             Action callback = new Action(delegate()
564             {
565                 while (true)
566                 {
567                     //每次接受都要新開一個SocketAsyncEventArgs,否則會報錯
568                     //其實我也想重復利用的
569                     SocketAsyncEventArgs e = new SocketAsyncEventArgs();
570                     e.Completed += new EventHandler<SocketAsyncEventArgs>(Accept_Completed);
571                     
572                     acceptLock.Reset();
573                     server.AcceptAsync(e);
574                     //在異步接受完成之前阻塞當前線程
575                     acceptLock.WaitOne();
576                 }
577             });
578             callback.BeginInvoke(null, null);
579         }
580 
581         /// <summary>
582         /// 關閉一個連接單元
583         /// </summary>
584         private void CloseSocket( ConnectionUnit unit )
585         {
586             //關閉並釋放客戶端socket的字眼
587             if (unit.client != null)
588             {
589                 unit.client.Shutdown(SocketShutdown.Both);
590                 unit.client.Dispose();
591                 unit.client = null;
592             }
593             //Console.WriteLine(unit.Uid+" disconnect ");
594             //把連接放回連接池
595             pool.Push(unit);
596             //釋放並發信號
597             semaphoreAccept.Release();
598             //減少當前連接數
599             Interlocked.Decrement(ref currentConnect);
600         }
601 
602         #endregion
603 
604         public void Dispose()
605         {
606             if (pool != null)
607             {
608                 pool.Dispose();
609                 pool = null;
610             }
611             if (buffer != null)
612             {
613                 buffer.Dispose();
614                 buffer = null;
615             }
616             if (server != null)
617             {
618                 server.Dispose();
619                 server = null;
620             }
621 
622         }
623     }

 

Web服務器
  1     class RequestHeader
  2     {
  3         public string ActionName { get; set; }
  4         public string URL { get; set; }
  5         public string Host { get; set; }
  6         public string Accept { get; set; }
  7         public string Connection { get; set; }
  8         public string Accept_Language { get; set; }
  9         public string User_Agent { get; set; }
 10         public string Accept_Encoding { get; set; }
 11 
 12         public string Form { get; set; }
 13         public int Content_Length { get; set; }
 14         public string Referer { get; set; }
 15         public string Content_Type { get; set; }
 16 
 17         public static RequestHeader ConvertRequestHander(string headerStr)
 18         {
 19             string regActionName = "GET|POST";
 20             string regURL = "(?<=GET|POST).*?(?=HTTP/1.1)";
 21             string regHost = @"(?<=Host\:\s).*(?=\r\n)";
 22             string regAccept = @"(?<=Accept\:\s).*(?=\r\n)";
 23             string regConnection = @"(?<=Connection\:\s).*(?=\r\n)";
 24             string regAcceptLanguage = @"(?<=Accept-Language\:\s).*(?=\r\n)";
 25             string regUserAgent = @"(?<=User-Agent\:\s).*(?=\r\n)";
 26             string regAcceptEncoding = @"(?<=Accept-Encoding\:\s).*(?=\r\n)";
 27 
 28             string regForm = @"(?<=\r\n\r\n).*";
 29             string regConntenLength = @"(?<=Connten-Length\:\s).*(?=\r\n)";
 30             string regRefere = @"(?<=Refere\:\s).*(?=\r\n)";
 31             string regContentType = @"(?<=Content-Type\:\s).*(?=\r\n)";
 32 
 33             RequestHeader hander = new RequestHeader();
 34             hander.ActionName = Regex.Match(headerStr, regActionName).Value;
 35             hander.URL = Regex.Match(headerStr, regURL).Value;
 36             hander.URL = System.Web.HttpUtility.UrlDecode(hander.URL).Trim();
 37             hander.Host = Regex.Match(headerStr, regHost).Value;
 38             hander.Accept = Regex.Match(headerStr, regAccept).Value;
 39             hander.Connection = Regex.Match(headerStr, regConnection).Value;
 40             hander.Accept_Language = Regex.Match(headerStr, regAcceptLanguage).Value;
 41             hander.Accept_Encoding = Regex.Match(headerStr, regAcceptEncoding).Value;
 42             hander.User_Agent = Regex.Match(headerStr, regUserAgent).Value;
 43             string tempStr = Regex.Match(headerStr, regConntenLength).Value;
 44             hander.Content_Length = Convert.ToInt32(tempStr == "" ? "0" : tempStr);
 45             hander.Referer = Regex.Match(headerStr, regRefere).Value;
 46             hander.Content_Type = Regex.Match(headerStr, regContentType).Value;
 47             hander.Form = Regex.Match(headerStr, regForm).Value;
 48             return hander;
 49         }
 50     }
 51 
 52     class ResponseHeader
 53     {
 54         public string ResponseCode { get; set; }
 55         public string Server { get; set; }
 56         public int Content_Length { get; set; }
 57         public string Connection { get; set; }
 58         public string Content_Type { get; set; }
 59 
 60         public override string ToString()
 61         {
 62             string result = string.Empty;
 63             result += "HTTP/1.1 " + this.ResponseCode + "\r\n";
 64             result += "Server: "+this.Server+"\r\n";
 65             result += "Content-Length: " + this.Content_Length + "\r\n";
 66             result += "Connection: "+this.Connection+"\r\n";
 67             result += "Content-Type: " + this.Content_Type + "\r\n\r\n";
 68             return result;
 69         }
 70 
 71         public string CreateErrorHtml()
 72         {
 73             string html = @"<html><head><meta http-equiv=""Content-Type"" content=""text/html;charset=utf-8""></head><body>{0}</body></html>";
 74             html = html.Replace("{0}", "<h2>My Web Server</h2><div>{0}</div>");
 75             html = string.Format(html, this.ResponseCode);
 76             return html;
 77         }
 78     }
 79 
 80     public class ServerConfigEntity 
 81     {
 82         public string IP { get; set; }
 83         public int Port { get; set; }
 84         public int MaxConnect { get; set; }
 85         public string VirtualPath { get; set; }
 86         public string DefaultPage { get; set; }
 87     }
 88 
 89     public class HttpProtocolServer 
 90     {
 91         private SocketPoolController _pool;
 92         private Dictionary<string, string> _supportExtension;
 93         private ServerConfigEntity config;
 94         private bool _runFlag;
 95         private AspxCreator _aspxHost;
 96 
 97         public HttpProtocolServer(ServerConfigEntity config)
 98         {
 99             this.config = config;
100             _pool = new SocketPoolController(32768, config.MaxConnect);
101             _supportExtension = new Dictionary<string, string>() 
102             {
103                 { "htm", "text/html" },
104                 { "html", "text/html" },
105                 { "xml", "text/xml" },
106                 { "txt", "text/plain" },
107                 { "css", "text/css" },
108                 { "png", "image/png" },
109                 { "gif", "image/gif" },
110                 { "jpg", "image/jpg" },
111                 { "jpeg", "image/jpeg" },
112                 { "zip", "application/zip"},
113                 {"js","text/javascript"},
114                 { "dll", "text/plain" },
115                 {"aspx","text/html"}
116             };
117             _aspxHost = (AspxCreator)ApplicationHost.CreateApplicationHost
118                 (typeof(AspxCreator), "/",
119                 AppDomain.CurrentDomain.BaseDirectory);
120             _runFlag = false;
121         }
122 
123         public void RunServer()
124         {
125             if (_runFlag) return;
126             _pool.OnReceive += new SocketPoolController.RecevieHandler(HandleRequest);
127             _pool.RunPool(config.IP, config.Port);
128             _runFlag = true;
129         }
130 
131         public void StopServer()
132         {
133             _pool.StopPool();
134             _pool = null;
135             _runFlag = false;
136         }
137 
138         private void HandleRequest(string uid, string header)
139         {
140             RequestHeader request = RequestHeader.ConvertRequestHander(header);
141             ResponseHeader response = new ResponseHeader();
142             response.Server = "My Test WebSite";
143             response.Connection = "close";
144 
145             //暫時只支持POST和GET的請求,其他的請求就視為未實現,發501響應
146             if (request.ActionName != "GET" && request.ActionName != "POST")
147             {
148                 response.ResponseCode = "501 Not Implemented";
149                 response.Content_Type = "text/html";
150                 SendErrorResponse(uid, response);
151                 return;
152             }
153 
154             //對請求資源名稱經行處理。主要是去除GET時帶的參數,還有把斜杠換過來
155             string fullURL = config.VirtualPath + request.URL;
156             bool containQM=fullURL.Contains('?');
157             string fileName =(containQM? Regex.Match(fullURL, @".*(?=\?)").Value:fullURL).Replace('/','\\');
158 
159             //如果請求的只是一個斜杠的,那證明請求的是默認頁面
160             if (fileName == fullURL + "\\")
161             {
162                 //如果配置中有默認頁的,發200的響應
163                 string defaultFile = Path.Combine(config.VirtualPath, config.DefaultPage);
164                 if (File.Exists(defaultFile))
165                 {
166                     response.Content_Type = "text/html";
167                     response.ResponseCode = "200 OK";
168                     SendResponse(uid, File.ReadAllText(defaultFile), response);
169                     return;
170                 }
171                 //如果不存在的,當404處理了
172                 else
173                 {
174                     response.ResponseCode = "404 Not Found";
175                     response.Content_Type = "text/html";
176                     SendErrorResponse(uid, response);
177                     return;
178                 }
179             }
180 
181             //如果請求的資源不存在的,那就發送404
182             FileInfo fileInfo = new FileInfo(fileName);
183             if (!fileInfo.Exists)
184             {
185                 response.ResponseCode = "404 Not Found";
186                 response.Content_Type = "text/html";
187                 SendErrorResponse(uid, response);
188                 return;
189             }
190 
191             //如果請求的資源不在支持的范圍內,也當作404了,感覺不是404的,貌似是403的
192             string extension = fileInfo.Extension.TrimStart('.');
193             if (string.IsNullOrEmpty(extension) || !_supportExtension.ContainsKey(extension))
194             {
195                 response.ResponseCode = "404 Not Found";
196                 response.Content_Type = "text/html";
197                 SendErrorResponse(uid, response);
198                 return;
199             }
200 
201             //既然也不是請求起始頁的,也沒發生上面列的錯誤的,就正常響應
202             response.Content_Type = _supportExtension[extension];
203             response.ResponseCode = "200 OK";
204 
205             if (string.Compare(extension, "aspx") == 0)
206             {
207                 string queryString = containQM ? Regex.Match(fullURL, @"(?<=\?).*").Value : string.Empty;
208                 string requestFile = containQM ? Regex.Match(request.URL, @"(?<=/).*(?=\?)").Value : Regex.Match(request.URL, @"(?<=/).*").Value;
209                 //byte[] aspxHtml = _aspxHost.CreateAspxPage("AJAX.aspx", string.Empty);
210 
211                 byte[] aspxHtml = _aspxHost.CreateAspxPage(requestFile, queryString);
212                 SendResponse(uid, aspxHtml, response);
213                 return;
214             }
215 
216             FileStream fs =null;
217             try
218             {
219                 fs = File.OpenRead(fileInfo.FullName);
220                 byte[] datas = new byte[fileInfo.Length];
221                 fs.Read(datas, 0, datas.Length);
222                 SendResponse(uid, datas, response);
223             }
224             finally
225             {
226                 fs.Close();
227                 fs.Dispose();
228                 fs = null;
229             }
230             return;
231         }
232 
233         private void SendErrorResponse(string uid,ResponseHeader header)
234         {
235             string errorPageContent = header.CreateErrorHtml();
236             header.Content_Length = errorPageContent.Length;
237             SendResponse(uid, errorPageContent, header);
238         }
239 
240         private void SendResponse(string uid,string content,ResponseHeader header)
241         {
242             header.Content_Length = content.Length;
243             _pool.SendMessage(uid, header.ToString());
244             _pool.SendMessage(uid, content);
245         }
246 
247         private void SendResponse(string uid, byte[] content, ResponseHeader header)
248         {
249             header.Content_Length = content.Length;
250             _pool.SendMessage(uid, header.ToString());
251             _pool.SendMessage(uid, content);
252         }
253     }

 

ASP.NET宿主
 1     internal class AspxCreator:MarshalByRefObject
 2     {       
 3         public byte[] CreateAspxPage(string fileName,string qs)
 4         {
 5             byte[] datas=null;
 6             MemoryStream ms = null;
 7             StreamWriter sw = null;
 8             try
 9             {
10                 ms = new MemoryStream();
11                 sw = new StreamWriter(ms, Encoding.UTF8);
12                 sw.AutoFlush = true;
13                 HttpWorkerRequest worker = new ChineseRequest(fileName, qs, sw);
14                 HttpRuntime.ProcessRequest(worker);
15                 datas = new byte[ms.Length];
16                 ms.Position = 0;
17                 sw.BaseStream.Read(datas, 0, datas.Length);
18             }
19             catch (Exception e) 
20             {
21             
22             }
23             finally
24             {
25                 if (sw != null)
26                 {
27                     sw.Close();
28                     sw.Dispose();
29                     sw = null;
30                 }
31                 if (ms != null)
32                 {
33                     ms.Close();
34                     ms.Dispose();
35                     ms = null;
36                 }
37             }
38             return datas;
39         }
40     }
41 
42     internal class ChineseRequest:SimpleWorkerRequest
43     {
44         private TextWriter Output;
45         public ChineseRequest(string fileName, string queryString, TextWriter textWirter)
46             : base(fileName, queryString, textWirter)
47         {
48             Output = textWirter;
49         }
50         public override void SendResponseFromMemory(byte[] data, int length)
51         {
52             Output.Write(System.Text.Encoding.UTF8.GetChars(data, 0, length));
53         }
54     }

 

若要查看連接池和Web服務器的實現詳情,請查看這里篇博文《Socket連接池》和《自己寫的Web服務器


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM