Socket(TCP)客戶端請求和服務端監聽和鏈接基礎(附例子)


一:基礎知識回顧

一:

Socket 類

實現 Berkeley 套接字接口。

Socket(AddressFamily, SocketType,ProtocolType)

使用指定的地址族、套接字類型和協議初始化 Socket 類的

新實例。

 

Bind 使 Socket 與一個本地終結點相關聯。

Listen 將 Socket 置於偵聽狀態。

Accept 為新建連接創建新的 Socket。

 

二:

Socket.Bind 方法

使 Socket 與一個本地終結點相關聯。

類型:System.Net.EndPoint

要與 Socket 關聯的本地 EndPoint。

如果需要使用特定的本地終結點,請使用 Bind 方法。 必須先調用 Bind,然后才能調用 Listen 方法。 除非需要使用特定的

本地終結點,否則不必在使用 Connect 方法之前調用 Bind。 對無連接和面向連接的協議都可以使用 Bind 方法。

 

在調用 Bind 之前,必須首先創建打算從其進行數據通信的本地 IPEndPoint。 如果您不介意分配哪個本地地址,則可以用

IPAddress.Any 作為地址參數創建一個 IPEndPoint,這樣,基礎服務提供程序將會分配最適合的網絡地址。 如果您有多個

網絡接口,這將有助於簡化您的應用程序。 如果您不介意使用哪個本地端口,則可以創建一個使用 0 作為端口號的

IPEndPoint。 在這種情況下,服務提供程序將會分配一個可用的端口號(介於 1024 和 5000 之間)。

 

如果使用上面的方法,您可以通過調用 LocalEndPoint 獲知所分配的本地網絡地址和端口號。 如果當前使用的是面向連接

的協議,則直到您調用了 Connect 或 EndConnect 方法后,LocalEndPoint 才會返回本地分配的網絡地址。 如果當前使用

的是無連接協議,則直到完成一個發送或接收操作后,才可訪問該信息。

 

如果 UDP 套接字若要獲取有關接收的數據包的接口信息,應顯式調用 SetSocketOption 方法與套接字選項設置為在調用

Bind 方法的后面的 PacketInformation。

 

如果打算接收多路廣播的數據報,則必須使用多路廣播端口號調用 Bind 方法。

 

如果打算通過使用 ReceiveFrom 方法來接收無連接的數據報,則必須調用 Bind 方法。

 

如果在調用 Bind 方法時接收到 SocketException,則可以使用 SocketException.ErrorCode 屬性獲取特定的錯誤代

碼。 獲取此代碼后,您可以參考 MSDN Library 中的 Windows Sockets 第 2 版 API 錯誤代碼文檔,獲取有關該錯誤

的詳細說明。

 

當應用程序中啟用了網絡跟蹤時,此成員將輸出跟蹤信息。

 

三:

Socket.Listen 方法

將 Socket 置於偵聽狀態。

 

參數

backlog

類型:System.Int32

掛起連接隊列的最大長度。

 

Listen 可以讓一個面向連接的 Socket 偵聽傳入的連接嘗試。 backlog 參數指定隊列中最多可容納的等待接受的傳入連接

數。 若要確定可指定的最大連接數,請檢索 MaxConnections 值。 Listen 不會阻止。

 

如果收到 SocketException,請使用 ErrorCode 屬性獲取特定的錯誤代碼。 獲取此代碼后,您可以參考 MSDN Library 中

的 Windows Sockets 第 2 版 API 錯誤代碼文檔,獲取有關該錯誤的詳細說明。 可使用 Accept 或 BeginAccept 來接受來

自隊列的連接。

 

在調用 Listen 之前,必須首先調用 Bind 方法,否則 Listen 將引發 SocketException。

 

當應用程序中啟用了網絡跟蹤時,此成員將輸出跟蹤信息。

 

根據操作系統的不同,backlog 參數被限制為不同的值。 您可以指定更大的值,但 backlog 將受操作系統的限制

四:

Socket.Accept 方法

為新建連接創建新的 Socket。

返回值

類型:System.Net.Sockets.Socket

新建連接的 Socket。

Accept 以同步方式從偵聽套接字的連接請求隊列中提取第一個掛起的連接請求,然后創建並返回新的 Socket。 不能使用返

回的這個 Socket 接受連接隊列中的任何附加連接。 然而,可以調用返回的 Socket 的 RemoteEndPoint 方法來標識遠程主

機的網絡地址和端口號。

在阻止模式中,Accept 將一直處於阻止狀態,直到傳入的連接嘗試排入隊列。 連接被接受后,原來的 Socket 繼續將傳入

的連接請求排入隊列,直到您關閉它。

如果使用非阻止的 Socket 調用此方法,而且隊列中沒有連接請求,則 Accept 將會引發 SocketException。 如果收到

