1. 程式人生 > >socket:通常每個套接字地址(協議/網路地址/埠)只允許使用一次

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異常,然後在監聽執行緒中捕獲這個異常不就可以中止監聽執行緒了嗎,試驗了一下,果然可以。監聽執行緒的程式碼如下:

  1. using System;  
  2. using System.IO;  
  3. using System.Net.Sockets;  
  4. using System.Net;  
  5. publicclass ListenThread  
  6. {  
  7.    publicvoid run()  
  8.    {  
  9.       Console.Write("creating listen socket ...");  
  10.       listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);  
  11.       listenSocket.Bind(new IPEndPoint(IPAddress.Any, 65365));  
  12.       listenSocket.Listen(0);  
  13.       Console.Write("    done.\n");  
  14.       try
  15.       {  
  16.          Console.Write("listening ...");  
  17.          ioSocket = listenSocket.Accept();  
  18.          Console.Write("    accepted.\n");  
  19.          Console.Write("creating I/O thread ...");  
  20.          // new Thread(new ThreadStart(this.networkIOThreadProc)).Start();
  21.          Console.Write("    done.\n");  
  22.       }  
  23.       catch (Exception e)  
  24.       {  
  25.          Console.WriteLine("Thread aborted.");  
  26.       }  
  27.       finally
  28.       {  
  29.          Console.WriteLine("Thread resource released.");  
  30.       }  
  31.    }  
  32.    publicvoid stop()  
  33.    {  
  34.       if (listenSocket != null)  
  35.       {  
  36.          listenSocket.Close();  
  37.       }  
  38.    }  
  39.    private Socket listenSocket = null;  
  40.    private Socket ioSocket = null;  
  41. }  

建立執行緒的程式碼如下:
  1. ListenThread listener = new ListenThread();  
  2. Thread listenThread = new Thread(new ThreadStart(listener.run));  
  3. listenThread.Start();  

中止執行緒的程式碼如下:
  1. listener.stop();  

呼叫執行緒類的stop函式之後,會將處於監聽遠端連線的listenSocket關閉掉,這時會導致引發System.Net.Sockets.SocketException,線上程程式碼中捕獲並處理這個異常就行了。這種方法實現簡單,也不會產生額外的CPU資源。