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);

posted @ 2014-04-22 22:42  HongMaJu  阅读(8650)  评论(0编辑  收藏  举报