接口和协议组成 个人对 Socket 和协议的理解 以及 使用 System.Net.Sockets 命名空间编写简单的 TCP 通讯程序

SinDynasty · 2017年02月25日 · 最后由 wangshun 回复于 2018年10月15日 · 5720 次阅读
本帖已被设为精华帖!

Socket

我们要想实现两个程序在不同主机上进行相互通讯,我们就必须准确得标识这两个程序。我们知道对于一个程序来说其都有一个 PID(即进程控制符),虽然对于同一台主机上来说 PID 是唯一的,但是在不同主机之间,两个程序的 PID 那就不一定是唯一的了,其极有可能会发生重复,因此我们无法使用 PID 来标识不同主机上的程序。

于是Socket变应运而生,其使用 IP 地址标识了主机后,再使用端口标识了程序,也许你会问:既然 IP 标识了主机,那为什么不继续使用 PID 标识程序,那是因为当我们开启一个程序,但系统分配给这个程序的 PID 是一个随机的值,这样就导致了我们本地的主机程序 无法得知 我们所需连接的 远程主机程序 的 PID,从而无法完成连接。而端口却是固定的且是唯一的,一个端口只能被一个进程所占用,因此在网络通信中我们使用的端口标识程序。

综上所述,Socket是指 本地 IP 地址 和 远程 IP 地址 以及 本地端口号 和 远程端口号 的组合,其作用是标识不同主机间的程序,在计算机专业术语中它的意思是套接字,但我们光靠一个套接字显然不可能实现网络通讯,我们需要一些方法,这些方法最初起源于 1983 年加利福尼亚大学伯克利分校发布的 Berkeley Sockets API,后经微软、英特尔等大型公司的完善及规范,形成了一套标准,并在 Windows 上推出了 WinSock API,因此在网络编程中,其也被常常称为套接字接口,简称套接口。在.Net Framework的基础类库中,微软对 WinSock API 进行了进一步的包装,并为我们提供了强大的System.Net.Sockets命名空间,我们可以利用该命名空间轻松地完成网络编程。

备注:在早期,一个端口确实只能被一个进程占用,但是在后续的发展中进行了拓展,可以多进程同时占用一个端口,当然如果这样做了的话,那网络通信可能会出问题,这就需要我们自己进行处理。

协议

最近发现有很多人认为 Socket 是指协议,在这儿我想很明确地告诉你们,那是错误的,记住Socket 不是协议
协议为计算机网络中进行数据交换而建立的规则、标准或约定的集合(百度百科上是这么写的)。其中它有三个要素:语义、语法、时序,以下是我个人对这三个要素的理解:

语义:是指发送的每个数据包中的数据所代表的含义,例如第一个数据包发送的数据表示的是协议编号,第二个数据包发送的数据表示的是真正所需传递的数据 1,第三个数据包发送的数据表示的是真正所需传递的数据 2,这就是语义。其也可以看做是发送数据包的顺序。

语法:是指单个数据包的格式,例如每个数据包的前 8 个字节表示的是数据包的大小,而后面的 N 个字节表示的是该数据包所传递的数据,这就是语法,当然这语法也包括数据在转换成字节时所用到的编码类型等。

时序:是指整个服务器和客户端交互的流程,其中包括长连接与短连接、同步与异步等。

协议指的是通过语义 + 语法 + 时序所建立起来的规则、标准或约定的集合,而 Socket 仅仅是实现网络通讯的工具而已。

Socket 不是协议!Socket 不是协议!Socket 不是协议!

重要的事说三遍

相关知识补充

套接字的分类:System.Net.Sockets命名空间中,微软为我们提供了一个名为SocketType的枚举,其表示的是套接字的类型。SocketType枚举中共有 6 个成员,其中常用的有流式套接字(Stream)、数据报套接字(Dgram)、原始套接字(Raw)。流式套接字(Stream)必须被用于 TCP 协议中,这已经被微软写死,如果该套接字类型与协议类型不对应会报错;数据报套接字(Dgram)也已经被微软写死,只能用于 UDP 协议中;原始套接字(Raw)可以操纵网络层和传输层,一般被用于自定义协议中。

TCP 是一种面向连接的协议:由于 TCP 是一种面向连接的协议,因此对于 TCP 服务端来说,其必须创建用于监听的 Socket用于连接的 Socket才能完成通讯,而由于 UDP 是一种面向无连接的协议,因此对于 UDP 服务端来说,其不需要监听连接。

网络通讯中所有数据都是以字节的形式进行传输:由于网络通讯中所有数据都是以字节的形式进行传输的,因此我们必须把相关数据都转化为 byte[] 的类型。其中 System 命名空间中的BitConverter类,为我们提供了一系列基础数据类型与字节数组相互转换的方法;另外对于字符串与字节数组的相互转换,我们可以使用 System.Text 命名空间中的Encoding类,并根据相应的编码方式来实现。

服务端和客户端通讯的基本流程(TCP)

服务端:创建用于监听的 Socket ListenSocket

方法一:

Socket ListenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

方法二:

Socket ListenSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);

AddressFamily:是一个枚举,指的是服务端地址的类型,对于 TCP 来说,我们提供的服务端地址一般是 IPV4 地址或 IPV6 地址,他们分别对应的是 InterNetwork 和 InterNetworkV6。
SocketType:是一个枚举,指的是套接字的类型,一般常用的有 Stream、Dgram 和 Raw,TCP 是使用流式套接字来实现传输的,因此我们在这里使用的是 Stream,如果我们使用了SocketType.Stream那 ProtocolType 必须使用ProtocolType.Tcp,另外 Dgram 是指数据报套接字,其被用于 UDP,如果我们使用了SocketType.Dgram那 ProtocolType 必须使用ProtocolType.Udp
ProtocolType:是一个枚举,指的是协议类型,这里我们要用的是 Tcp,当然这个枚举里是没有 HTTP 的,由于 HTTP 是基于 TCP 的,因此如果要发 HTTP,我们需要使用的还是 Tcp。

