最近項目因為要負載均衡所以就使用了sessionState的Session共享,但是卻發現多台服務器中有個別服務器的Session沒有共享,於是就有了這篇文章,下面開始說說。
這個基本上就分兩種情況:一種就是在多台服務器上,在IIS中建立的網站的標識符一樣(要想都一樣,需要在建網站的時候輸入同樣的描述符,就我知道的方 法,只有在第一次建這個網站的時候,輸入的描述符要一樣,建好后再改描述符是沒用的,它會保留以前的標識符,另外這種情況有一個特例,就是默認網站,雖然 在iis中可以看到默認網站的標識符都是1,但是它不屬於第一種情況,它屬於第二情況)。
另外一種情況是,網站的標識符不一樣,比如在兩台服務器上不同名的網站,或者在同一台服務器上的兩個或多個網站(在同一IIS下這種情況根本就不可能會有相同標識符的網站),還有就是默認網站的情況。
這個我在網上有看到別人說,說到要起相同網站名的文章有一篇,但那個說的不對,它只是強調起相同的名字,其實真正是要IIS中網站的標識符一樣,不是它們的網站名(也就是描述符)一樣,同一IIS下也能有兩個網站擁有相同的標識符。這點一定要注意。
另外要說的是,如果使用cookie來存儲sessionid的話,這個一定要確保這些共享session的網站在網頁瀏覽器中可以使用相同的域名來訪問 到,比如a.test.com,b.test.com,c.test.com等等的.test.com域的網站。這些網站所在的計算機或者說服務器不非得 真的處在這樣的一個域或者網站有這樣的一個域名什么的,只要能讓瀏覽器所在的計算機通過這些域名訪問到這些服務器就行,我自己的測試環境就是家里的兩台計 算機的一個小內網,也沒有域,只是修改了使用瀏覽器做測試的機器上的C:\WINDOWS\system32\drivers\etc\host,讓瀏覽 器可以使用那些域名訪問到另外的服務器即可。這個也說明在客戶端的瀏覽器使用cookie的一個機制,只要是地址欄里輸入的頂級域一樣,比如訪問 a.test.com,b.test.com什么的,只要都是test.com的,它就會使用那些cookie了。所以說要確保這些服務器都在同一個域名 下面,這樣這些服務器才能得到一樣的sessionid,因為asp.net會把sessionid存儲在叫ASP.NET_SessionId的 cookie中,只有訪問同樣的域名的網站才會發送這個COOKIE,不然訪問其他域名的網站,比如a.test1.com,瀏覽器不會發送這個 cookie,即使使用同樣的stateserver,不能得到相同的sessionid,也無法共享session。
先說一下stateserver的web.config等的配置,這些不論是上面說的哪兩種情況都是一樣的,在web.config中需要添加以下兩個節點:
|
1
2
3
4
|
<!--web.config中sessionState節點的配置方案-->
<sessionState cookieName=
"DotNesession"
mode=
"StateServer"
stateConnectionString=
"tcpip=127.0.0.1:42424"
cookieless=
"false"
timeout=
"30"
/>
<httpRuntime targetFramework=
"4.5"
/>
<machineKey validationKey=
"D4033EDA0C4490B94331CAD18C43A72146BC0083FBB0D5ABE876A43EE271904EB2DAE181096561A451F7ADF61ED9EDBE40C0B920ED45682F96A8EA2788B96913"
decryptionKey=
"DDE7A8EF5E6B8C31DA73F8D942FF28B0790BF0F2446202BBA0F80F39A5375BAA"
validation=
"SHA1"
decryption=
"AES"
/>
|
這個machineKey的值可以是隨意的,但一定要配成一樣的,因為需要用它來給session進行解密。另外要說的就是如果stateserver配為遠程的服務器的話,則需要修改stateserver服務器的注冊表的[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\aspnet_state\Parameters]中的AllowRemoteConnection,把它改成1,默認是不允許遠程連接的,值為0,另外在這里也可以改Port 端口號,默認的stateserver需要用的asp.net狀態服務需要監聽42424端口。然后在stateserver上啟動asp.net狀態服務,另外要是先啟動了這個服務,再修改的注冊表,則需要再重啟一下這個服務。
最后說一下以上兩種情況分別需要寫不同的處理程序,其實處理第二種情況的程序也能處理第一種情況,不過第一種情況的程序比第二種的簡單。在這里先說一下分 類的標准,這是根據,在如果沒有寫處理程序的情況下,stateserver會把這些要共享session的網站產生的session放置到 appdomin的情況來區分的。
第一種情況,因為網站的標識ID是相同的,當然除了默認網站那個特例,因為aps.net狀態服務應該是根據網站的標識id來決定放置session的 appdomain的id, 所以放置這些網站的appdomain的id也是相同的,因此這些網站產生的session會被放在同一個appdomain中,所以處理程序很簡單,只 要確保ASP.NET_SessionId這個cookie能在這些網站中共享就行了。網上給出來的通用做 法,就是在自定義實現IHttpModule的自定義類中,在request結束的事件中,改寫ASP.NET_SessionId的cookie的 domain為這些網站的主域名即可,按上例來說,即.test.com。代碼如下:
public class Test:IHttpModule
{
#region IHttpModule 成員
void IHttpModule.Dispose()
{
//throw new Exception("The method or operation is not implemented.");
}
void IHttpModule.Init(HttpApplication context)
{
context.EndRequest += new EventHandler(this.EndRequest);
}
#endregion
private void EndRequest(object sender, EventArgs args)
{
HttpApplication application = sender as HttpApplication;
for (int i = 0; i < application.Response.Cookies.Count; i++ )
{
if( application.Response.Cookies[i].Name == "ASP.NET_SessionId")
application.Response.Cookies[i].Domain = ".test.com";
}
}
}
在EndRequest當中,或在此類中的其他相應方法中,切記不要沒有for循環而使用以下代碼來賦值
application.Response.Cookies["ASP.NET_SessionId"].domain = ".test.com"
甚至是不用循環而直接在方法中直接使用像string str = application.Response.Cookies["ASP.NET_SessionId"].value這樣的代碼。不然這樣會產生一種這樣 的效果,每刷新兩次瀏覽器,session就會發生更新,這樣就導致程序出現問題。內部的原因我也不甚了解,但從表面來看,是因為 application.Response.Cookies這個集合只有在網站最開始打開的時候,可以通過循環訪問到ASP.NET_SessionId 這個cookie,然后再刷新網頁,這個循環就不會訪問到ASP.NET_SessionId這個cookie了,但是使用上面的代碼,則仍可以直接訪問 到ASP.NET_SessionId這個cookie。應該是就因為在這種情況下修改了這個cookie,導致cookie或什么發生了變化等原因,最 終讓下一個提交請求被認為是新的請求,而產生了新的session。
第二種情況,根據第一種情況中的描述,現在因為網站的標識id不同(默認網站情況除外),所以每個網站放置session的appdomain的id也不 同,因此這些session被放在了不同的appdomain中,所以像上面那樣的代碼,雖然可以確保將想共享的sessionid傳至服務器,但是由於 session被放在了不同的appdomain中,所以實際上是產生了兩個相同sessionid的session被分別放在了屬於不同網站的兩個 appdomain中, 這樣肯定也是不能共享session的了,這個解決起來就麻煩點,需要通過反射來調用一個asp.net沒有公開的類 OutOfProcSessionStateStore,看名字也知道它代表的是進程外session存儲了,來修改它一個靜態成員s_uribase, 此成員代表state外部存儲需要訪問的appdomain的一個內部id,只要在創建session前,設置一個相同的appdomain的id(當然 看程序這個id其實好像可以隨意設了,應該不局限於只使用網站的根域名),這樣就能確保取session和放置session都到同一個 appdomain中。大致的代碼如下:
public class CookieTest:IHttpModule
{
#region IHttpModule 成員
void IHttpModule.Dispose()
{
//throw new Exception("The method or operation is not implemented.");
}
void IHttpModule.Init(HttpApplication context)
{
//throw new Exception("The method or operation is not implemented.");
Type stateServerSessionProvider = typeof(HttpSessionState).Assembly.GetType("System.Web.SessionState.OutOfProcSessionStateStore");
FieldInfo uriField = stateServerSessionProvider.GetField("s_uribase", BindingFlags.Static | BindingFlags.NonPublic);
if (uriField == null)
throw new ArgumentException("UriField was not found");
uriField.SetValue(null, ".test.com");
context.EndRequest += new EventHandler(this.EndRequest);
}
private void EndRequest(object sender, EventArgs args)
{
HttpApplication application = sender as HttpApplication;
for (int i = 0; i < application.Response.Cookies.Count; i++)
{
application.Response.Cookies[i].Domain = ".test.com";
}
}
}
最后在web.config中注冊這個http模塊:
<httpModules>
<add name="test" type="WebApplication5.Test,WebApplication5"/>
</httpModules>
這樣就可以在相同頂級域名的網站間共享session狀態了.
還可以在Global.asax.cs中加入下面代碼
public override void Init() { base.Init(); foreach (string moduleName in this.Modules) { string appName = "APPNAME"; IHttpModule module = this.Modules[moduleName]; SessionStateModule ssm = module as SessionStateModule; if (ssm != null) { FieldInfo storeInfo = typeof(SessionStateModule).GetField("_store", BindingFlags.Instance | BindingFlags.NonPublic); SessionStateStoreProviderBase store = (SessionStateStoreProviderBase)storeInfo.GetValue(ssm); if (store == null)//In IIS7 Integrated mode, module.Init() is called later { FieldInfo runtimeInfo = typeof(HttpRuntime).GetField("_theRuntime", BindingFlags.Static | BindingFlags.NonPublic); HttpRuntime theRuntime = (HttpRuntime)runtimeInfo.GetValue(null); FieldInfo appNameInfo = typeof(HttpRuntime).GetField("_appDomainAppId", BindingFlags.Instance | BindingFlags.NonPublic); appNameInfo.SetValue(theRuntime, appName); } else { Type storeType = store.GetType(); if (storeType.Name.Equals("OutOfProcSessionStateStore")) { FieldInfo uribaseInfo = storeType.GetField("s_uribase", BindingFlags.Static | BindingFlags.NonPublic); uribaseInfo.SetValue(storeType, appName); } } } } }

