前幾天寫了篇有關寫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服務器和連接池的最新源碼

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 }

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 }

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服務器》