SocketException,請使用 SocketException.ErrorCode 屬性獲取特定的錯誤代碼。 獲取此代碼后,您可以參考 MSDN

Library 中的 Windows Sockets 第 2 版 API 錯誤代碼文檔,獲取有關該錯誤的詳細說明。

在調用 Accept 方法之前,必須首先調用 Listen 方法來偵聽傳入的連接請求,並將偵聽到的請求放入隊列中。

當應用程序中啟用了網絡跟蹤時,此成員將輸出跟蹤信息。

五:

ThreadStart 委托

表示在 Thread 上執行的方法。

在創建托管的線程時,在該線程上執行的方法將通過一個傳遞給 Thread 構造函數的 ThreadStart 委托或

ParameterizedThreadStart 委托來表示。 在調用 Thread.Start 方法之前,該線程不會開始執行。 執行將從 ThreadStart

或 ParameterizedThreadStart 委托表示的方法的第一行開始。

Visual Basic 和 C# 用戶在創建線程時可以省略 ThreadStart 或 ParameterizedThreadStart 委托構造函數。 在 Visual

Basic 中,將方法傳遞給 Thread 構造函數時使用 AddressOf 運算符,例如 Dim t As New Thread(AddressOf

ThreadProc)。 在 C# 中,只需指定線程過程的名稱。 編譯器會選擇正確的委托構造函數。

在 2.0 版的 .NET Framework 中,為 C++ 中的靜態方法創建 ThreadStart 委托只需要一個參數:回調方法的地址(用

類名限定)。 在早期版本中,為靜態方法創建委托需要兩個參數:零(空)和方法地址。 對於實例方法,所有版本都需

要兩個參數:實例變量和方法地址。

class Test

{

    static void Main()

    {

        // To start a thread using a static thread procedure, use the

        // class name and method name when you create the ThreadStart

        // delegate. Beginning in version 2.0 of the .NET Framework,

        // it is not necessary to create a delegate explicitly.

        // Specify the name of the method in the Thread constructor,

        // and the compiler selects the correct delegate. For example:

        //

        // Thread newThread = new Thread(Work.DoWork);

        //

        ThreadStart threadDelegate = new ThreadStart(Work.DoWork);

        Thread newThread = new Thread(threadDelegate);

        newThread.Start();

        // To start a thread using an instance method for the thread

        // procedure, use the instance variable and method name when

        // you create the ThreadStart delegate. Beginning in version

        // 2.0 of the .NET Framework, the explicit delegate is not

        // required.

        //

        Work w = new Work();

        w.Data = 42;

        threadDelegate = new ThreadStart(w.DoMoreWork);

        newThread = new Thread(threadDelegate);

        newThread.Start();

    }

}

class Work

{

    public static void DoWork()

    {

        Console.WriteLine("Static thread procedure.");

    }

    public int Data;

    public void DoMoreWork()

    {

        Console.WriteLine("Instance thread procedure. Data={0}", Data);

    }

}

六:

Control.InvokeRequired 屬性

獲取一個值,該值指示調用方在對控件進行方法調用時是否必須調用 Invoke 方法,因為調用方位於創建控件所在的線程以外

的線程中。

屬性值

類型:System.Boolean

如果控件的 Handle 是在與調用線程不同的線程上創建的(說明您必須通過 Invoke 方法對控件進行調用),則為 true;否

則為 false。

實現

ISynchronizeInvoke.InvokeRequired

 

Windows 窗體中的控件被綁定到特定的線程,不具備線程安全性。 因此,如果從另一個線程調用控件的方法,那么必須使

用控件的一個 Invoke 方法來將調用封送到適當的線程。 該屬性可用於確定是否必須調用 Invoke 方法,當不知道什么線程

擁有控件時這很有用。

如果已經創建控件的句柄,則除了 InvokeRequired 屬性以外,控件上還有四個可以從任何線程上安全調用的方法,它們

是:Invoke、BeginInvoke、EndInvoke 和 CreateGraphics。 在后台線程上創建控件的句柄之前調用 CreateGraphics

可能會導致非法的跨線程調用。 對於所有其他方法調用,當從另一個線程進行調用時,應使用這些 Invoke 方法之一。

如果控件句柄尚不存在,則 InvokeRequired 沿控件的父級鏈搜索,直到它找到有窗口句柄的控件或窗體為止。 如果找不到

合適的句柄,InvokeRequired 方法將返回 false。

這意味着如果不需要 Invoke(調用發生在同一線程上),或者如果控件是在另一個線程上創建的但尚未創建控件的句柄,則

InvokeRequired 可以返回 false。

如果尚未創建控件的句柄,您就不能簡單地在控件上調用屬性、方法或事件。 這可能導致在后台線程上創建控件的句柄,從

而隔離不帶消息泵的線程上的控件並使應用程序不穩定。

