备注:string ServerIP,int ServerPort,long ProtocolNumber,byte[] ClientData 这四个值都是需要提前设置已知的,我们只需将其传入即可,因此我不会写有关他们的申明,我在此只做解释下他们的含义
string ServerIP:表示的是服务器的 IP 地址;

int ServerPort:表示的是服务端的端口号;

long ProtocolNumber:表示的是协议号;

byte[] ClientData:表示的是需要客户端需要发给服务端的数据(由于在网络通讯中所有数据都是以字节为单位发送的,因此在发送前我们需要将其转化为 byte[] 的类型);

一、服务端:创建服务端监听 Socket ListenSocket,并开始监听客户端

IPAddress ServerIPAddress = IPAddress.Parse(ServerIP);
IPEndPoint ServerIPEndPoint = new IPEndPoint(ServerIPAddress, ServerPort);
Socket ListenSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);
ListenSocket.Bind(ServerIPEndPoint);
ListenSocket.Listen(0);

任何协议都是通过套接口完成传输的,而在 C# .Net 中,微软为我们封装好了一套有关套接字的命名空间 System.Net.Sockets,我们只需要引用这个控件就能简单地完成网络通讯的编程。

因为网络通讯都是以套接字套接口为基础的,因此我们需要创建一个 Socket 对象,在 System.Net.Sockets 创建 Socket 对象的方法有 3 种,一般使用的是 Socket(AddressFamily, SocketType, ProtocolType) 这个方法,里面的 3 个参数分别是指:

1、AddressFamily:是一个枚举,指的是服务端地址的类型,对于 TCP 来说,我们提供的服务端地址一般是 IPV4 地址或 IPV6 地址,他们分别对应的是 InterNetwork 和 InterNetworkV6

2、SocketType:是一个枚举,指的是套接字的类型,一般常用的有 Stream 和 Dgram,TCP 是使用流式套接字来实现传输的,因此我们在这里使用的是 Stream,而 Dgram 是指数据报套接字,一般用于 UDP

3、ProtocolType:是一个枚举,指的是协议类型,这里我们要用的是 Tcp,当然这个枚举里是没有 HTTP 的,由于 HTTP 是基于 TCP 的,因此如果要发 HTTP,我们需要使用的还是 Tcp,对于如何使用 Socket 来发基于 TCP 的 HTTP,我想等以后再另写一篇

当然创建 Socket 对象还可以使用 Socket(SocketType, ProtocolType) 这个方法,在这里我就使用这个方法创建了一个服务端用于监听客户端的 Socket ListenSocket,这样无论我提供的地址是 IPV4 地址还是 IPV6 地址都不影响。

在创建完服务端用于监听客户端的 Socket ListenSocket 后,我们需要将 Socket ListenSocket 与服务器地址相绑定,在这里我们使用的方法是 Socket.Bind(EndPoint) 方法,而要获得这个方法所需的参数 EndPoint,我们需要先使用我们预先所提供的服务端地址 string ServerIP,利用 IPAddress.Parse(String) 方法,创建一个服务端的 IP 地址对象 IPAddress ServerIPAddress,然后使用 IPEndPoint(IPAddress, Int32) 的方法,将服务端的 IP 地址对象 IPAddress ServerIPAddress 和服务端的端口号 int ServerPort 代入,获得服务端的终结点 IPEndPoint ServerIPEndPoint,由于 IPEndPoint 继承自 EndPoint,我们只需 ListenSocket.Bind(ServerIPEndPoint) 就能完成服务端的地址绑定的工作。

最后,我们使用 Socket.Listen(Int32) 的方法开启监听即可,其中该方法所需要传入的 int 值是指要排队进行验收的最大传入连接数,在这里,我设成了 0。当启动这个方法的时候,服务端运行到这一步的时候,就会卡住,直至收到客户端连接请求。

二、客户端:创建用于连接服务器的 Socket ConnectSocket,并连接服务器

IPAddress ServerIPAddress = IPAddress.Parse(ServerIP);
IPEndPoint ServerIPEndPoint = new IPEndPoint(ServerIPAddress, ServerPort);
Socket ConnectSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);
ConnectSocket.Connect(ServerIPEndPoint);

首先我们需要创建一个客户端连接服务端的 Socket 对象,当然对于创建客户端连接服务端的 Socket 对象的方法,我就不再解释,上面已经说得很清楚了,之后我们再使用 Socket.Connect(EndPoint) 这个方法去连接服务器,当这个方法所需的参数是指服务端的终结点,创建这个服务端终结点的方法在上面也已经说得很清楚了,我也不在做过多地解释了。

三、服务端:收到客户端的连接后,创建一个新的用于连接客户端的 Socket ConnectSocket

Socket ConnectSocket = ListenSocket.Accept();

