网络游戏《丛林战争》开发与学习之(一):网络编程的基础知识

《丛林战争》是一款完整的网络游戏案例,运用U3D开发客户端,Socket开发服务端,其中涉及到了网络编程、数据库和Unity的功能实现,之前通过U3D开发了一个单机游戏《黑暗之光》,并没有涉及网络编程的知识,通过《丛林战争》这个完整的游戏,系统性地学习网络编程,并进一步学习利用U3D开发游戏。

本篇内容是网络编程的基础知识,主要内容如下:

  • 介绍TCP/IP的基本概念以及基础TCP协议
  • 实现服务器端与客户端的同步收发
  • 实现服务器端与客户端的异步收发

1. TCP/IP基本概念

下图是一个网络的简化图,可以看出IP的作用是在复杂的网络环境中将数据包发给最终的目标地址,本节主要介绍IP、端口号和TCP协议。

1.1 IP

IP分为局域网IP和公网IP,每台机都有这两个IP。当一个路由器连接多个主机,相当于路由器与这些主机组成了一个局域网,路由器会给每个主机分配一个局域网IP,可以在Win+R > cmd,小黑窗中输入ipconfig查到,公网IP则可以通过百度查询到。

1.2 端口号

主机之间通过路由器进行通信,以主机B与主机D通信为例,B通过ip地址找到了主机D,连接建立之后,考虑到进行通信最终是软件之间的交互,因此需要搞清是与什么软件进行通信。假设D中有QQ、微信、绝地求生等软件,需要为每个软件分配一个端口号。(即ip找机器,端口号找软件)

下图就是一个例子,IP数据172.23.12.14找到了主机A,A中各个端口号对应不同应用(图中以服务端为例),需要通过独有的端口号找到对应软件。后期也会学习如何向系统申请端口号。

一般知名端口号在0~1023之间,而我们经常使用的自定义/动态分配的端口号则一般在1024~65535之间。详细参考百度百科。https://baike.baidu.com/item/%E7%AB%AF%E5%8F%A3%E5%8F%B7/10883658

1.3 TCP协议(三次握手与四次挥手)

三次握手与四次挥手的详解可以参照该博客:https://blog.csdn.net/sssnmnmjmf/article/details/68486261

在这里大致解释一下:(主机A代表张三,B代表李四)

三次握手:

    张三:李四你在吗?(请求连接)

    李四:张三我在的(确认应答)

    张三:好的我要发数据了(对李四的确认应答)

四次挥手:

    张三:李四我没事了,挂电话了(请求切断)

    李四:好的,知道你没事了(确认应答)

    李四:那就这样,挂电话吧(请求切断)

    张三:那我挂了(确认应答)

2 利用TCP协议实现服务端与客户端的通信

利用C#服务器控制应用程序实现简单的通信,步骤如下图,图中所示即为服务端与客户端的连接步骤,TCP连接都是基于这个规则。

2.1 服务端与客户端的同步收发

服务端代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
namespace TCPServer
{
    class Program
    {
        static void Main(string[] args)
        {
            Socket socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);  //第一个参数表示时候用ipv4,第二个Stream表示TCP传输用到的数据流,如果是UDP协议可以用Dgram报文
            //IPAddress xxx.xx.xx.xx IPEndPoint xxx.xx.xx.x:port 需要用到using System.Net;
            IPAddress ipAddr = IPAddress.Parse("127.0.0.1");    //服务器端申请ip及端口号
            IPEndPoint ipEndPoint = new IPEndPoint(ipAddr, 1200);
            socketServer.Bind(ipEndPoint);  //进行绑定
            socketServer.Listen(10);    //最多处理10个连接的队列
            Socket socketClient = socketServer.Accept();  // 接收客户端连接,之后通过socketClient进行对客户端数据的收发

            //发送给客户端
            string sendMsg = "hello,客户端";
            byte[] sendData = Encoding.UTF8.GetBytes(sendMsg);  //字符串转为bype数组发送,用到using System.Text;
            socketClient.Send(sendData);

            //从客户端接收
            byte[] recvData = new byte[1024];   //分配1024字节大小的空间存储
            int count = socketClient.Receive(recvData); //接收到的字节大小
            string recvMsg = Encoding.UTF8.GetString(recvData, 0, count);  //数组转为字符串,转换0~count长度,由于recvData未填满,直接转换recvData会出错
            Console.WriteLine(recvMsg);
            socketClient.Close();
            socketServer.Close();
        }
    }
}

客户端代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
namespace TCPClient
{
    class Program
    {
        static void Main(string[] args)
        {
            Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            clientSocket.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1200));

            //从服务端接收数据
            byte[] dataRecv = new byte[1024];   
            int count = clientSocket.Receive(dataRecv);
            string msgRecv = Encoding.UTF8.GetString(dataRecv, 0, count);
            Console.WriteLine(msgRecv);
            
            //向服务端发送数据
            string msgSend = Console.ReadLine();
            byte[] dataSend = Encoding.UTF8.GetBytes(msgSend);  //字符串转为bype数组发送,用到using System.Text;
            clientSocket.Send(dataSend);
            Console.ReadKey();
            clientSocket.Close();
        }
    }
}

结果如下。

上述方法是一个同步发送与接收的例子,当服务器处于接收状态时,无法发送数据给客户端,反之亦然。因此需要一种异步的处理方法。

2.2 服务端实现异步接收

在服务器端添加异步接收功能,将之前的代码修改为:

static byte[] dataBuffer = new byte[1024];
        static void StartAsync()
        {
            Socket socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);  //第一个参数表示时候用ipv4,第二个Stream表示TCP传输用到的数据流,如果是UDP协议可以用Dgram报文
            //IPAddress xxx.xx.xx.xx IPEndPoint xxx.xx.xx.x:port 需要用到using System.Net;
            IPAddress ipAddr = IPAddress.Parse("127.0.0.1");    //服务器端申请ip及端口号
            IPEndPoint ipEndPoint = new IPEndPoint(ipAddr, 1200);
            socketServer.Bind(ipEndPoint);  //进行绑定
            socketServer.Listen(10);    //最多处理10个连接的队列
            Socket socketClient = socketServer.Accept();  // 接收客户端连接,之后通过socketClient进行对客户端数据的收发
            //发送给客户端
            string sendMsg = "hello,客户端";
            byte[] sendData = Encoding.UTF8.GetBytes(sendMsg);  //字符串转为bype数组发送,用到using System.Text;
            socketClient.Send(sendData);
            //从客户端接收
            socketClient.BeginReceive(dataBuffer, 0, 1024, SocketFlags.None, ReceiveCallBack, socketClient);   //开始监听客户端数据,当接收到数据时调用RecvCallBack,实现异步接收
            Console.ReadKey();
            socketClient.Close();
            socketServer.Close();
        }
        static void ReceiveCallBack(IAsyncResult ar)
        {
            Socket clientSocket = ar.AsyncState as Socket;
            int count = clientSocket.EndReceive(ar);
            string msg = Encoding.UTF8.GetString(dataBuffer, 0, count);
            Console.WriteLine("接收到的消息为:" + msg);
            clientSocket.BeginReceive(dataBuffer, 0, 1024, SocketFlags.None, ReceiveCallBack, clientSocket);    //接收完一条消息后回掉自身,继续接收
        }

在客户端发送数据代码前加一个while(true)以达到不断发送数据的目的,结果如下。

2.3 服务端接收多个客户端的异步数据

若要使服务端同时接收多个客户端的数据,服务端实现异步收发需要调用AcceptCallBack

socketServer.BeginAccept(AcceptCallBack, socketServer);  

服务端代码如下

     static void StartAsyncAcceptAndRecv()
        {
            Socket socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);  //第一个参数表示时候用ipv4,第二个Stream表示TCP传输用到的数据流,如果是UDP协议可以用Dgram报文
            IPAddress ipAddr = IPAddress.Parse("127.0.0.1");    //服务器端申请ip及端口号
            IPEndPoint ipEndPoint = new IPEndPoint(ipAddr, 1200);
            socketServer.Bind(ipEndPoint);  //进行绑定
            socketServer.Listen(10);    //最多处理10个连接的队列
            //Socket socketClient = socketServer.Accept();  // 接收客户端连接,之后通过socketClient进行对客户端数据的收发
            socketServer.BeginAccept(AcceptCallBack, socketServer);
        }

客户端不变,此时服务端可以接收多个客户端的数据,如下图所示。

工程源码下载地址:https://download.csdn.net/download/s1314_jhc/10519603(下载分不够可以私聊我发代码)

猜你喜欢

转载自blog.csdn.net/s1314_jhc/article/details/80914044