當 InvokeRequired 在后台線程上返回 false 時,您也可以通過檢查 IsHandleCreated 的值來避免這種情況。 如果尚未創

建控件句柄,您必須等到控件句柄已創建,才能調用 Invoke 或 BeginInvoke。 通常,僅當在應用程序主窗體的構造函數中

創建了后台線程時(如同在 Application.Run(new MainForm()) 中),在已經顯示窗體或取消 Application.Run 之前,才會

發生這種情況。

一種解決方案是等到已經創建了窗體的句柄,然后啟動后台線程。 通過調用 Handle 屬性強制創建句柄,或者等待 Load 事

件啟動后台進程。

.NET Framework 4.5 0(共 3)對本文的評價是有幫助

[BrowsableAttribute(false)]

public bool InvokeRequired { get; }

C#

社區附加資源

一種更好的解決方案是使用 SynchronizationContext 返回的 SynchronizationContext,而不是使用控件進行線程間封送

處理。

如果應當處理消息的線程不再處於活動狀態,則可能會引發異常。

七:

Control.Invoke 方法

在擁有此控件的基礎窗口句柄的線程上執行委托。

Invoke(Delegate) 在擁有此控件的基礎窗口句柄的線程上執行指定的委托。

Invoke(Delegate, Object[]) 在擁有控件的基礎窗口句柄的線程上,用指定的參數列表執行指定委托。

八:

Control.Invoke 方法 (Delegate, Object[])

在擁有控件的基礎窗口句柄的線程上,用指定的參數列表執行指定委托。

參數

method

類型:System.Delegate

一個方法委托,它采用的參數的數量和類型與 args 參數中所包含的相同。

args

類型:System.Object[]

作為指定方法的參數傳遞的對象數組。 如果此方法沒有參數,該參數可以是 null。

返回值

類型:System.Object

Object ,它包含正被調用的委托返回值;如果該委托沒有返回值,則為 null。

實現

ISynchronizeInvoke.Invoke(Delegate, Object[])

 

委托類似於 C 或 C++ 語言中的函數指針。 委托將對方法的引用封裝在委托對象中。 然后可以將委托對象傳遞給調用所引

用的方法的代碼,隨后要在編譯時調用的方法可以是未知的。 與 C 或 C++ 中的函數指針不同的是,委托是面向對象的、類

型安全的和更保險的。

如果控件句柄尚不存在,則此方法沿控件的父級鏈搜索,直到它找到有窗口句柄的控件或窗體為止。 如果找不到合適的句

柄,此方法將引發異常。 在調用過程中引發的異常將傳播回調用方。

如果已經創建控件的句柄,則除了 InvokeRequired 屬性以外,控件上還有四個可以從任何線程上安全調用的方法,它們

是:Invoke、BeginInvoke、EndInvoke 和 CreateGraphics。 在后台線程上創建控件的句柄之前調用 CreateGraphics

可能會導致非法的跨線程調用。 對於所有其他方法調用,則應使用調用 (invoke) 方法之一封送對控件的線程的調用。

委托可以是 EventHandler 的實例,在此情況下,發送方參數將包含此控件,而事件參數將包含 EventArgs.Empty。 委托還

可以是 MethodInvoker 的實例或采用 void 參數列表的其他任何委托。 調用 EventHandler 或 MethodInvoker 委托比調

用其他類型的委托速度更快。

如果應當處理消息的線程不再處於活動狀態,則可能會引發異常。

九:

