asp教程.net並行與多線程的理解
asp.net教程並發請求數量影響的條件相當多,不考慮程式執行時間和是否被阻塞,它會受到伺服器tcp(一般windows伺服器好像沒限制)串連數限制,iis串連數限制,clr線程池對它的限制。
伺服器tcp限制,[hklmsystemcurrentcontrolsetservicestcpipparameters] “enableconnectionratelimiting” =dword:00000000, 該登錄機碼的值改成 “0” 即可,如果是比如windows2003伺服器,一般是沒有這個限制的,比如我自己的2003沒有。
iis串連數限制:iis6及以下,%systemroot%system32inetsrv下的metabase.xml檔案裡直接修改,比如修改預設的asprequestqueuemax="3000",可以把請求排隊列隊設定更大,修改預設的aspprocessorthreadmax="25",把request並發請求線程設定更大等,具體可以參考相關資料,進程太多會設定到cpu切換線程要耗時,根據網上說法,並不是越大越好,但可以綜合根據並發量和系統效能相關形成設定;iis7配置的地方又不一樣了,%systemroot%system32inetsrvconfigapplicationhost.config檔案裡修改,修改<serverruntime appconcurrentrequestlimit="5000" /> ,並發要求節流數量,
iis manager > applicationpools > advanced settings, queue length:1000修改隊列長度.hklmsystemcurrentcontrolsetserviceshttpparameters maxconnections 修改更大。
.net架構方面的限制
我認為一個asp.net程式運行起來後, 不關心不同的iis版本,它們生命邏輯不一樣,iis6交給的進程之前也不一樣,但我認為該進程都至少由三個線程池來管理,一個clr負責不同request請求的背景工作執行緒池,一個io處理線程,還有該進程的非託管級的線程池,託管的這兩個線程池的初始化和最大數量都可以配置和程式設定,非託管的線程池用於中轉請求給clr處理,和有io或者其他非同步作業時,處理的非託管級線程。當然託管級的線程池我們是可以作一定控制的,不同.net版本,和系統cpu數量,初始化線程池數量是不一樣的,比如我現在cpu是單cpu雙核系統,在.net4下一個mvc請求中執行下面代碼:
int workthread = 0;
int iothread = 0;
threadpool.getmaxthreads(out workthread, out iothread);
擷取到最大背景工作執行緒池和io線程池都為200,但如果根據相關資料,說該值為100 *cpu數量(雙核也算多個cpu),windows程式中擷取到的值將更大,說明預設系統給的線程數是不一樣的。
在不同.net版本中,machine.config中<processmodel autoconfig="true"/>採用預設配置,我覺得應該是.net不同版本裡直接寫死了預設線程數量等資訊,可以通過修改machine.config中該節點的一些值來達到不同的目的,預設配置的一下參數:
設定maxworkerthreaders和maxiothreads為100
設定maxconnection 為12*cpu數量
設定minfreethreads為88*cpu數量
設定minworkerthreads為50
最大線程數量 = (最大背景工作執行緒 * cpu數量) - 最小空閑線程, 當然還有其他一些重要的參數。
這些都是iis6和iis7傳統模式下的設定檔的修改,ii7整合模式又有點不一樣了,請求最大數量由下面決定:maxconcurrentrequestspercpu:限制每個cpu執行的請求的數量,即使有更多的線程可用。在.net 3.5及以前的版本中,預設值是12,在.net 4中是5000。如果設為0,沒有限制;maxconcurrentthreadspercpu:限制每個cpu處理請求的線程的數量。預設值是0,沒有限制。這基本上就相當於對請求數量沒有限制了,同時 <processmodel
requestqueuelimit="10000"/> 加了
表示請求排隊隊列10000,在%systemroot%system32inetsrvconfigapplicationhost.config中設定,<serverruntime appconcurrentrequestlimit="10000" /> ,都是增加並行要求節流的
看一款多線程作業碼利用socket
首先必須包含的兩個命名空間:
using system.net;
using system.net.sockets;
幾個常用的類:(這些東西,查下msdn就很詳細了)
iphostentry, dns,ipaddress,ipendpoint,還有最重要的socket
ipendpoint: 這個是網路終結點,很好理解,就是網路上一個固定的地址:一個ip與一個連接埠的組合.
下面我還是以我以前寫的一個很簡單的聊天程式做樣本吧, (很短代碼的)
form1.cs
//說明下, 這個是集server與client與一體的.
using system;
using system.collections.generic;
using system.componentmodel;
using system.data;
using system.drawing;
using system.text;
using system.windows.forms;
using system.net;
using system.net.sockets; //這個和上面的是使用socket必須的.
using system.io;
using system.threading; //這個是使用多線程必需的.
namespace onlysocket
{
public partial class form1 : form //partial表示這塊代碼只是form1類的部分, form1類繼承自form類
{
public form1()
{
initializecomponent(); //建構函式, 初始化容器.
}
socket sock; //定義一個socket類的對象 (預設為protected)
thread th; //定義一個thread類的對象
//
public static ipaddress getserverip() //靜態函數, 無需執行個體化即可調用.
{
iphostentry ieh = dns.gethostbyname(dns.gethostname()); //不多說了, dns類的兩個靜態函數
//或用dns.resolve()代替gethostname()
return ieh.addresslist[0]; //返回address類的一個執行個體. 這裡addresslist是數組並不奇怪,一個server有n個ip都有可能
}
private void beginlisten() //socket監聽函數, 等下作為建立新線程的參數
{
ipaddress serverip = getserverip(); //調用本類靜態函數getserverip得到本機ipaddress.
ipendpoint iep = new ipendpoint(serverip, convert.toint32(tbport.text)); //本地終結點
sock = new socket(addressfamily.internetwork, sockettype.stream, protocoltype.tcp); //執行個體化內成員sock
byte[] bytemessage = new byte[100]; //存放訊息的位元組數組緩衝區, 注意數組表示方法,和c不同的.
this.lbiep.text = iep.tostring();
sock.bind(iep); //socket類的一個重要函數, 綁定一個ip,
while (true) //這裡弄了個死迴圈來監聽連接埠, 有人會問死迴圈了,那程式不卡住了, 注意這隻是個類, 這裡還沒有main函數呢.
{
try
{
sock.listen(5); //好了,sock綁定了本地終結點就可以開始監聽了,5表示最大串連數為5
socket newsock = sock.accept(); //這裡又有socket類的一個重要的方法:accept, 該方法接受來自外面的socket串連請求, 並返回一個socket通訊端, 這個通訊端就開始處理這一個client與server之間的對話
newsock.receive(bytemessage); //接受client發送過來的資料儲存到緩衝區.
string msg = "from [" + newsock.remoteendpoint.tostring() + "]:" +system.text.encoding.utf8.getstring(bytemessage)+"n"; //getstring()函數將byte數群組轉換為string類型.
rtbtalk.appendtext(msg+"n"); //顯示在文本控制項裡
}
catch (socketexception se) //捕捉異常,
{
lbstate.text = se.tostring(); //將其顯示出來, 在此亦可以自訂錯誤.
}
}
}
private void btconnect_click(object sender, eventargs e) //連線按鍵觸發的事件: 串連server
{
btconnect.enabled = false;
btstopconnect.enabled = true;
try
{
th = new thread(new threadstart(beginlisten)); //建立一個新的線程專門用於處理監聽,這句話可以分開寫的,比如: threadstart ts=new threadstart(beginlisten); th=new thread (ts); 不過要注意, threadstart的建構函式的參數一定要是無參數的函數. 在此函數名其實就是其指標, 這裡是委託嗎?
th.start(); //啟動線程
lbstate.text = "listenning...";
}
catch (socketexception se) //處理異常
{
messagebox.show(se.message, "出現問題", messageboxbuttons.ok, messageboxicon.information);
}
catch (argumentnullexception ae) //參數為空白異常
{
lbstate.text = "參數錯誤";
messagebox.show(ae.message, "錯誤", messageboxbuttons.ok, messageboxicon.warning);
}
}
private void btstopconnect_click(object sender, eventargs e) //停止監聽
{
btstopconnect.enabled = false;
btconnect.enabled = true;
sock.close(); //關閉通訊端
th.abort(); //終止監聽線程
lbstate.text = "listenning stopped";
}
private void btexit_click(object sender, eventargs e)
{
sock.close();
th.abort();
dispose(); //清理資源,就是釋放記憶體
this.close(); //關閉對話方塊, 退出程式
}
private void btsend_click(object sender, eventargs e)
{
try
{
ipaddress clientip = ipaddress.parse(tbtargetip.text); //類ipaddress的靜態函數parse() :將text轉化為ipaddress的一個執行個體.
int clientport = convert.toint32(tbport.text); //c#的這些轉化函數很方便的,不像c++那樣麻煩
ipendpoint clientiep = new ipendpoint(clientip, clientport); //這裡用client表示不是很好....,
byte[] byte_message;
socket socket = new socket(addressfamily.internetwork, sockettype.stream, protocoltype.tcp); 執行個體化的時候還有很多參數的, 這個是tcp的. tcp的sockettype是stream:資料流, 如果協議類型是udp, 則是資料包傳送, qq就是用的udp.
socket.connect(clientiep); //socket的又一個函數connect(ipendpoint) .串連遠程通訊端
byte_message = system.text.encoding.utf8.getbytes(rtbwords.text); //發現utf8可支援中文,就用之
socket.send(byte_message);
rtbtalk.appendtext("n"+"my words:" + rtbwords.text + "n");
socket.shutdown(socketshutdown.both);
socket.close();
}
catch (argumentnullexception ae)
{
messagebox.show(ae.message,"參數為空白",messageboxbuttons.okcancel,messageboxicon.information);
}
catch (socketexception se)
{
messagebox.show(se.message, "出現問題", messageboxbuttons.ok, messageboxicon.information);
}
}
}
}
program.cs
using system;
using system.collections.generic;
using system.windows.forms;
namespace onlysocket
{
static class program
{
/// <summary>
/// 應用程式的主進入點。
/// </summary>
[stathread]
static void main() //這兒才是main函數
{
application.enablevisualstyles();
application.setcompatibletextrenderingdefault(false);
application.run(new form1());
}
}
}
寫了半天了, 夠累的了, 雖然都是很基礎的東西, 我自己寫的時候也複習了一邊 , 呵呵.
其實多線程我自己也不是很熟練, 記得去年暑假寫過一個多線程掃描器, 也不知為啥, 線程開到50以上就異常, 很鬱悶的. 其實當時我就是用的new thread=thread(new threadstart(fun))實現的, 方法感覺很笨拙,呵呵.
大致代碼好像是這樣的吧:
先寫個scan類:
public class scan
{
try{ public scan(){ ...init... }
public void scan{ ..task迴圈掃描... } //task結構體裡面有ip, 連接埠, 是否已掃描標記flag}
catch{}
}
然後主函數裡面可以這樣搞:
scan[] scanner = new scan[xx]
thread[] thread = new thread[xx];
for (int i = 0; i < xx;i++)
{
scanner[i] = new scan(this, i);
thread[i] = new thread(new threadstart(scanner[i].startscan));
thread[i].start();
}
其實這樣就可以簡單的實現多線程了.