自己写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服务器

posted @ 2013-04-30 23:33  猴健居士  阅读(3858)  评论(5编辑  收藏  举报