socket:通常每個套接字地址(協議/網路地址/埠)只允許使用一次
今天在自己寫的C/S的Server端重啟監聽時遇到的問題,原因應該是正在Accept狀態的listenSocket未能關閉,二次分配相同的埠時引發了異常。網上查看了多人的觀點,隨手記一下。
大致的處理辦法有兩類:
一是想辦法把埠關掉;二是使用埠複用忽略掉這種異常。
第一類辦法有兩種解決方法:
A.自定義一個訊息,想關閉埠時直接把這個訊息傳給監聽的埠,而監聽端也要在收到訊息後,針對這個特定的訊息編寫類似listenSocket.Close()的程式碼;
B.Socket想辦法設定成執行緒外部可訪問型別,比如線上程外部定義,作為引數傳遞進執行緒;或是定義為全域性變數。要關閉埠時直接在外部使用listenSocket.Close(),從而引發異常,使程式從監聽阻塞的Accept()狀態跳出來,當然listenSocket.Accept()要用Try...Catch來遮蔽掉這種異常。
第二類辦法就是使用埠複用,做埠繫結前,使用listenSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);將繫結的埠設定為可複用,直接遮蔽掉這種異常。
本人偷懶,直接用了埠複用,程式碼少啊。雖然對這種技術也不是很瞭解,但感覺上應該會有部分資源沒有釋放掉,如果頻繁使用恐怕會影響Server的效能【埠收到訊息後需要判斷到底發給哪個Socket,雖然是自動判斷的,但總歸會影響一些效率吧】
用特定訊息結束端口占用也是我第一感覺上想用的辦法,後來想了想,如果被別人知道我用的訊息結構,我的Server監聽豈不是隨時可以被別人關閉!好吧,我承認關閉也沒什麼損失...
順手把內容也Copy過來,以下是原文程式碼:
剛剛學習C#,在編寫一個網路通訊的程式的時候,遇到了點麻煩。監聽程式碼是放在一個執行緒中,當線上程中呼叫Socket.Accept()函式時,倘若這時需要中止該執行緒,C#似乎沒有提供現成的辦法,使用了Thread.Abort()和Thread.Interrupt()函式,都沒有用。有人說用非同步Accept方法避免阻塞,可是用這種方法就得線上程中不停地輪詢Socket的狀態,會導致CPU負荷增加。還有人提出可以現在程式內部建立一個對偵聽Socket的連線,然後傳送特定的推出資料序列,當監聽程式收到這個特殊序列後就主動結束執行緒。這個方法雖然可以解決問題,但是未免複雜了些。
想來想去,突然想到如果將監聽socket關閉掉,引發socket異常,然後在監聽執行緒中捕獲這個異常不就可以中止監聽執行緒了嗎,試驗了一下,果然可以。監聽執行緒的程式碼如下:
- using System;
- using System.IO;
- using System.Net.Sockets;
- using System.Net;
- publicclass ListenThread
- {
- publicvoid run()
- {
- Console.Write("creating listen socket ...");
- listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
- listenSocket.Bind(new IPEndPoint(IPAddress.Any, 65365));
- listenSocket.Listen(0);
- Console.Write(" done.\n");
- try
- {
- Console.Write("listening ...");
- ioSocket = listenSocket.Accept();
- Console.Write(" accepted.\n");
- Console.Write("creating I/O thread ...");
- // new Thread(new ThreadStart(this.networkIOThreadProc)).Start();
- Console.Write(" done.\n");
- }
- catch (Exception e)
- {
- Console.WriteLine("Thread aborted.");
- }
- finally
- {
- Console.WriteLine("Thread resource released.");
- }
- }
- publicvoid stop()
- {
- if (listenSocket != null)
- {
- listenSocket.Close();
- }
- }
- private Socket listenSocket = null;
- private Socket ioSocket = null;
- }
建立執行緒的程式碼如下:
- ListenThread listener = new ListenThread();
- Thread listenThread = new Thread(new ThreadStart(listener.run));
- listenThread.Start();
中止執行緒的程式碼如下:
- listener.stop();
呼叫執行緒類的stop函式之後,會將處於監聽遠端連線的listenSocket關閉掉,這時會導致引發System.Net.Sockets.SocketException,線上程程式碼中捕獲並處理這個異常就行了。這種方法實現簡單,也不會產生額外的CPU資源。