网络游戏是一个人的互动娱乐软件应用。因为它是交互式,当然,需要了解对方的通信。这需要通信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;
}
}
Unity3d在线游戏Socket通讯的更多相关文章
-
Photon + Unity3D 在线游戏开发 学习笔记(两)
本文和大家 和大家说说 Photon 解压后的目录结构 这里面最基本的我们 以后开发要用到的目录 就是 deploy目录,这个目录里 放的是要挂载的 server 当然我们的 server端也要放在 ...
-
【坦克大战】Unity3D多人在线游戏(泰课的坦克大战--旋转的螺丝钉)
[坦克大战]Unity3D多人在线游戏 http://www.taikr.com/my/course/937 1.NetworkManager的介绍: 说明:选择固定生成时会自动寻找有StartPos ...
-
关于socket通讯,如何才能高效?
关于socket通讯,如何才能高效? 网络通讯,一个不朽的话题,今天和一个做游戏的朋友(以前的同事聊天),他向我诉说了他的痛苦 他之前是做客户端的,无奈人力资源紧张,也开始搞服务器,他说自己的服务器总 ...
-
《Unity3D/2D游戏开发从0到1(第二版本)》 书稿完结总结
前几天,个人著作<Unity3D/2D游戏开发从0到1(第二版)>经过七八个月的技术准备以及近3个月的日夜编写,在十一长假后终于完稿.今天抽出一点时间来,给广大热心小伙伴们汇报一下书籍概况 ...
-
《Unity3D/2D游戏开发从0到1》正式出版发行
<Unity3D/2D游戏开发从0到1>正式出版发行 去年个人编写的Unity书籍正式在2015年7月正式发行,现在补充介绍一下个人著作.书籍信息: 书籍的名称: <Uni ...
-
Unity3D 入门 游戏开发 Unity3D portal game development
Unity3D 入门 游戏开发 Unity3D portal game development 作者:韩梦飞沙 Author:han_meng_fei_sha 邮箱:313134555@qq.com ...
-
Unity3D手机游戏开发
<Unity3D手机游戏开发> 基本信息 作者: 金玺曾 出版社:清华大学出版社 ISBN:9787302325550 上架时间:2013-8-7 出版日期:2013 年8月 开本:16开 ...
-
客户端技术的一点思考(数据存储用SQLite, XMPP通讯用Gloox, Web交互用LibCurl, 数据打包用Protocol Buffer, socket通讯用boost asio)
今天看到CSDN上这么一篇< 彻底放弃没落的MFC,对新人的忠告!>, 作为一个一直在Windows上搞客户端开发的C++程序员,几年前也有过类似的隐忧(参见 落伍的感觉), 现在却有一些 ...
-
c#Socket通讯
参考http://bbs.cskin.net/thread-326-1-1.html的大神的代码 socket封装 /// <summary> /// 自定义Socket对象 /// &l ...
随机推荐
-
IntelliJ idea创建Spring MVC的Maven项目
参考:http://my.oschina.net/gaussik/blog/385697?fromerr=Pie9IlFV 创建Maven Web项目 菜单File->New Project可进 ...
-
JS事件冒泡与捕获
1事件传播——冒泡与捕获 默认情况下,事件使用冒泡事件流,不使用捕获事件流.然而,在Firefox和Safari里,你可以显式的指定使用捕获事件流,方法是在注册事件时传入useCapture参数,将这 ...
-
Redrain仿酷狗音乐播放器开发完毕,发布测试程序
转载请说明原出处,谢谢~~ 从暑假到现在中秋刚过,我用duilib开发仿酷狗播放器大概经历了50天.做仿酷狗的意图只是看原酷狗的界面比较漂亮,想做个完整一些的工程来练习一下duilib.今天把写好的程 ...
-
virsh console使用方法
How to use virsh console virsh 是libvirt 开源函数库中的一个命令行工具,用来在命令行下通过libvirt发布管理虚拟机的各种命令,现在介绍一下其中一个命令的使用方 ...
-
C语言初学 比较五个整数并输出最大值和最小值1
#include<stdio.h> #include<math.h> int max(int x,int y) { if(x>y) return x; else retu ...
-
Unicode字符列表
注:除非有特别指明,否则以下符号皆属“半角”而非“全角”. 代码 显示 描述 U+0020 空格 U+0021 ! 叹号 U+0022 " 双引号 U+0023 # 井号 U+0024 $ ...
-
用CSS的border画三角形
用border画三角形,实际上属于一种奇淫巧技. 利用的是border的一个特性:当一个元素的宽高都为0时,给border设置宽度(至少给2个相邻的边框设置宽度),border就会撑开这个元素. 四个 ...
-
pycharm的console显示乱码和中文的配置
第一种方式: 在python脚本开始的地方加入指定编码方式 # -*- coding : UTF-8 -*- 第二种方式: 有些时候我们会得到这种格式的字符串: "name": & ...
-
MySQL 数据库应用程序编程
普通用户使用客户端应用程序(Client Application)和服务器程序(Server Application)通信以取得服务, 而服务器程序通常要和数据库服务器通信以取得数据存取服务, 这时就 ...
-
Mysql添加字段.md
alter table td_user add gender bit DEFAULT 0 COMMENT '性别';