Fork me on GitHub
负载均衡

C#手动做一个负载均衡服务器

思路

负载均衡服务器最出名的当数 Nginx了。Nginx服务器通过异步的方式把连接转发给内网和N个服务器,用来分解单台应用服务器的压力,了解了原理及场景后,用C#来实现一个。思路如下:

1. 使用一个站点的 Application_BeginRequest 来接收连接,转发连接。

2. 对各类静态资源做单独处理,(可转可不转)

3. 可以转发Get,Post,异步转发。

4. 对指定的请求,转发到同一台服务器,保持使用者的登录状态。

实现

Vs2015建一个Mvc建站: localhost:1500/。修改Web.config,用于接收所有连接。

 <system.webServer> 
    <modules runAllManagedModulesForAllRequests="true">
    </modules>
 </system.webServer>

引入 MyCmn.dll (http://code.taobao.org/svn/MyOql/libs4),MyHelper 封装了 类型转换函数,方便使用。

代码如下:

复制代码
        public class RequestWrap
        {
            public HttpWebRequest Request { get; set; }
            private ManualResetEvent Event { get; set; }
            private Action<HttpWebResponse> Action { get; set; }
            public RequestWrap(HttpWebRequest request)
            {
                Event = new ManualResetEvent(false);
                this.Request = request;
            }

            public void Run(Action<HttpWebResponse> act)
            {
                this.Action = act;

                Request.BeginGetResponse(new AsyncCallback(GetResponseCallback), this);
                this.Event.WaitOne(15000);
            }

            private static void GetResponseCallback(IAsyncResult asyncResult)
            {
                RequestWrap wrap = (RequestWrap)asyncResult.AsyncState;
                HttpWebResponse response = null;
                try
                {
                    response = wrap.Request.EndGetResponse(asyncResult) as HttpWebResponse;
                }
                catch (WebException ex)
                {
                    response = ex.Response as HttpWebResponse;
                }
                wrap.Action(response);
                wrap.Event.Set();
            }
        }

        private void Application_BeginRequest(Object source, EventArgs e)
        {
            var lastExtName = "";
            {
                var lastIndex = Request.Url.LocalPath.LastIndexOf('.');
                if (lastIndex > 0)
                {
                    lastExtName = Request.Url.LocalPath.Slice(lastIndex);
                }
            }

            // <modules runAllManagedModulesForAllRequests="true"> 设置之后,静态资源就进来了。
            if (lastExtName.IsIn(".js", ".css", ".html", ".htm", ".png", ".jpg", ".gif"))
            {
                return;
            }

            HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create(GetTransferHost() + Request.RawUrl);
            myRequest.Proxy = null;

            myRequest.Timeout = 15000;
            myRequest.ReadWriteTimeout = 3000;

            myRequest.Method = Request.HttpMethod;

            Request.Headers.AllKeys.All(k =>
            {
                if (!WebHeaderCollection.IsRestricted(k))
                {
                    myRequest.Headers.Add(k, Request.Headers[k]);
                }
                else
                {
                    var val = Request.Headers[k];
                    if (k.Is("Range"))
                    {
                        myRequest.AddRange(val.AsInt());
                    }
                    else if (k.Is("If-Modified-Since"))
                    {
                        myRequest.IfModifiedSince = val.AsDateTime();
                    }
                    else if (k.Is("Accept"))
                    {
                        myRequest.Accept = val;
                    }
                    else if (k.Is("Content-Type"))
                    {
                        myRequest.ContentType = val;
                    }
                    else if (k.Is("Expect"))
                    {
                        myRequest.Expect = val;
                    }
                    else if (k.Is("Date"))
                    {
                        myRequest.Date = val.AsDateTime();
                    }
                    else if (k.Is("Host"))
                    {
                        myRequest.Host = val;
                    }
                    else if (k.Is("Referer"))
                    {
                        myRequest.Referer = val;
                    }
                    else if (k.Is("Transfer-Encoding"))
                    {
                        myRequest.TransferEncoding = val;
                    }
                    else if (k.Is("User-Agent"))
                    {
                        myRequest.UserAgent = val;
                    }
                    //else if (k.Is("Connection"))
                    //{
                    //    o.Connection = val;
                    //}
                    else if (k.Is("KeepAlive"))
                    {
                        myRequest.KeepAlive = val.AsBool();
                    }
                }
                return true;
            });

            using (var readStream = Request.InputStream)
            {
                while (true)
                {
                    byte[] readBuffer = new byte[1024];
                    var readLength = readStream.Read(readBuffer, 0, readBuffer.Length);
                    if (readLength == 0) break;
                    myRequest.GetRequestStream().Write(readBuffer, 0, readLength);
                }
            }

            new RequestWrap(myRequest).Run(myResponse =>
            {
                myResponse.Headers.AllKeys.All(k =>
                {
                    if (k.Is("X-Powered-By"))
                    {
                        return true;
                    }
                    Response.Headers[k] = myResponse.Headers[k];
                    return true;
                });

                using (var readStream = myResponse.GetResponseStream())
                {

                    while (true)
                    {
                        byte[] readBuffer = new byte[1024];
                        var read = readStream.Read(readBuffer, 0, readBuffer.Length);
                        if (read == 0) break;
                        Response.OutputStream.Write(readBuffer, 0, read);
                    }
                }
                Response.StatusCode = myResponse.StatusCode.AsInt();
            });
            Response.End();
        }

        public static string GetClientIPAddress()
        {
            if (HttpContext.Current == null)
                return string.Empty;
            HttpContext context = HttpContext.Current;//System.Web.HttpContext.Current;

            string ipAddress = context.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];

            if (!string.IsNullOrEmpty(ipAddress))
            {
                string[] addresses = ipAddress.Split(',');
                if (addresses.Length != 0)
                {
                    return addresses[0];
                }
            }

            return context.Request.ServerVariables["REMOTE_ADDR"]; //+ " host " + context.Request.UserHostAddress;
        }


        private string GetTransferHost()
        {
            string[] hosts = new string[] { "http://localhost" };

            var index = GetClientIPAddress().Last() % hosts.Length ;

            return hosts[index];
        }
复制代码

 

解释

其中, RequestWrap 是对异步请求包装的请求类。封装了一个 Run 方法进行异步调用。过滤了应用服务器的回发头 X-Powered-By 

关于 WebHeaderCollection.IsRestricted ,是由于一个错误引出的: 异常处理:必须使用适当的属性或方法修改此标头,文章地址: http://blog.useasp.net/archive/2013/09/03/the-methods-to-dispose-http-header-cannot-add-to-webrequest-headers.aspx,摘录如下:

你可以在这里设置其他限制的标头.
注意:
Range HTTP标头是通过AddRange来添加
If-Modified-Since HTTP标头通过IfModifiedSince 属性设置
Accept由 Accept 属性设置。
Connection由 Connection 属性和 KeepAlive 属性设置。
Content-Length由 ContentLength 属性设置。
Content-Type由 ContentType 属性设置。
Expect由 Expect 属性设置。
Date由 Date属性设置,默认为系统的当前时间。
Host由系统设置为当前主机信息。
Referer由 Referer 属性设置。
Transfer-Encoding由 TransferEncoding 属性设置(SendChunked 属性必须为 true)。
User-Agent由 UserAgent 属性设置。
 
其中: Connection 设置会出错,所以我注掉了。
 

 

alarm   作者:NewSea     出处:http://newsea.cnblogs.com/    QQ,MSN:iamnewsea@hotmail.com

posted on 2015-11-21 20:10  HackerVirus  阅读(578)  评论(1编辑  收藏  举报