服务端:将 Socket ListenSocket 与 服务端的 IP 及端口 进行绑定

IPAddress ServerIPAddress = IPAddress.Parse(ServerIP);
IPEndPoint ServerIPEndPoint = new IPEndPoint(ServerIPAddress, ServerPort);
ListenSocket.Bind(ServerIPEndPoint);

IPAddress:表示的是一个 IP 地址,我们可以使用IPAddress.Parse(String)的方法,将一个字符串形式的 IP 地址转换为一个 IPAddress 类。
IPEndPoint:表示的是一个由 IP 及端口组成的一个网络终结点,我们可以使用构造函数IPEndPoint(IPAddress, Int32)来进行创建,其所需的第二个参数指的是端口号。
Socket.Bind(EndPoint):该方法可以将 Socket ListenSocket 与 服务端的 IP 及端口 进行绑定,其中 EndPoint 表示的是一个网络地址,由于 IPEndPoint 继承自 EndPoint,所以我们我们可以直接将 IPEndPoint 带入方法中来完成该步骤。

服务端:将 Socket ListenSocket 设为监听状态,开始监听

ListenSocket.Listen(MaxListenNumber);

Socket.Listen(Int32):该方法可以将服务端的 Socket ListenSocket 设为监听状态,其所需的参数 Int32 表示的是连接队列的最大连接数。

客户端:创建连接 Socket ConnectSocket

方法一:

Socket ConnectSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

方法二:

Socket ConnectSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);

该方法与之前说的创建服务端的 Socket ListenSocket 相同因此不再叙述。

客户端:向服务器发起连接请求,并完成连接

设定服务端的 IP 及端口号

IPAddress ServerIPAddress = IPAddress.Parse(ServerIP);
IPEndPoint ServerIPEndPoint = new IPEndPoint(ServerIPAddress, ServerPort);

IPAddress 和 IPEndPoint:在发起连接前,我们需要设定服务端的地址,而设定服务端地址的方法也是利用 IPAddress 和 IPEndPoint 这两个类,由于该方法与之前讲的相同,我便不再叙说。
同步:

ConnectSocket.Connect(ServerIPEndPoint);

Socket.Connect(EndPoint):该方法可以使客户端向服务端以同步的方式发起一个连接请求。其所需的参数 EndPoint 表示的是服务端的地址,我们可以将 IPEndPoint 来带入。
异步:

    object[] ConnectState = { ConnectSocket };
    ConnectSocket.BeginConnect(ServerIPEndPoint, ConnectCallBack, ConnectState);

private static void ConnectCallBack(IAsyncResult AsyncResult)
{
    object[] ConnectState = (object[])AsyncResult.AsyncState;
    Socket ConnectSocket = (Socket)ConnectState[0];
    ConnectSocket.EndConnect(AsyncResult);
}//void ConnectCallBack(IAsyncResult AsyncResult)

Socket.BeginConnect(EndPoint, AsyncCallback, Object):该方法可以使客户端向服务器以异步的方式发起一个连接请求。该方法需要 3 个参数:第一个参数 EndPoint,表示的是服务端的地址,我们可以将IPEndPoint来带入;第二个参数 AsyncCallback表示的是一个异步委托,其原型是 delegate void AsyncCallback(IAsyncResult ar),因此回调函数应是 一个无返回值 void 类型 且 所需参数是一个 IAsyncResult 的函数,由于该参数是一个委托,因此我们可以直接将回调函数名直接带入即可;第三个参数 Object表示所需传递至回调函数的参数,由于这里只能带入一个参数,我们需要将多个参数打包成一个 object[],然后带入。
IAsyncResult 和 IAsyncResult.AsyncState:IAsyncResult 是一个接口,表示的是一个异步操作的状态,我们可以使用 IAsyncResult 的 AsyncState 属性以及强制转换类型的方法来获取 在 BeginConnect(EndPoint, AsyncCallback, Object) 函数中所带入的第三个参数 object 的 值。
Socket.EndConnect(IAsyncResult):该方法可以结束一个客户端向服务端发起的异步连接请求,其所需的参数是一个IAsyncResult

服务端:接收客户端发来的请求并创建一个用于连接该客户端的 Socket ConnectSocket

同步:

Socket ConnectSocket = ListenSocket.Accept();

Socket.Accept():该方法可以使服务端接受客户端的连接请求,并生成一个服务端用于连接客户端的 Socket ConnectSocket。
异步:

    object[] AcceptState = { ListenSocket };
    ListenSocket.BeginAccept(AcceptCallBack, AcceptState);

private static void AcceptCallBack(IAsyncResult AsyncResult)
{
    object[] AcceptState = (object[])AsyncResult.AsyncState;
    Socket ListenSocket = (Socket)AcceptState[0];
    Socket ConnectSocket = ListenSocket.EndAccept(AsyncResult);
}//void AcceptCallBack(IAsyncResult AsyncResult)

Socket.BeginAccept(AsyncCallback, Object):该方法可以 以异步的方式 使服务端接收来自客户端发来的连接请求,并生成一个服务端用于连接客户端的 Socket ConnectSocket。该方法需要 2 个参数:第一个参数 AsyncCallback和之前说的一样,表示是的一个异步委托,我们只需带入回调函数名即可;第二个参数 Object表示所需传递至回调函数的参数。
Socket.EndAccept(IAsyncResult):该方法可以终止 使服务端接受客户端的连接请求 的异步操作,其所需的参数是一个IAsyncResult,该方法具有一个返回值,返回值即为 Socket ConnectSocket。

客户端:开始向服务端发送数据(服务端向客户端发送数据也是同理)

同步:

ConnectSocket.Send(ClientData, 0, ClientData.Length, SocketFlags.None);

Socket.Send(Byte[], Int32, Int32, SocketFlags):该方法可以将要发送的 byte[] 形式的数据写入连接套接字中,从而实现数据的发送。该方法需要 4 个参数:第一个参数 Byte[]表示的是所需发送的数据对象;第二个参数 Int32表示的是发送数据的起始位置;第三个参数 Int32表示的是发送数据的大小;第四个参数 SocketFlags是一个枚举,其指的是套接字的收发行为,一般我们只需使用 None 即可。
异步:

    object[] SendState = { ConnectSocket, ClientData };
    ConnectSocket.BeginSend(ClientData, 0, ClientData.Length, SocketFlags.None, SendCallBack, SendState);