服务端在监听到客户端的连接后,我们必须使用 Socket.Accept() 这个方法来创建一个服务端用于连接客户端的 Socket 对象 Socket ConnectSocket。记住监听的 Socket 对象只能用于监听,而连接的客户端只能用于连接和传输。

四、客户端:包装需要发送的数据 byte[] ClientData,并发送

//包装数据大小
byte[] ClientDataLength = BitConverter.GetBytes((long)ClientData.Length);
ClientData = ClientDataLength.Concat(ClientData).ToArray();
//包装协议编号
byte[] ProtocolNumber_byte = BitConverter.GetBytes(ProtocolNumber);
ClientData = ProtocolNumber_byte.Concat(ClientData).ToArray();

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

  在传输时,为了让服务端知道客户端发来的数据大小,以便于接收,我们需要对数据 byte[] ClientData 进行相应的包装,我们首先通过 ClientData.Length 来获得数据 byte[] ClientData 的大小,BitConverter.GetBytes(Int64) 的方法,将该数据包的大小转换为一个大小为 8 个字节的 byte 数组 byte[] ClientDataLength,当然在这里我们也可以用 BitConverter.GetBytes(Int32),将其转化为一个 4 字节的 byte 数据 byte[] ClientDataLength。

之后我们使用 byte[] B.Concat(byte[] b).ToArray(),将 byte[] ClientDataLength 加到 byte[] ClientData 的前面,形成一个新的数据包 byte[] ClientData。

另外由于一个游戏或者一个软件不可能只有一个协议,因此我们还需要以相同的方法将协议编号 long ProtocolNumber 也包装进 byte[] ClientData 中,在这里我将其包装成了 一个前 8 个字节代表协议编号,在后面的 8 个字节代表数据包大小,剩下的后面的字节代表客户端发给服务端的数据 的 byte[] ClientData。

然后我们使用 Socket.Send(Byte[], Int32, Int32, SocketFlags) 的方法,将该数据发送给服务端。其 4 个参数的含义分别是指:所需发送的数据对象 Byte[]、发送该数据对象 Byte[] 的起始位置、所需发送的字节数、SocketFlags 枚举(其指的是套接字的收发行为,在这里我们只需使用 None 即可)。

五、服务端:接收从客户端发送来的数据 byte[] ClientData,并解析

                    //接收协议编号
                    byte[] ProtocolNumber_byte = new byte[8];
                    ConnectSocket.Receive(ProtocolNumber_byte, 0, ProtocolNumber_byte.Length, SocketFlags.None);
                    long ProtocolNumber = BitConverter.ToInt64(ProtocolNumber_byte, 0);
                    //接收数据大小
                    byte[] ClientDataLength = new byte[8];
                    ConnectSocket.Receive(ClientDataLength, 0, ClientDataLength.Length, SocketFlags.None);
                    long ClientDataSize = BitConverter.ToInt64(ClientDataLength, 0);

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

由于客户端之前对数据进行了相应的包装,因此我们也需要对其进行相应的解析和接收。

我们知道客户端发来的数据前 8 位代表的是协议编号,因此在接收前我们先使用 new byte[数字] 的方法,创建一个占 8 个字节的接收数据的对象 byte[] ProtocolNumber_byte,
然后我们使用 Socket.Receive(Byte[], Int32, Int32, SocketFlags) 的方法来接收数据对象,该方法中的 4 个参数分别是指接收数据的对象、接收数据存放的起始位置,接收数据的大小、SocketFlags 枚举(其指的是套接字的收发行为,在这里我们只需使用 None 即可)。之后我们使用 BitConverter.ToInt64(byte[]) 的方法将 byte[] ProtocolNumber_byte 转化为 long ProtocolNumber 就能获取到协议编号。

  数据接收后,接收的数据便不会在 Socket 中,比如客户端向服务端发送了 4 个字节的数据,我服务端先接收了前 2 个字节的数据,当服务端再次去接收 2 个字节的数据时,服务端接收到的是后 2 个字节的数据。

因此我们无需做其他的操作,只需再次创建 8 个字节的对象 byte[] ClientDataLength,并以相同的方法接收转化,就能获得客户端发给服务端的数据大小 long ClientDataSize。

然后我们通过获得的数据大小 long ClientDataSize,去创建一个相应字节大小的接收对象 byte[] ClientData 去接收客户端发给服务端真正的数据,当然在接收前我们需要判断一下 long ClientDataSize 的大小,如果 long ClientDataSize 等于 0,我们就直接将 byte[] ClientData 设为 new byte[0] 即可。

六、服务端:根据解析出来的协议编号,使用反射的方法,将接收的数据 byte[] ClientData 发给相应的协议处理,并获取服务端发往客户端的数据 byte[] ServerData

