自己写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服务器和连接池的最新源码
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服务器》