private static void SendCallBack(IAsyncResult AsyncResult)
{
    object[] SendState = (object[])AsyncResult.AsyncState;
    Socket ConnectSocket = (Socket)SendState[0];
    byte[] ClientData = (byte[])SendState[1];
    ConnectSocket.EndSend(AsyncResult);
}//void SendCallBack(IAsyncResult AsyncResult)

Socket.BeginSend(Byte[], Int32, Int32, SocketFlags, AsyncCallback, Object):该方法可以 以异步的方式 将要发送的数据写入连接套接字中。该方法需要 6 个参数::第一个参数 Byte[]表示的是所需发送的数据对象;第二个参数 Int32表示的是发送数据的起始位置;第三个参数 Int32表示的是发送数据的大小;第四个参数 SocketFlags是一个枚举,其指的是套接字的收发行为,一般我们只需使用 None 即可;第五个参数 AsyncCallback表示的是一个异步委托,我们只需带入回调函数名即可;第六个参数 Object表示的是所需传递给回调函数的数据。
Socket.EndSend(IAsyncResult):该方法可以终止 将数据 byte[] 写入套接字 的异步操作,其所需的参数是一个IAsyncResult,该方法具有一个 int 类型的返回值,表示的是已经写入套接字的数据大小。

服务端:开始接收客户端发来的数据(客户端接收服务端发来的数据也是同理)

同步:

ConnectSocket.Receive(ClientData, 0, ClientData.Length, SocketFlags.None);

Socket.Receive(Byte[], Int32, Int32, SocketFlags):该方法可以从连接套接字中取出远程发来的数据 byte[]。该方法需要 4 个参数:第一个参数 Byte[]表示的是接收数据的数据对象;第二个参数 Int32表示的是接收数据的起始存放位置;第三个参数 Int32表示的是所需接收数据的大小;第四个参数 SocketFlags是一个枚举,其指的是套接字的收发行为,一般我们只需使用 None 即可。
异步:

    object[] ReceiveState = { ConnectSocket, ClientData };
    ConnectSocket.BeginReceive(ClientData, 0, ClientData.Length, SocketFlags.None, ReceiveCallBack, ReceiveState);

private static void ReceiveCallBack(IAsyncResult AsyncResult)
{
    object[] ReceiveState = (object[])AsyncResult.AsyncState;
    Socket ConnectSocket = (Socket)ReceiveState[0];
    byte[] ClientData = (byte[])ReceiveState[1];
    ConnectSocket.EndReceive(AsyncResult);
}//void ReceiveCallBack(IAsyncResult AsyncResult)

Socket.BeginReceive(Byte[], Int32, Int32, SocketFlags, AsyncCallback, Object):该方法可以 以异步的方式 从连接套接字中取出远程发来的数据 byte[]。该方法需要 6 个参数:第一个参数 Byte[]表示的是接收数据的数据对象;第二个参数 Int32表示的是接收数据的起始存放位置;第三个参数 Int32表示的是所需接收数据的大小、第四个参数 SocketFlags是一个枚举,其指的是套接字的收发行为,一般我们只需使用 None 即可;第五个参数 AsyncCallback表示的是一个异步委托,我们只需带入回调函数名即可;第六个参数 Object表示的是所需传递给回调函数的数据。
Socket.EndReceive(IAsyncResult):该方法可以终止 从连接套接字中取出远程发来的数据 的异步操作,其所需的参数是一个IAsyncResult,该方法具有一个 int 类型的返回值,表示的是从连接套接字中已经取出的数据大小。

客户端:关闭客户端用于连接服务端的 Socket ConnectSocket(服务端关闭 Socket ConnectSocket 也是同理)

同步:

ConnectSocket.Disconnect(true);

Socket.Disconnect(Boolean):该方法可以关闭套接字连接,并允许设定是否可以重用套接字,其所需的参数 Boolean 是一个布尔值,为 true 则表示可以重用套接字,为 false 则表示不可重用套接字。
异步:

    object[] DisconnectState = { ConnectSocket };
    ConnectSocket.BeginDisconnect(true, DisconnectCallBack, DisconnectState);

private static void DisconnectCallBack(IAsyncResult AsyncResult)
{
    object[] DisconnectState = (object[])AsyncResult.AsyncState;
    Socket ConnectSocket = (Socket)DisconnectState[0];
    ConnectSocket.EndReceive(AsyncResult);
}//void DisconnectCallBack(IAsyncResult AsyncResult)

Socket.BeginDisconnect(Boolean, AsyncCallback, Object):该方法可以 以异步的方式 关闭套接字连接。该方法有 3 个参数:第一个参数 Boolean是一个布尔值,为 true 则表示可以重用套接字,为 false 则表示不可重用套接字;;第二个参数 AsyncCallback表示的是一个异步委托,我们只需带入回调函数名即可;第三个参数 Object表示的是所需传递给回调函数的数据。
Socket.EndDisconnect(IAsyncResult):该方法可以终止 关闭套接字连接 的异步操作,其所需的参数是一个IAsyncResult

System.Net.Sockets 命名空间的 API 文档

在 System.Net.Sockets 命名空间还有其他许多重要的类、方法等,例如能增强异步通讯的 SocketAsyncEventArgs 类等,其相关 API 文档都在 MSDN 里,网址是https://msdn.microsoft.com/zh-cn/library/system.net.sockets(v=vs.110).aspx

短连接与长连接