private class Protocol
{
    public static byte[] Protocol_0(byte[] ClientData)
    {
        byte[] ServerData = Encoding.Default.GetBytes("未知的协议");
        return ServerData;
    }//byte[] Protocol_0(byte[] ClientData)

    public static byte[] Protocol_1(byte[] ClientData)
    {
        byte[] ServerData = ClientData;
        return ServerData;
    }//byte[] Protocol_1(byte[] ClientData)

}//class Protocol

我们要根据协议编号 long ProtocolNumber,将服务端接收到的数据 byte[] ClientData 传入相应的协议进行处理,在这里我用的是反射的方法,反射一个专门存放协议的类 class Protocol,从中寻找相应的协议方法,之后调用该协议方法,获取服务端需要返回给客户端的数据 byte[] ServerData,为此在该类中所有的协议方法我们都必须统一命名,在这里我的命名规则就是"Protocol_" + 协议编号 ProtocolNumber,另外所有协议方法都需要一个 byte[] ClientData 作为参数,并且都会返回一个 byte[] ServerData 值。

为了防止客户端发错误协议的协议编号,我们需要设定一个针对未知协议的协议,即我这儿的 0 号协议 Protocol_0,其将通过后续的步骤向客户端返回一个"未知的协议"的字符串,其中 Encoding.GetBytes(string) 是一种将 string 字符串按特定的编码转换为 byte[] 的方法,Default 指的是一种 ANSI 的编码方式。

string ProtocolName = "Protocol_" + ProtocolNumber;
Protocol P = new Protocol();
Type T = P.GetType();
MethodInfo ProtocolMethodInfo = T.GetMethod(ProtocolName);
if (ProtocolMethodInfo == null)
{
    ProtocolMethodInfo = T.GetMethod("Protocol_0");
}

  使用 Object.GetType() 的方法可以获取一个实例的类型,由于 Object 是所有类型的基类,因此我们可以先创建一个 Protocol 类型的实例对象 Protocol P,然后使用 Protocol.GetType() 来获取该实例的类型 Type T,接着根据我们的命名规则去确定我们所需查找的协议方法名 string ProtocolName,之后使用 Type.GetMethod(string) 的方法,将我们所需查找的协议方法名 string ProtocolName 代入,获取到相应的协议方法对象 MethodInfo ProtocolMethodInfo,备注:由于 Type.GetMethod(string) 这个方法只能查找带有 public 的公共方法,因此所有的协议方法都需要在前面添加 public,当然这个类可以不带 public。

最后我们还需判断一次获取到的协议方法对象 MethodInfo ProtocolMethodInfo 是否为 null,因为有可能不存在该协议,如果为 null,此时我们就只需要将协议方法对象 MethodInfo ProtocolMethodInfo 设为我们预先设定好的 0 号协议即可。

object[] ProtocolParameter = { ClientData };
object ServerData_object = ProtocolMethodInfo.Invoke(null, ProtocolParameter);
byte[] ServerData = new byte[0];
if (ServerData_object != null)
{
    ServerData = (byte[])ServerData_object;
}

  最后我们需要使用 MethodInfo.Invoke(Object, Object[]) 来调用我们所查找到的协议方法,该方法的第一参数是调用该方法所需的实例对象,当然由于我在此类中写的都是带有 static 的静态方法,因此只需将其设为 null 即可,其第二参数是调用该方法所需的参数集合,由于此类中所有的方法都只需一个 byte[] ClientData 作为参数,因此我们只需这样 object[] ProtocolParameter = { ClientData },然后把 object[] ProtocolParameter 代入即可,最后该方法具有一个返回值,其返回的数据是调用该方法后返回的数据 object ServerData_object(对于 void 类型的函数其返回的是 null),由于 Protocol 类中,所有的方法返回的都是 byte[] 类型的数据,所以我们只需将 object ServerData_object 强制转换成 byte[] 的类型就能获得 byte[] ServerData,当然在强制转换前还是要先判断一下 object ServerData_object 是否为 null,如果为 null 则不转换,直接将 byte[] ServerData 设为 new byte[0] 即可。

七、服务端:包装需要发送的数据 byte[] ServerData,并发送

//包装数据大小
byte[] ServerDataLength = BitConverter.GetBytes((long)ServerData.Length);
ServerData = ServerDataLength.Concat(ServerData).ToArray();

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

  此方法和之前客户端发给服务端数据的方法相同,因此我不再做过多的说明

八、客户端:接收从服务端发来的数据 byte[] ServerData

                    //接收数据大小
                    byte[] ServerDataLength = new byte[8];
                    ConnectSocket.Receive(ServerDataLength, 0, ServerDataLength.Length, SocketFlags.None);
                    long ServerDataSize = BitConverter.ToInt64(ServerDataLength, 0);

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

此方法和之前服务端接收客户端数据的方法相同,因此我不再做过多的说明

九、客户端:关闭用于连接服务端的 Socket ConnectSocket

if (ConnectSocket != null)
{
    ConnectSocket.Close();
}

最后在客户端我们需要使用 Socket.Close() 的方法来关闭客户端与服务端连接 ConnectSocket。

十、服务端:关闭用于连接的客户端的 Socket ConnectSocket

if (ConnectSocket != null)
{
    ConnectSocket.Close();
}

最后在服务端我们需要使用 Socket.Close() 的方法来关闭客户端与服务端连接 ConnectSocket。

客户端完整源代码:

            public static byte[] TCP(string ServerIP, int ServerPort, long ProtocolNumber, byte[] ClientData)
            {
                try
                {
                    IPAddress ServerIPAddress = IPAddress.Parse(ServerIP);
                    IPEndPoint ServerIPEndPoint = new IPEndPoint(ServerIPAddress, ServerPort);
                    Socket ConnectSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);
                    ConnectSocket.Connect(ServerIPEndPoint);
                    //包装数据大小
                    byte[] ClientDataLength = BitConverter.GetBytes((long)ClientData.Length);
                    ClientData = ClientDataLength.Concat(ClientData).ToArray();
                    //包装协议编号
                    byte[] ProtocolNumber_byte = BitConverter.GetBytes(ProtocolNumber);
                    ClientData = ProtocolNumber_byte.Concat(ClientData).ToArray();

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

                    //接收数据大小
                    byte[] ServerDataLength = new byte[8];
                    ConnectSocket.Receive(ServerDataLength, 0, ServerDataLength.Length, SocketFlags.None);
                    long ServerDataSize = BitConverter.ToInt64(ServerDataLength, 0);

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

                    if (ConnectSocket != null)
                    {
                        ConnectSocket.Close();
                    }

                    return ServerData;
                }
                catch (Exception Ex)
                {
                    new Exception(Ex.Message);
                    return null;
                }
            }//byte[] TCP(string ServerIP, int ServerPort, long ProtocolNumber, byte[] ClientData)

服务端完整源代码:

    public static void TCP(string ServerIP, int ServerPort)
    {
        try
        {
            IPAddress ServerIPAddress = IPAddress.Parse(ServerIP);
            IPEndPoint ServerIPEndPoint = new IPEndPoint(ServerIPAddress, ServerPort);
            Socket ListenSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);
            ListenSocket.Bind(ServerIPEndPoint);
            ListenSocket.Listen(0);
            Socket ConnectSocket = ListenSocket.Accept();

            //接收协议编号
            byte[] ProtocolNumber_byte = new byte[8];
            ConnectSocket.Receive(ProtocolNumber_byte, 0, ProtocolNumber_byte.Length, SocketFlags.None);
            long ProtocolNumber = BitConverter.ToInt64(ProtocolNumber_byte, 0);
            //接收数据大小
            byte[] ClientDataLength = new byte[8];
            ConnectSocket.Receive(ClientDataLength, 0, ClientDataLength.Length, SocketFlags.None);
            long ClientDataSize = BitConverter.ToInt64(ClientDataLength, 0);

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

            string ProtocolName = "Protocol_" + ProtocolNumber;
            Protocol P = new Protocol();
            Type T = P.GetType();
            MethodInfo ProtocolMethodInfo = T.GetMethod(ProtocolName);
            if (ProtocolMethodInfo == null)
            {
                ProtocolMethodInfo = T.GetMethod("Protocol_0");
            }

            object[] ProtocolParameter = { ClientData };
            object ServerData_object = ProtocolMethodInfo.Invoke(null, ProtocolParameter);
            byte[] ServerData = new byte[0];
            if (ServerData_object != null)
            {
                ServerData = (byte[])ServerData_object;
            }

            //包装数据大小
            byte[] ServerDataLength = BitConverter.GetBytes((long)ServerData.Length);
            ServerData = ServerDataLength.Concat(ServerData).ToArray();

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

            if (ConnectSocket != null)
            {
                ConnectSocket.Close();
            }
        }
        catch (Exception Ex)
        {
            new Exception(Ex.Message);
        }
    }//void TCP(string ServerIP,int ServerPort)

public class Protocol
{
    public static byte[] Protocol_0(byte[] ClientData)
    {
        byte[] ServerData = Encoding.Default.GetBytes("未知的协议");
        return ServerData;
    }//byte[] Protocol_0(byte[] ClientData)

    public static byte[] Protocol_1(byte[] ClientData)
    {
        byte[] ServerData = ClientData;
        return ServerData;
    }//byte[] Protocol_1(byte[] ClientData)

}//class Protocol


↙↙↙阅读原文可查看相关链接,并与作者交流