学习书籍《Unity3d网络游戏实战》 罗培羽著 机械工业出版社
本文是作者在学习过程中遇到的认为值得记录的点,因此引用的代码等资源基本出资罗培羽老师的书籍
以下为个人的学习简记,与诸君共论 由于U3D的官方案例Tank Tutorial中对坦克的基本操作已经有了详尽的描述,因此本文着重于面板系统、服务端基本网络框架和客户端基本网络框架的搭建
如有疑问或者描述错误的地方,欢迎各位提出或修正,讨论交流是获取和巩固知识的重要途径之一!
Socket套接字
Socket是支持TCP/IP协议网络通信的基本操作单元,可以将套接字看作不同主机间的进程双向通信的端点。举个栗子,小时候玩过的传声筒(如图),传声筒的一段的杯子就类似于Socket,两个人之间要进行通信首先都要有一个杯子(Socket),然后两个杯子之间要有一条线(TCP连接),如果不熟悉TCP连接可以翻看上一节内容。
Socket的通信基本流程:
1. Server需要创建一个Socket,然后绑定一个端口(端口的基本概念请自行搜索)然后进行监听;
2. Client通过Connect方法连接Server,Server的Socket通过Accept接收Client连接请求,在connect-accept过程中将会进行三次握手;
3. 连接建立后,两端可以相互发送数据,直到一方发出关闭连接请求,四次挥手后,关闭连接。
在整个流程中我们需要做的就是实现逻辑,具体的三次握手、四次挥手、数据收发的确认将会由操作系统完成。
同步Socket
同步Socket能够实现Server和Client两端之间的通信,下面给出简单的实例:
在这里,Server和Client都使用Console Project实现,如果想更直观,可以自行制作GUI界面的程序。
Server:
using System;
using System.Net; // Remember add Net & Net.Sockets namespace;
using System.Net.Sockets;
namespace Server
{
class MainClass
{
public static void Main (string[] args)
{
// Console.WriteLine ("Hello World!");
Socket servSk = new Socket (
AddressFamily.InterNetwork, // Address for IPv4
SocketType.Stream, // DataType for stream
ProtocolType.Tcp); // Tcp Protocol
string servId = "127.0.0.1";
int servPort = 2333;
IPAddress ipAdr = IPAddress.Parse (servId);
IPEndPoint ipEp = new IPEndPoint (ipAdr, servPort);
servSk.Bind (ipEp); // Bind
servSk.Listen (0); // Listen
Console.WriteLine ("[Program.Main] Server start.");
while (true) {
Socket connSk = servSk.Accept (); // Accept
Console.WriteLine ("[Program.Main] Accept connect request from " + connSk.RemoteEndPoint.ToString ());
byte[] readBuff = new byte[1024]; // Receive
int count = connSk.Receive (readBuff);
string readStr = System.Text.Encoding.UTF8.GetString (readBuff, 0, count);
Console.WriteLine ("[Program.Main] Receive message: " + readStr + " from " + connSk.RemoteEndPoint.ToString ());
byte[] sendBuff = new byte[1024]; // Send
sendBuff = System.Text.Encoding.UTF8.GetBytes ("Server had receive your message: " + readStr);
connSk.Send (sendBuff);
}
}
}
}
Client:
using System;
using System.Net;
using System.Net.Sockets;
namespace Client
{
class Program
{
public static void Main (string[] args)
{
//Console.WriteLine ("Hello World!");
Socket clientSk = new Socket (
AddressFamily.InterNetwork,
SocketType.Stream,
ProtocolType.Tcp);
clientSk.Connect ("127.0.0.1", 2333); // Connect
Console.WriteLine ("[Program.Main] Connect to server: 127.0.0.1:2333 success.");
byte[] sendBuff = new byte[1024]; // Send
string sendStr = "Hello Server!";
sendBuff = System.Text.Encoding.UTF8.GetBytes (sendStr);
clientSk.Send (sendBuff);
byte[] readBuff = new byte[1024]; // Receive
int count = clientSk.Receive (readBuff);
string readStr = System.Text.Encoding.UTF8.GetString (readBuff, 0, count);
Console.WriteLine (readStr);
Console.ReadLine ();
}
}
}
几个要点:
1. AddressFamily.InterNetwork 指的是IPv4地址;
2. SocketType.Stream 指的是基于连接的字节流,这是游戏开发中最常用的类型;
3. ProtocolType.Tcp 顾名思义指的就是TCP协议啦;
更详细的参数可以参考C#官方文档
异步Socket
同步Socket适用于单个C/S之间的通信,但通常我们需要Server与多个Client之间进行通信,我们可以通过创建Conn类来管理各个与Client连接的Socket,在Server中初始化一个连接池,每当有新的Client接入时,遍激活连接池里的一个连接进行管理,由于Server在一开始就初始化了连接池,因此会占用更多内存,这是个用空间换时间的策略。
Conn:
using System;
using System.Net;
using System.Net.Sockets;
namespace Server
{
public class Conn
{
private const int BUFFER_SIZE = 1024;
public Socket socket; // The socket connect to client.
public bool isUse = false; // mark this conn's type
public byte[] readBuff = new byte[BUFFER_SIZE]; // use to store data receive from client/
public int buffCount = 0; // the total length of data which store in readBuff
public Conn()
{
readBuff = new byte[BUFFER_SIZE];
}
public void Init(Socket socket) // Init conn
{
this.socket = socket;
isUse = true;
buffCount = 0;
}
public int BufferRemain() // Return the useful length of readBuff
{
return BUFFER_SIZE - buffCount;
}
public string GetAddress() // Return the client address.
{
return socket.RemoteEndPoint.ToString ();
}
public void Close() // Use to close conn
{
if (isUse == false)
return;
Console.WriteLine ("[Conn.Close] Disconnect with " + GetAddress ());
socket.Close ();
isUse = false;
}
}
}
Server:
using System;
using System.Net;
using System.Net.Sockets;
namespace Server
{
public class Server
{
public Socket servSk; // Server socket
public Conn[] conns; // Connect pool
public int maxCount = 50; // Max client count that server accept.
int NewIndex() // get a conns index
{
if (conns == null)
return -1;
for (int i = 0; i < maxCount; i++) {
Conn conn = conns [i];
if (conn == null) {
conn = new Conn ();
return i;
}
if (conn.isUse == false) {
return i;
}
}
return -1;
}
public void Start(string ip, int port)
{
conns = new Conn[maxCount]; // Init conn
for (int i = 0; i < maxCount; i++) {
conns [i] = new Conn ();
}
servSk = new Socket ( // Socket
AddressFamily.InterNetwork,
SocketType.Stream,
ProtocolType.Tcp);
IPAddress ipAdr = IPAddress.Parse (ip);
IPEndPoint ipEp = new IPEndPoint (ipAdr, port);
servSk.Bind (ipEp); // Bind
servSk.Listen (maxCount); // Listen
servSk.BeginAccept ( // Begin Accept
AcceptCb,
null);
Console.WriteLine ("[Server.Start] Server start.");
}
void AcceptCb(IAsyncResult ar)
{
try {
Socket connSk = servSk.EndAccept(ar); // End Accept
int index = NewIndex();
if(index < 0)
{
Console.WriteLine("[Server.AcceptCb] conns is full.");
connSk.Close();
} else {
Conn conn = conns[index];
conn.Init(connSk);
conn.socket.BeginReceive( // Begin Receive
conn.readBuff,
conn.buffCount,
conn.BufferRemain(),
SocketFlags.None,
ReceiveCb,
conn);
}
servSk.BeginAccept( // Begin listen next new client's connect request.
AcceptCb,
null);
} catch (Exception ex) {
Console.WriteLine ("[Server.AcceptCb] Accept connect request fail. " + ex.Message);
}
}
void ReceiveCb(IAsyncResult ar)
{
Conn conn = (Conn)ar.AsyncState;
try {
int count = conn.socket.EndReceive(ar); // End Receive
if(count > 0)
{
string recvStr = System.Text.Encoding.UTF8.GetString(conn.readBuff,0,count);
Console.WriteLine("[Server.ReceiveCb] Receive message: " + recvStr + " from " + conn.GetAddress());
recvStr = conn.GetAddress() + ": " + recvStr;
byte[] sendBuff = System.Text.Encoding.UTF8.GetBytes(recvStr);
for (int i = 0; i < conns.Length; i++) {
Conn tconn = conns[i];
if(tconn == null || tconn.isUse == false)
continue;
Console.WriteLine("[Server.ReceiveCb] Transmit message to " + tconn.GetAddress());
tconn.socket.Send(sendBuff);
}
}
conn.socket.BeginReceive( // Begin receive next message.
conn.readBuff,
conn.buffCount,
conn.BufferRemain(),
SocketFlags.None,
ReceiveCb,
conn);
} catch (Exception ex) {
Console.WriteLine ("[Server.ReceiveCb] Receive message fail. " + ex.Message);
conn.Close ();
}
}
}
}
Client端可以使用同步Socket中Client的脚本,或者仿照Server脚本中BeginReceive->ReceiveCb的流程实现持续监听Server,随时接收Server发来的数据。
下一节 MySQL的基本操作