短连接:短连接是指服务端与客户端每次完成通讯后,就断开连接套接字 Socket ConnectSocket 的连接,同时每次需要服务端与客户端产生通讯的时候,都要重新创建连接套接字 Socket ConnectSocket,最典型的短连接应该就是 HTTP 了吧(当然从 HTTP/1.1 起,HTTP 默认使用长连接)
长连接:用于对于客户端来说,其只需要一个连接套接字 Socket ConnectSocket 就能完成与服务端所有的通讯,因此对于长连接来说,客户端只需在最开始创建一个连接套接字 Socket ConnectSocket,便可以和服务端反复通讯多次。而当一个连接套接字 Socket ConnectSocket 长时间存在,其便会出现两个问题:1、当 Socket ConnectSocket 的连接如果被中断后,我们再去使用这个 Socket ConnectSocket 进行通讯时,其便会出错;2、如果服务端不断地接收客户端的连接并创建相应的连接套接字 Socket ConnectSocket,却又不去关闭已经失效的 Socket ConnectSocket,那么服务端迟早将会挂掉。由于会出现以上这两个问题,为此便引申出了 KeepAlive 机制。
KeepAlive 机制:简单地来讲就是让一台主机每隔一段时间不停地向另一台远程主机发送连接请求(心跳包),以确认对方是否仍处于连接状态,如果发现对方长时间不应答,便关闭与对方连接。理论上来说服务端和客户端都可以向对方发送心跳包,但一般来说都是由客户端向服务端发送心跳包。在 TCP 中,KeepAlive 机制默认是如果对方 2 小时不应答,则会断开连接,但是由于 2 小时时间过长,因此一般我们都要重写该机制。

共收到 16 条回复 时间 点赞

C#:使用 System.Net.Sockets 命名空间编写的简单的 TCP 通讯示例(包含同步服务端、同步客户端、异步服务端、异步客户端)

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Text;
using System.Threading;

namespace SinDynasty
{
    public class SyncServer
    {
        private class SyncTCP
        {
            private static Socket ListenSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);
            private Socket ConnectSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);
            private List<byte[]> ClientDataList = new List<byte[]>();
            private List<byte[]> ServerDataList = new List<byte[]>();

            public static void Listen(string ServerIP, int ServerPort, int MaxListenNumber)
            {
                IPAddress ServerIPAddress = IPAddress.Parse(ServerIP);
                IPEndPoint ServerIPEndPoint = new IPEndPoint(ServerIPAddress, ServerPort);
                ListenSocket.Bind(ServerIPEndPoint);
                ListenSocket.Listen(MaxListenNumber);
            }//void Listen(string ServerIP, int ServerPort, int MaxListenNumber)

            public void Accept()
            {
                ConnectSocket = ListenSocket.Accept();
            }//void Accept()

            public void Receive()
            {
                byte[] ClientDataQuantity = new byte[4];
                ConnectSocket.Receive(ClientDataQuantity, 0, ClientDataQuantity.Length, SocketFlags.None);

                long CycleIndex = 0;
                while (CycleIndex < BitConverter.ToInt32(ClientDataQuantity, 0))
                {
                    byte[] ClientDataLength = new byte[4];
                    ConnectSocket.Receive(ClientDataLength, 0, ClientDataLength.Length, SocketFlags.None);

                    byte[] ClientData = new byte[0];
                    if (BitConverter.ToInt32(ClientDataLength, 0) > 0)
                    {
                        ClientData = new byte[BitConverter.ToInt32(ClientDataLength, 0)];
                        ConnectSocket.Receive(ClientData, 0, ClientData.Length, SocketFlags.None);
                    }

                    ClientDataList.Add(ClientData);

                    CycleIndex = CycleIndex + 1;
                }
            }//void Receive()

            public void ProtocolHandler()
            {
                byte[] ProtocolNumber = ClientDataList[0];
                if (ProtocolNumber.Length != 4)
                {
                    ProtocolNumber = BitConverter.GetBytes(0);
                }
                string ProtocolName = "Protocol_" + BitConverter.ToInt32(ProtocolNumber, 0);
                Protocol P = new Protocol();
                Type T = P.GetType();
                MethodInfo ProtocolMethodInfo = T.GetMethod(ProtocolName);
                if (ProtocolMethodInfo == null)
                {
                    ProtocolMethodInfo = T.GetMethod("Protocol_0");
                }
                object[] ProtocolParameter = { ClientDataList };
                ServerDataList = (List<byte[]>)ProtocolMethodInfo.Invoke(null, ProtocolParameter);
            }//void ProtocolHandler()

            public void Send()
            {
                byte[] ServerDataQuantity = BitConverter.GetBytes(ServerDataList.Count);
                ConnectSocket.Send(ServerDataQuantity, 0, ServerDataQuantity.Length, SocketFlags.None);

                foreach (byte[] ServerData in ServerDataList)
                {
                    byte[] ServerDataLength = BitConverter.GetBytes(ServerData.Length);
                    ConnectSocket.Send(ServerDataLength, 0, ServerDataLength.Length, SocketFlags.None);

                    ConnectSocket.Send(ServerData, 0, ServerData.Length, SocketFlags.None);
                }
            }//void Send()

            public void Disconnect()
            {
                if (ConnectSocket != null)
                {
                    ConnectSocket.Disconnect(true);
                }
            }//void DisconnectConnect()

            private class Protocol
            {
                public static List<byte[]> Protocol_0(List<byte[]> ClientDataList)
                {
                    List<byte[]> ServerDataList = new List<byte[]>();
                    ServerDataList.Add(Encoding.Default.GetBytes("未知的协议"));

                    return ServerDataList;
                }//List<byte[]> Protocol_0(List<byte[]> ClientDataList)

                public static List<byte[]> Protocol_1(List<byte[]> ClientDataList)
                {
                    List<byte[]> ServerDataList = ClientDataList;

                    return ServerDataList;
                }//List<byte[]> Protocol_1(List<byte[]> ClientDataList)

            }//class Protocol

        }//class SyncTCP

        public static void TCP_Start(string ServerIP, int ServerPort, int MaxListenNumber)
        {
            SyncTCP.Listen(ServerIP, ServerPort, MaxListenNumber);
        }//void TCP_Start(string ServerIP, int ServerPort, int MaxListenNumber)

        public static void TCP()
        {
            SyncTCP Server = new SyncTCP();
            while (true)
            {
                Server.Accept();
                Server.Receive();
                Server.ProtocolHandler();
                Server.Send();
                Server.Disconnect();
            }
        }//void TCP()

    }//class SyncServer

    public class SyncClient
    {
        private class SyncTCP
        {
            private static Socket ConnectSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);
            public List<byte[]> ClientDataList = new List<byte[]>();
            public List<byte[]> ServerDataList = new List<byte[]>();

            public static void Connect(string ServerIP, int ServerPort)
            {
                IPAddress ServerIPAddress = IPAddress.Parse(ServerIP);
                IPEndPoint ServerIPEndPoint = new IPEndPoint(ServerIPAddress, ServerPort);
                ConnectSocket.Connect(ServerIPEndPoint);
            }//void Connect(string ServerIP, int ServerPort)

            public void Send()
            {
                byte[] ClientDataQuantity = BitConverter.GetBytes(ClientDataList.Count);
                ConnectSocket.Send(ClientDataQuantity, 0, ClientDataQuantity.Length, SocketFlags.None);

                foreach (byte[] ClientData in ClientDataList)
                {
                    byte[] ClientDataLength = BitConverter.GetBytes(ClientData.Length);
                    ConnectSocket.Send(ClientDataLength, 0, ClientDataLength.Length, SocketFlags.None);

                    ConnectSocket.Send(ClientData, 0, ClientData.Length, SocketFlags.None);
                }
            }//void Send()

            public void Receive()
            {
                byte[] ServerDataQuantity = new byte[4];
                ConnectSocket.Receive(ServerDataQuantity, 0, ServerDataQuantity.Length, SocketFlags.None);

                long CycleIndex = 0;
                while (CycleIndex < BitConverter.ToInt32(ServerDataQuantity, 0))
                {
                    byte[] ServerDataLength = new byte[4];
                    ConnectSocket.Receive(ServerDataLength, 0, ServerDataLength.Length, SocketFlags.None);

                    byte[] ServerData = new byte[0];
                    if (BitConverter.ToInt32(ServerDataLength, 0) > 0)
                    {
                        ServerData = new byte[BitConverter.ToInt32(ServerDataLength, 0)];
                        ConnectSocket.Receive(ServerData, 0, ServerData.Length, SocketFlags.None);
                    }

                    ServerDataList.Add(ServerData);

                    CycleIndex = CycleIndex + 1;
                }
            }//void Receive()

            public static void Disconnect()
            {
                if (ConnectSocket != null)
                {
                    ConnectSocket.Disconnect(true);
                }
            }//void Disconnect()

        }//class SyncTCP

        public static void TCP_Start(string ServerIP, int ServerPort)
        {
            SyncTCP.Connect(ServerIP, ServerPort);
        }//void TCP_Start(string ServerIP, int ServerPort)

        public static List<byte[]> TCP(List<byte[]> ClientDataList)
        {
            SyncTCP TCP = new SyncTCP();
            TCP.ClientDataList = ClientDataList;
            TCP.Send();
            TCP.Receive();
            List<byte[]> ServerDataList = TCP.ServerDataList;

            return ServerDataList;
        }//List<byte[]> TCP(List<byte[]> ClientDataList)

        public static void TCP_End()
        {
            SyncTCP.Disconnect();
        }//void TCP_End()

    }//class SyncClient

    public class AsyncServer
    {
        private class AsyncTCP
        {
            private static Socket ListenSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);
            private Socket ConnectSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);
            private List<byte[]> ClientDataList = new List<byte[]>();
            private List<byte[]> ServerDataList = new List<byte[]>();

            public static void Listen(string ServerIP, int ServerPort, int MaxListenNumber)
            {
                IPAddress ServerIPAddress = IPAddress.Parse(ServerIP);
                IPEndPoint ServerIPEndPoint = new IPEndPoint(ServerIPAddress, ServerPort);
                ListenSocket.Bind(ServerIPEndPoint);
                ListenSocket.Listen(MaxListenNumber);
            }//void Listen(string ServerIP, int ServerPort, int MaxListenNumber)

            private ManualResetEvent MRE_Accept = new ManualResetEvent(false);
            public void Accept()
            {
                MRE_Accept.Reset();

                ListenSocket.BeginAccept(AcceptCallBack, null);

                MRE_Accept.WaitOne();
            }//void Accept()

            private void AcceptCallBack(IAsyncResult AsyncResult)
            {
                ConnectSocket = ListenSocket.EndAccept(AsyncResult);

                MRE_Accept.Set();
            }//void AcceptCallBack(IAsyncResult AsyncResult)

            private ManualResetEvent MRE_Receive = new ManualResetEvent(false);
            private ManualResetEvent MRE_ReceiveClientDataQuantity = new ManualResetEvent(false);
            private ManualResetEvent MRE_ReceiveClientDataLength = new ManualResetEvent(false);
            private ManualResetEvent MRE_ReceiveClientData = new ManualResetEvent(false);

            public void Receive()
            {
                MRE_Receive.Reset();

                MRE_ReceiveClientDataQuantity.Reset();

                byte[] ClientDataQuantity = new byte[4];
                object[] ReceiveClientDataQuantityState = { ClientDataQuantity };
                ConnectSocket.BeginReceive(ClientDataQuantity, 0, ClientDataQuantity.Length, SocketFlags.None, ReceiveClientDataQuantityCallBack, ReceiveClientDataQuantityState);

                MRE_ReceiveClientDataQuantity.WaitOne();

                MRE_Receive.WaitOne();
            }//void Receive()

            private void ReceiveClientDataQuantityCallBack(IAsyncResult AsyncResult)
            {
                object[] ReceiveClientDataQuantityState = (object[])AsyncResult.AsyncState;
                byte[] ClientDataQuantity = (byte[])ReceiveClientDataQuantityState[0];
                ConnectSocket.EndReceive(AsyncResult);

                MRE_ReceiveClientDataQuantity.Set();

                long CycleIndex = 0;
                while (CycleIndex < BitConverter.ToInt32(ClientDataQuantity, 0))
                {
                    MRE_ReceiveClientDataLength.Reset();

                    byte[] ClientDataLength = new byte[4];
                    ConnectSocket.BeginReceive(ClientDataLength, 0, ClientDataLength.Length, SocketFlags.None, ReceiveClientDataLengthCallBack, null);

                    MRE_ReceiveClientDataLength.WaitOne();

                    byte[] ClientData = new byte[0];
                    if (BitConverter.ToInt32(ClientDataLength, 0) > 0)
                    {
                        MRE_ReceiveClientData.Reset();

                        ClientData = new byte[BitConverter.ToInt32(ClientDataLength, 0)];
                        ConnectSocket.BeginReceive(ClientData, 0, ClientData.Length, SocketFlags.None, ReceiveClientDataCallBack, null);

                        MRE_ReceiveClientData.WaitOne();
                    }

                    ClientDataList.Add(ClientData);

                    CycleIndex = CycleIndex + 1;
                }

                MRE_Receive.Set();
            }//void ReceiveClientDataQuantityCallBack(IAsyncResult AsyncResult)

            private void ReceiveClientDataLengthCallBack(IAsyncResult AsyncResult)
            {
                ConnectSocket.EndReceive(AsyncResult);

                MRE_ReceiveClientDataLength.Set();
            }//void ReceiveClientDataLengthCallBack(IAsyncResult AsyncResult)

            private void ReceiveClientDataCallBack(IAsyncResult AsyncResult)
            {
                ConnectSocket.EndReceive(AsyncResult);

                MRE_ReceiveClientData.Set();
            }//void ReceiveClientDataCallBack(IAsyncResult AsyncResult)

            public void ProtocolHandler()
            {
                byte[] ProtocolNumber = ClientDataList[0];
                if (ProtocolNumber.Length != 4)
                {
                    ProtocolNumber = BitConverter.GetBytes(0);
                }
                string ProtocolName = "Protocol_" + BitConverter.ToInt32(ProtocolNumber, 0);
                Protocol P = new Protocol();
                Type T = P.GetType();
                MethodInfo ProtocolMethodInfo = T.GetMethod(ProtocolName);
                if (ProtocolMethodInfo == null)
                {
                    ProtocolMethodInfo = T.GetMethod("Protocol_0");
                }
                object[] ProtocolParameter = { ClientDataList };
                ServerDataList = (List<byte[]>)ProtocolMethodInfo.Invoke(null, ProtocolParameter);
            }//void ProtocolHandler()

            private ManualResetEvent MRE_Send = new ManualResetEvent(false);
            private ManualResetEvent MRE_SendServerDataQuantity = new ManualResetEvent(false);
            private ManualResetEvent MRE_SendServerDataLength = new ManualResetEvent(false);
            private ManualResetEvent MRE_SendServerData = new ManualResetEvent(false);
            public void Send()
            {
                MRE_Send.Reset();

                MRE_SendServerDataQuantity.Reset();

                byte[] ServerDataQuantity = BitConverter.GetBytes(ServerDataList.Count);
                ConnectSocket.BeginSend(ServerDataQuantity, 0, ServerDataQuantity.Length, SocketFlags.None, SendServerDataQuantityCallBack, null);

                MRE_SendServerDataQuantity.WaitOne();

                MRE_Send.WaitOne();
            }//void Send()

            private void SendServerDataQuantityCallBack(IAsyncResult AsyncResult)
            {
                ConnectSocket.EndSend(AsyncResult);

                MRE_SendServerDataQuantity.Set();

                foreach (byte[] ServerData in ServerDataList)
                {
                    MRE_SendServerDataLength.Reset();

                    byte[] ServerDataLength = BitConverter.GetBytes(ServerData.Length);
                    ConnectSocket.BeginSend(ServerDataLength, 0, ServerDataLength.Length, SocketFlags.None, SendServerDataLengthCallBack, null);

                    MRE_SendServerDataLength.WaitOne();

                    MRE_SendServerData.Reset();

                    ConnectSocket.BeginSend(ServerData, 0, ServerData.Length, SocketFlags.None, SendServerDataCallBack, null);

                    MRE_SendServerData.WaitOne();
                }

                MRE_Send.Set();
            }//void SendServerDataQuantityCallBack(IAsyncResult AsyncResult)

            private void SendServerDataLengthCallBack(IAsyncResult AsyncResult)
            {
                ConnectSocket.EndSend(AsyncResult);

                MRE_SendServerDataLength.Set();
            }//void SendServerDataLengthCallBack(IAsyncResult AsyncResult)

            private void SendServerDataCallBack(IAsyncResult AsyncResult)
            {
                ConnectSocket.EndSend(AsyncResult);

                MRE_SendServerData.Set();
            }//void SendServerDataCallBack(IAsyncResult AsyncResult)

            private ManualResetEvent MRE_Disconnect = new ManualResetEvent(false);
            public void Disconnect()
            {
                if (ConnectSocket != null)
                {
                    MRE_Disconnect.Reset();

                    ConnectSocket.BeginDisconnect(true, DisconnectCallBack, null);

                    MRE_Disconnect.WaitOne();
                }
            }//void DisconnectConnect()

            private void DisconnectCallBack(IAsyncResult AsyncResult)
            {
                ConnectSocket.EndDisconnect(AsyncResult);

                MRE_Disconnect.Set();
            }//void DisconnectCallBack(IAsyncResult AsyncResult)

            private class Protocol
            {
                public static List<byte[]> Protocol_0(List<byte[]> ClientDataList)
                {
                    List<byte[]> ServerDataList = new List<byte[]>();
                    ServerDataList.Add(Encoding.Default.GetBytes("未知的协议"));

                    return ServerDataList;
                }//List<byte[]> Protocol_0(List<byte[]> ClientDataList)

                public static List<byte[]> Protocol_1(List<byte[]> ClientDataList)
                {
                    List<byte[]> ServerDataList = ClientDataList;

                    return ServerDataList;
                }//List<byte[]> Protocol_1(List<byte[]> ClientDataList)

            }//class Protocol

        }//class AsyncTCP

        public static void TCP_Start(string ServerIP, int ServerPort, int MaxListenNumber)
        {
            AsyncTCP.Listen(ServerIP, ServerPort, MaxListenNumber);
        }//void TCP_Start(string ServerIP, int ServerPort, int MaxListenNumber)

        public static void TCP()
        {
            AsyncTCP Server = new AsyncTCP();
            while (true)
            {
                Server.Accept();
                Server.Receive();
                Server.ProtocolHandler();
                Server.Send();
                Server.Disconnect();
            }
        }//void TCP()

    }//class AsyncServer

    public class AsyncClient
    {
        private class AsyncTCP
        {
            private static Socket ConnectSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);
            public List<byte[]> ClientDataList = new List<byte[]>();
            public List<byte[]> ServerDataList = new List<byte[]>();

            private static ManualResetEvent MRE_Connect = new ManualResetEvent(false);
            public static void Connect(string ServerIP, int ServerPort)
            {
                MRE_Connect.Reset();

                IPAddress ServerIPAddress = IPAddress.Parse(ServerIP);
                IPEndPoint ServerIPEndPoint = new IPEndPoint(ServerIPAddress, ServerPort);
                ConnectSocket.BeginConnect(ServerIPEndPoint, ConnectCallBack, null);

                MRE_Connect.WaitOne();
            }//Socket Connect(string ServerIP, int ServerPort)

            private static void ConnectCallBack(IAsyncResult AsyncResult)
            {
                ConnectSocket.EndConnect(AsyncResult);

                MRE_Connect.Set();
            }//void ConnectCallBack(IAsyncResult AsyncResult)

            private ManualResetEvent MRE_Send = new ManualResetEvent(false);
            private ManualResetEvent MRE_SendClientDataQuantity = new ManualResetEvent(false);
            private ManualResetEvent MRE_SendClientDataLength = new ManualResetEvent(false);
            private ManualResetEvent MRE_SendClientData = new ManualResetEvent(false);
            public void Send()
            {
                MRE_Send.Reset();

                MRE_SendClientDataQuantity.Reset();

                byte[] ClientDataQuantity = BitConverter.GetBytes(ClientDataList.Count);
                ConnectSocket.BeginSend(ClientDataQuantity, 0, ClientDataQuantity.Length, SocketFlags.None, SendClientDataQuantityCallBack, null);

                MRE_SendClientDataQuantity.WaitOne();

                MRE_Send.WaitOne();
            }//void Send()

            private void SendClientDataQuantityCallBack(IAsyncResult AsyncResult)
            {
                ConnectSocket.EndSend(AsyncResult);

                MRE_SendClientDataQuantity.Set();

                foreach (byte[] ClientData in ClientDataList)
                {
                    MRE_SendClientDataLength.Reset();

                    byte[] ClientDataLength = BitConverter.GetBytes(ClientData.Length);
                    ConnectSocket.BeginSend(ClientDataLength, 0, ClientDataLength.Length, SocketFlags.None, SendClientDataLengthCallBack, null);

                    MRE_SendClientDataLength.WaitOne();

                    MRE_SendClientData.Reset();

                    ConnectSocket.BeginSend(ClientData, 0, ClientData.Length, SocketFlags.None, SendClientDataCallBack, null);

                    MRE_SendClientData.WaitOne();
                }

                MRE_Send.Set();
            }//void SendClientDataQuantityCallBack(IAsyncResult AsyncResult)

            private void SendClientDataLengthCallBack(IAsyncResult AsyncResult)
            {
                ConnectSocket.EndSend(AsyncResult);

                MRE_SendClientDataLength.Set();
            }//void SendClientDataLengthCallBack(IAsyncResult AsyncResult)

            private void SendClientDataCallBack(IAsyncResult AsyncResult)
            {
                ConnectSocket.EndSend(AsyncResult);

                MRE_SendClientData.Set();
            }//void SendClientDataCallBack(IAsyncResult AsyncResult)

            private ManualResetEvent MRE_Receive = new ManualResetEvent(false);
            private ManualResetEvent MRE_ReceiveServerDataQuantity = new ManualResetEvent(false);
            private ManualResetEvent MRE_ReceiveServerDataLength = new ManualResetEvent(false);
            private ManualResetEvent MRE_ReceiveServerData = new ManualResetEvent(false);
            public void Receive()
            {
                MRE_Receive.Reset();

                MRE_ReceiveServerDataQuantity.Reset();

                byte[] ServerDataQuantity = new byte[4];
                object[] ReceiveServerDataQuantityState = { ServerDataQuantity };
                ConnectSocket.BeginReceive(ServerDataQuantity, 0, ServerDataQuantity.Length, SocketFlags.None, ReceiveServerDataQuantityCallBack, ReceiveServerDataQuantityState);

                MRE_ReceiveServerDataQuantity.WaitOne();

                MRE_Receive.WaitOne();
            }//void Receive()

            private void ReceiveServerDataQuantityCallBack(IAsyncResult AsyncResult)
            {
                object[] ReceiveServerDataQuantityState = (object[])AsyncResult.AsyncState;
                byte[] ServerDataQuantity = (byte[])ReceiveServerDataQuantityState[0];
                ConnectSocket.EndReceive(AsyncResult);

                MRE_ReceiveServerDataQuantity.Set();

                long CycleIndex = 0;
                while (CycleIndex < BitConverter.ToInt32(ServerDataQuantity, 0))
                {
                    MRE_ReceiveServerDataLength.Reset();

                    byte[] ServerDataLength = new byte[4];
                    ConnectSocket.BeginReceive(ServerDataLength, 0, ServerDataLength.Length, SocketFlags.None, ReceiveServerDataLengthCallBack, null);

                    MRE_ReceiveServerDataLength.WaitOne();

                    byte[] ServerData = new byte[0];
                    if (BitConverter.ToInt32(ServerDataLength, 0) > 0)
                    {
                        MRE_ReceiveServerData.Reset();

                        ServerData = new byte[BitConverter.ToInt32(ServerDataLength, 0)];
                        ConnectSocket.BeginReceive(ServerData, 0, ServerData.Length, SocketFlags.None, ReceiveServerDataCallBack, null);

                        MRE_ReceiveServerData.WaitOne();
                    }

                    ServerDataList.Add(ServerData);

                    CycleIndex = CycleIndex + 1;
                }

                MRE_Receive.Set();
            }//void ReceiveClientDataQuantityCallBack(IAsyncResult AsyncResult)

            private void ReceiveServerDataLengthCallBack(IAsyncResult AsyncResult)
            {
                ConnectSocket.EndReceive(AsyncResult);

                MRE_ReceiveServerDataLength.Set();
            }//void ReceiveClientDataLengthCallBack(IAsyncResult AsyncResult)

            private void ReceiveServerDataCallBack(IAsyncResult AsyncResult)
            {
                ConnectSocket.EndReceive(AsyncResult);

                MRE_ReceiveServerData.Set();
            }//void ReceiveClientDataCallBack(IAsyncResult AsyncResult)

            private static ManualResetEvent MRE_Disconnect = new ManualResetEvent(false);
            public static void Disconnect()
            {
                if (ConnectSocket != null)
                {
                    MRE_Disconnect.Reset();

                    ConnectSocket.BeginDisconnect(true, DisconnectCallBack, null);

                    MRE_Disconnect.WaitOne();
                }
            }//void DisconnectConnect()

            private static void DisconnectCallBack(IAsyncResult AsyncResult)
            {
                ConnectSocket.EndDisconnect(AsyncResult);

                MRE_Disconnect.Set();
            }//void DisconnectCallBack(IAsyncResult AsyncResult)

        }//class AsyncTCP

        public static void TCP_Start(string ServerIP, int ServerPort)
        {
            AsyncTCP.Connect(ServerIP, ServerPort);
        }//void TCP_Start(string ServerIP, int ServerPort)

        public static List<byte[]> TCP(List<byte[]> ClientDataList)
        {
            AsyncTCP TCP = new AsyncTCP();
            TCP.ClientDataList = ClientDataList;
            TCP.Send();
            TCP.Receive();
            List<byte[]> ServerDataList = TCP.ServerDataList;

            return ServerDataList;
        }//List<byte[]> TCP(List<byte[]> ClientDataList)

        public static void TCP_End()
        {
            AsyncTCP.Disconnect();
        }//void TCP_End()

    }//class AsyncClient

}//namespace SinDynasty

示例声明:1、表示示例中的代码为了便于理解,对于异步的代码个人感觉写得有点不太好,但还是就那样吧;2、无论是 List ClientDataList 还是 List ServerDataList,其第一个元素表示的是一个 byte[] 类型的协议编号,否则会出错
示例流程:(对于如何建立服务端与客户端的连接,这里先暂且略过,这儿先讲数据传输)客户端获取 List ClientDataList 中所含元素的数量 ClientDataQuantity,并发送给服务端---->服务端接收 ClientDataQuantity,让服务端知道接下来要接收多少个数据---->客户端遍历 List ClientDataList 中的元素,并循环打包成 前 4 字节表示数据大小 + 后面的字节表示数据 的格式,并发送---->服务端根据之前客户端发来的 ClientDataQuantity,循环接收客户端发来的数据包,接收单个数据包的方法是先接收 4 字节的数据获得接下来所需接收的数据大小,然后接收数据---->服务端每接收一个 byte[] 的数据,便将该数据添加到 List ClientDataList---->当服务端获得完整的 List ClientDataList 后,便解析 List ClientDataList 中的第一个元素,获得协议编号---->服务端根据协议编号,利用反射的方法,获得所需调用的协议方法,并使用 List ClientDataList 和 MethodInfo.Invoke() 的方法获得 List ServerDataList---->服务端安装之前客户端发送数据的方式,发送 List ServerDataList---->客户端也按照之前服务端接收数据的方式,接收 List ServerDataList

