网络游戏是一个人的互动娱乐软件应用。因为它是交互式,当然,需要了解对方的通信。这需要通信Socket:我们今天要实现的主角即套接字。Socket的英文原义是“孔”或“插座”。正如其英文原意那样。象一个多孔插座。
一台电脑机器宛如布满各种插座的房间,每一个插座有一个编号,有的插座提供220伏交流电, 有的提供110伏交流电。有的则提供有线电视节目。 客户软件将插头插到不同编号的插座,就能够得到不同的服务。以下我们来看看下图,游戏中玩家移动是怎样通讯:
以下是Unity3d游戏通讯Socket实现: BaseGameSocket.cs
//@原创:dongfu.luo using System; using System.Collections.Generic; using System.Net.Sockets; public abstract class BaseGameSocket { //用来接收服务端发过来的缓冲Buff private byte[] _data_buffer; //缓冲二进制数组从哪里開始读 private int _data_offset; //缓冲二进制数组读多少个byte private int _data_size; //游戏serverIP地址 private string _ip; private bool _is_read_head; //游戏server端口 private int _port; //要服务端发送的数据命令列表 private List<PacketOut> _send_list = new List<PacketOut>(); private Socket _sock; private const int MAX_SEND_QUEUE = 0x3e8; //清空数据,通常是在断开重联时候调用 private void Clear() { this._ip = null; this._port = 0; this._sock = null; List<PacketOut> list = this._send_list; lock (list) { this._send_list.Clear(); } this._is_read_head = false; this._data_buffer = null; this._data_offset = 0; this._data_size = 0; } //关闭client的Socket public void Close() { try { Socket socket = this._sock; this.Clear(); if ((socket != null) && socket.Connected) { socket.Shutdown(SocketShutdown.Both); socket.Close(); } } catch (Exception exception) { UEDebug.LogError("Connect: " + exception.ToString()); this.Error(Lang.GetString("k3432")); } } //连接游戏服务端 public void Connect(string ip, int port) { try { this.Close(); this._ip = ip; this._port = port; this._sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); this._sock.NoDelay = true; this._sock.BeginConnect(this._ip, this._port, new AsyncCallback(this.OnConnect), this._sock); } catch (Exception exception) { UEDebug.LogError("Connect: " + exception.ToString()); this.Error(Lang.GetString("k3432")); } } //假设Socket没有关闭继续等server信息,假设有信息则開始读 private void ContinueRead() { try { if (this.IsConnected) { this._sock.BeginReceive(this._data_buffer, this._data_offset, this._data_size - this._data_offset, SocketFlags.None, new AsyncCallback(this.OnRead), null); } } catch (Exception exception) { UEDebug.LogError(exception.ToString()); this.Error(Lang.GetString("k3432")); } } //从发送队列从不断向游戏server发送命令 private void ContinueSend() { PacketOut state = null; List<PacketOut> list = this._send_list; lock (list) { if (this._send_list.Count == 0) { return; } state = this._send_list[0]; if (state.start) { return; } state.start = true; } Socket sock = state.sock; if (sock.Connected) { sock.BeginSend(state.buff, 0, state.buff.Length, SocketFlags.None, new AsyncCallback(this.OnSend), state); } } //发送失败或错误,则关闭Socket通常是网络断了server关闭了 protected void Error(string msg) { this.Close(); this.OnError(msg); } //程序员都知道这是析构函数 ~PackedSocket() { this.Close(); } public int GetSendQueueSize() { List<PacketOut> list = this._send_list; lock (list) { return this._send_list.Count; } } //此函数由子类去处理 protected abstract void OnConnect(); //假设是第一次连接上了,解析消息头 private void OnConnect(IAsyncResult ret) { if (ret.AsyncState == this._sock) { try { this._sock.EndConnect(ret); this.ReadHead(); this.OnConnect(); } catch (Exception exception) { Debug.log(exception); } } } protected abstract void OnError(string msg); protected abstract void OnPack(byte[] data); //有服务端信息来,開始解析,现解析信息头再解析消息体 private void OnRead(IAsyncResult ar) { try { if (this.IsConnected) { int num = this._sock.EndReceive(ar); this._data_offset += num; if (num <= 0) { } else if (this._data_offset != this._data_size) { this.ContinueRead(); } else if (this._is_read_head) { int num2 = BitConverter.ToInt32(this._data_buffer, 0); this.ReadData(num2); } else { this.OnPack(this._data_buffer); this.ReadHead(); } } } catch (Exception exception) { Debug.log(exception); } } //假设命令发送成功。检查发送队列是否还有须要发送的命令。假设有则继续发送 private void OnSend(IAsyncResult ar) { PacketOut asyncState = ar.AsyncState as PacketOut; Socket sock = asyncState.sock; if (sock.Connected) { sock.EndSend(ar); List<PacketOut> list = this._send_list; lock (list) { if (this._send_list.Contains(asyncState)) { this._send_list.Remove(asyncState); } } this.ContinueSend(); } } //读取消息体 private void ReadData(int pack_len) { this._is_read_head = false; this._data_size = pack_len; this._data_offset = 4; this._data_buffer = new byte[this._data_size]; this.ContinueRead(); } //读取消息头 private void ReadHead() { this._is_read_head = true; this._data_size = 4; this._data_offset = 0; this._data_buffer = new byte[this._data_size]; this.ContinueRead(); } //详细的发送信息,先把数据发到发送队列 public void Send(byte[] buff) { if (this.IsConnected) { PacketOut item = new PacketOut { start = false, buff = buff, sock = this._sock }; int count = 0; List<PacketOut> list = this._send_list; lock (list) { this._send_list.Add(item); count = this._send_list.Count; } if (count > 0x3e8) { } else { this.ContinueSend(); } } } public bool IsClosed { get { return (this._sock == null); } } public bool IsConnected { get { return ((this._sock != null) && this._sock.Connected); } } private class PacketOut { public byte[] buff; public Socket sock; public bool start; } }