Socket.Connect 方法
建立與遠程主機的連接。
Connect(EndPoint) 建立與遠程主機的連接。
Socket.Connect 方法 (EndPoint)
建立與遠程主機的連接。
參數
remoteEP
類型:System.Net.EndPoint
EndPoint ,表示遠程設備。
如果當前使用的是面向連接的協議(如 TCP),則 Connect 方法會同步建立 LocalEndPoint 與指定遠程終結點之間的網絡
連接。 如果您使用的是無連接協議,Connect 會建立一個默認遠程主機。 在調用 Connect 之后,請使用 Send 方法將數據
發送到遠程設備,或者使用 Receive 方法從遠程設備接收數據。
如果您使用的是無連接協議(如 UDP),則不必先調用 Connect 即可發送和接收數據。 您可以使用 SendTo 和
ReceiveFrom 來與遠程主機進行同步通信。 如果您確實調用了 Connect,則將丟棄來自指定的默認地址以外的地址的任何
數據報。 如果要將默認遠程主機設置為廣播地址,必須首先調用 SetSocketOption 方法,並將套接字選項設置為
SocketOptionName.Broadcast,否則 Connect 將引發 SocketException。 如果收到 SocketException,請使用
SocketException.ErrorCode 屬性獲取特定的錯誤代碼。
除非在調用 Connect 之前已經將 Blocking 屬性專門設置為 false,否則 Connect 方法將會阻止。 如果當前使用的是面向
連接的協議(如 TCP),並且確實禁用了阻止,則 Connect 將會引發 SocketException,因為它建立連接需要花費一段時
間。 而無連接協議則不會引發異常,因為它們只需要建立一個默認遠程主機。 可以使用 SocketException.ErrorCode 來獲
得特定錯誤代碼。 獲取此代碼后,您可以參考 MSDN Library 中的 Windows Sockets 第 2 版 API 錯誤代碼文檔,獲取有
關該錯誤的詳細說明。 如果錯誤返回 WSAEWOULDBLOCK,則說明遠程主機連接已由面向連接的 Socket 初始化,但是尚
未成功完成。 可使用 Poll 方法來確定 Socket 何時完成連接。
如果當前使用的是面向連接的協議,而且在調用 Connect 之前並沒有調用 Bind,則基礎服務提供程序將會分配本地網絡
地址和端口號。 而如果當前使用的是無連接協議,則要等到發送或接收操作完成時,服務提供程序才會分配本地網絡地
址和端口號。 如果要更改默認遠程主機,則使用所需的終結點再次調用 Connect。
如果該套接字之前已斷開連接,則不能使用此方法還原連接。 請使用 BeginConnect 異步方法之一重新連接。 這是基礎
提供程序的一個限制。
當應用程序中啟用了網絡跟蹤時,此成員將輸出跟蹤信息。

二:小例子

a,

b,服務器端

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net.Sockets;
using System.Net;
using System.Threading;

namespace socket
{
    public partial class Server : Form
    {
        public Server()
        {
            InitializeComponent();
            //關閉對文本框的非法線程操作檢查,不建議用
            //TextBox.CheckForIllegalCrossThreadCalls = false;
        }
        Socket sck = null;
        Thread newthread = null;
        //聲明一個帶參數委托
        private delegate void SetText(string text);
        /// 開始服務端監聽
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btn_StartServer_Click(object sender, EventArgs e)
        {
            #region
            //創建一個Socket實例
            //第一個參數表示使用ipv4
            //第二個參數表示發送的是數據流
            //第三個參數表示使用的協議是Tcp協議
            sck = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //獲取ip地址
            IPAddress ip = IPAddress.Parse(tb_ip.Text);
            //創建一個網絡通信節點,這個通信節點包含了ip地址,跟端口號。
            //這里的端口我們設置為1029,這里設置大於1024,為什么自己查一下端口號范圍使用說明。
            IPEndPoint endPoint = new IPEndPoint(ip, int.Parse(tb_socket.Text));
            //Socket綁定網路通信節點
            sck.Bind(endPoint);
            //設置監聽隊列
            sck.Listen(10);
            ShowMsg("開啟監聽!");

            //實力化一個線程上的委托
            ThreadStart threadDelegate = new ThreadStart(this.accpet);
            //實力化一個處理線程委托的的新線程
            newthread = new Thread(threadDelegate);
            //等同於上面兩句newthread = new Thread(new ThreadStart(this.accpet));
            newthread.IsBackground = true;
            newthread.Start();
            #endregion
        }

        //消息框里面數據
       public void ShowMsg(string str)
        {
            if (this.tb_infor.InvokeRequired)
            {
                //實例化一個委托
                SetText d = new SetText(ShowMsg);
                this.Invoke(d, new object[] { str });
            }
            else
            {
                
                string Ystr = "";
                if (tb_infor.Text != "")
                {
                    Ystr = tb_infor.Text + "\r\n";
                }
                this.tb_infor.Text = Ystr + str;
            }
        }
       public void accpet()
        {
            //創建一個接收客戶端通信的Socket
            Socket accSck = sck.Accept();
            //如果監聽到客戶端有鏈接,則運行到下一部,提示,鏈接成功!
            ShowMsg("鏈接成功!");
 
        }



    }
}

c,客戶端

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net.Sockets;
using System.Threading;
using System.Net;

namespace client
{
    public partial class Client : Form
    {
        public Client()
        {
            InitializeComponent();
     
        }
        Socket clientSocket = null;
        Thread thread = null;
        /// <summary>
        /// 鏈接服務端
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btn_login_Click(object sender, EventArgs e)
        {
            clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //這里的ip地址,端口號都是服務端綁定的相關數據。
            IPAddress ip = IPAddress.Parse(tb_ip.Text);
            IPEndPoint endpoint = new IPEndPoint(ip, Convert.ToInt32(tb_socket.Text));
            clientSocket.Connect(endpoint);//鏈接有端口號與IP地址確定服務端.
        }
    }
}

三:錯誤異常

a,

處理辦法:使用InvokeRequired判斷,Invoke處理線程委托

b,



處理辦法:

//設置SOCKET允許多個SOCKET訪問同一個本地IP地址和端口號
sck.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM