《丛林战争》是一款完整的网络游戏案例,运用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(下载分不够可以私聊我发代码)