示例语义:其就是 List ClientDataList 和 List ServerDataList 中所含的数据及其排序,外加上在发送数据时最开始发送的 ClientDataQuantity 及 ServerDataQuantity
示例语法:其就是在打包 List ClientDataList 和 List ServerDataList 时所采用的格式,前 4 字节表示数据大小 + 后面的字节表示数据
示例时序:短连接,服务端先调用 TCP_Start 的方法,建立用于监听的 Socket ListenSocket,并绑定端口,及开始监听---->客户端创建用于连接的 Socket ConnectSocket,并请求连接---->服务端开始接受客户端的连接请求---->服务端与客户端相互传输数据---->服务端调用 Disconnect() 的方法关闭与该客户端的连接---->客户端调用 TCP_End 的方法关闭与服务端的连接

恒温 将本帖设为了精华贴 02月26日 09:22

解惑,继续再接再厉,还能给大家答疑

感谢科普!期待更新!

萌兽加油~~,棒棒的

accept 函数返回-1 和执行成功的,可以单独讲下哦。

陈子昂 回复

很实用的协议知识,对服务端协议测试起到了很好的科普作用,感谢!

匿名 #10 · 2017年03月03日

正准备入坑服务器测试~解惑

感谢作者用心的扫盲,朴实的技术分享。
补充一点我的理解:
Socket 就是客户端与服务器之间通信用的套接字。
主流编程语言都为网络编程提供了 Socket API,基于此可以进行协议开发。

记得更新打赏二维码,你已经错过本轮的社区打赏

我没想过要打赏啊╮(╯▽╰)╭

这是之前在 TesterHome 游戏测试群里,jiazurongyu 说要搞个手册,然后用了我https://testerhome.com/topics/6956这文章,我感觉那文章不好,就重新写了这篇文章

厉害了,同时会 C# 和 Java 的啊。

最近发现我这篇文章里对 Socket 的理解有误,具体的不说了,那是关于底层的东西,关键知道 Socket 不是协议 就行了

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册