[Unity通信]一个基于socket的3DARPG网络游戏(一):建立连接和事件分发
来源:互联网 发布:paxos算法 视频 编辑:程序博客网 时间:2024/05/20 20:02
一.客户端
1.定义一个消息体,服务器和客户端通信的时候,传输的就是这样的信息。
using System.Collections;using System.Text;public class SocketMessage { //大模块,例如登录注册模块,角色模块(行走、释放技能),购买模块 public int ModuleType { get; set; } //进一步分类,例如登录注册模块中含有登录和注册两种类型 public int MessageType { get; set; } //SocketMessage的核心,包含各种内容 public string Message { get; set; } //信息的总字节数 public int Length { get; set; } public SocketMessage(int moduleType, int messageType, string message) { ModuleType = moduleType; MessageType = messageType; Message = message; //Length的字节数,ModuleType的字节数,MessageType的字节数,系统自动添加的存储字符串长度的字节,Message的字节数 Length = 4 + 4 + 4 + 1 + Encoding.UTF8.GetBytes(message).Length; }}
2.因为传输的是二进制信息,所以要有一个类专门来读取和写入二进制
using System.Collections;using System.IO;using System;using System.Text;//对SocketMessage的读写public class ByteArray { //为了节省传输的流量,所以传输的是二进制 //读与写操作都是对一个流来进行的,这里使用MemoryStream private MemoryStream memoryStream; private BinaryReader binaryReader; private BinaryWriter binaryWriter; private int readIndex = 0; private int writeIndex = 0; public ByteArray() { memoryStream = new MemoryStream(); binaryReader = new BinaryReader(memoryStream); binaryWriter = new BinaryWriter(memoryStream); } public void Destroy() { binaryReader.Close(); binaryWriter.Close(); memoryStream.Close(); memoryStream.Dispose(); } public int GetReadIndex() { return readIndex; } public int GetLength() { return (int)memoryStream.Length; } public int GetPosition() { //position是从0开始的 return (int)memoryStream.Position; } public byte[] GetByteArray() { return memoryStream.ToArray(); } public void Seek(int offset, SeekOrigin seekOrigin) { //offset:相对于 SeekOrigin 所指定的位置的偏移量参数 memoryStream.Seek(offset, seekOrigin); } #region read public bool ReadBoolean() { Seek(readIndex, SeekOrigin.Begin); bool a = binaryReader.ReadBoolean(); readIndex += 1; return a; } public short ReadInt16() { Seek(readIndex, SeekOrigin.Begin); short a = binaryReader.ReadInt16(); readIndex += 2; return a; } public int ReadInt32() { Seek(readIndex, SeekOrigin.Begin); int a = binaryReader.ReadInt32(); readIndex += 4; return a; } public float ReadSingle() { Seek(readIndex, SeekOrigin.Begin); float a = binaryReader.ReadSingle(); readIndex += 4; return a; } public double ReadDouble() { Seek(readIndex, SeekOrigin.Begin); double a = binaryReader.ReadDouble(); readIndex += 8; return a; } public string ReadString() { Seek(readIndex, SeekOrigin.Begin); string a = binaryReader.ReadString(); //因为binaryWriter写字符串时会在字符串前面加一字节,存储字符串的长度 readIndex += Encoding.UTF8.GetBytes(a).Length + 1; return a; } #endregion #region write public void Write(bool value) { Seek(writeIndex, SeekOrigin.Begin); binaryWriter.Write(value); writeIndex += 1; } public void Write(short value) { Seek(writeIndex, SeekOrigin.Begin); binaryWriter.Write(value); writeIndex += 2; } public void Write(int value) { Seek(writeIndex, SeekOrigin.Begin); binaryWriter.Write(value); writeIndex += 4; } public void Write(float value) { Seek(writeIndex, SeekOrigin.Begin); binaryWriter.Write(value); writeIndex += 4; } public void Write(double value) { Seek(writeIndex, SeekOrigin.Begin); binaryWriter.Write(value); writeIndex += 8; } public void Write(string value) { Seek(writeIndex, SeekOrigin.Begin); binaryWriter.Write(value); //因为binaryWriter写字符串时会在字符串前面加一字节,存储字符串的长度 writeIndex += Encoding.UTF8.GetBytes(value).Length + 1; } public void Write(byte[] value) { Seek(writeIndex, SeekOrigin.Begin); binaryWriter.Write(value); writeIndex += value.Length; } #endregion}
3.定义socket客户端,用来连接服务器,并收发信息
using System.Collections;using System.Net.Sockets;using System.Net;using System;using System.Text;using System.Threading;using UnityEngine;public class SocketClient { private Socket socket;//当前套接字 private ByteArray byteArray = new ByteArray();//字节数组缓存 private Thread handleMessage;//处理消息的线程 public SocketClient() { handleMessage = new Thread(HandleMessage); handleMessage.Start(); } public SocketClient(Socket socket) { this.socket = socket; handleMessage = new Thread(HandleMessage); handleMessage.Start(); } public Socket GetSocket() { return socket; } public void Destroy() { handleMessage.Abort(); socket.Close(); byteArray.Destroy(); } /// <summary> /// 异步连接服务器 /// </summary> public void AsynConnect() { IPEndPoint serverIp = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080); socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.BeginConnect(serverIp, asyncResult => { socket.EndConnect(asyncResult); Debug.Log("connect success!"); AsynRecive(); AsynSend(new SocketMessage(19, 89, "你好,服务器")); AsynSend(new SocketMessage(19, 89, "你好,服务器")); AsynSend(new SocketMessage(19, 89, "你好,服务器")); }, null); } /// <summary> /// 异步接受信息 /// </summary> public void AsynRecive() { byte[] data = new byte[1024]; socket.BeginReceive(data, 0, data.Length, SocketFlags.None, asyncResult => { int length = socket.EndReceive(asyncResult); byte[] temp = new byte[length]; Debug.Log("接受到的字节数为" + length); Array.Copy(data, 0, temp, 0, length); byteArray.Write(temp); AsynRecive(); }, null); } /// <summary> /// 异步发送信息 /// </summary> public void AsynSend(SocketMessage sm) { ByteArray ba = new ByteArray(); ba.Write(sm.Length); ba.Write(sm.ModuleType); ba.Write(sm.MessageType); ba.Write(sm.Message); byte[] data = ba.GetByteArray(); ba.Destroy(); socket.BeginSend(data, 0, data.Length, SocketFlags.None, asyncResult => { int length = socket.EndSend(asyncResult); }, null); } /// <summary> /// 解析信息 /// </summary> public void HandleMessage() { int tempLength = 0;//用来暂存信息的长度 bool hasGetMessageLength = false;//是否得到了消息的长度 while (true) { if (!hasGetMessageLength) { if (byteArray.GetLength() - byteArray.GetReadIndex() > 4)//消息的长度为int,占四个字节 { tempLength = byteArray.ReadInt32();//读取消息的长度 hasGetMessageLength = true; } } else { //根据长度就可以判断消息是否完整 //GetReadIndex()可以得到已读的字节 //注意上面的ReadInt32读取后,读的索引会加上4,要把多余的减去 if ((tempLength + byteArray.GetReadIndex() - 4) <= byteArray.GetLength()) { SocketMessage sm = new SocketMessage(byteArray.ReadInt32(), byteArray.ReadInt32(), byteArray.ReadString()); //SocketServer.HandleMessage(this, sm); SocketSingletion.Instance.Send(sm); hasGetMessageLength = false; } } } }}
4.定义一个单例基类
using UnityEngine;using System.Collections;public class MonoSingletion<T> : MonoBehaviour { private static T instance; public static T Instance { get { return instance; } } void Awake() { instance = GetComponent<T>(); }}
5.定义一个类,管理socke客户端的生命周期,并提供事件接口供其他类使用
using UnityEngine;using System.Collections;public class SocketSingletion : MonoSingletion<SocketSingletion> { public SocketClient socketClient; public delegate void SendDelegate(SocketMessage sm); public event SendDelegate sendEvent = null;// Use this for initializationvoid Start () { socketClient = new SocketClient(); socketClient.AsynConnect();}// Update is called once per framevoid Update () {} public void Send(SocketMessage sm) { sendEvent(sm); } void OnDestroy() { print("Destroy socketClient"); socketClient.Destroy(); }}
二.服务器端
0.这里为了方便直接用c#写的服务器端,打开vs,直接把ByteArray、SocketClient、SocketMessage这三个类复制过来
1.
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Net;using System.Net.Sockets;public class SocketServer { private Socket socket;//当前套接字 public Dictionary<string, SocketClient> dictionary = new Dictionary<string, SocketClient>();//string为ip地址 public void Listen() { IPEndPoint serverIp = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080); socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.Bind(serverIp); socket.Listen(100); Console.WriteLine("server ready."); AsynAccept(socket); } /// <summary> /// 异步连接客户端 /// </summary> public void AsynAccept(Socket serverSocket) { serverSocket.BeginAccept(asyncResult => { Socket client = serverSocket.EndAccept(asyncResult); SocketClient socketClient = new SocketClient(client); string s = socketClient.GetSocket().RemoteEndPoint.ToString(); Console.WriteLine("连接的客户端为: " + s); dictionary.Add(s, socketClient); socketClient.AsynRecive(); socketClient.AsynSend(new SocketMessage(20, 15, "你好,客户端")); socketClient.AsynSend(new SocketMessage(20, 15, "你好,客户端")); socketClient.AsynSend(new SocketMessage(20, 15, "你好,客户端")); AsynAccept(serverSocket); }, null); } /// <summary> /// 解析信息 /// </summary> public static void HandleMessage(SocketClient sc, SocketMessage sm) { Console.WriteLine(sc.GetSocket().RemoteEndPoint.ToString() + " " + sm.Length + " " + sm.ModuleType + " " + sm.MessageType + " " + sm.Message); }}
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace ConsoleApplication6{ class Program { static void Main(string[] args) { SocketServer socketServer = new SocketServer(); socketServer.Listen(); Console.ReadKey(); } }}
三.测试
1.在unity中新建一个测试类
using UnityEngine;using System.Collections;public class ReceiveSocketMessage : MonoBehaviour {// Use this for initializationvoid Start () { SocketSingletion.Instance.sendEvent += PrintInfo;}// Update is called once per framevoid Update () {} public void PrintInfo(SocketMessage sm) { print(" " + sm.Length + " " + sm.ModuleType + " " + sm.MessageType + " " + sm.Message); }}
2.运行程序
分析:服务器端向客户端发送了三条信息,而本人使用了两个gameobject来订阅接收的事件,所以打印了6条信息。同时,在客户端中只接受到两条信息,一条字节数为62,另一条字节数为31,说明出现了粘包问题了,这里本人使用的是为每条传递的消息体前加了4个字节(int型),用来记录消息的长度,这样就可以实现分包了。同样的,服务器端只接受了一条信息,也是粘包的体现。
2 0
- [Unity通信]一个基于socket的3DARPG网络游戏(一):建立连接和事件分发
- [Unity通信]一个基于socket的3DARPG网络游戏(二):消息分类处理和json的使用
- [Unity3D ARPG网络游戏编程实践] 网络连接 :unity NetWork与socket的对比(一)
- 基于TCP的网络游戏黑白棋系列(一):建立连接
- 基于tornado的简单socket通信建立
- API SOCKET基础(一) TCP建立连接并通信
- API SOCKET基础(一) TCP建立连接并通信
- API SOCKET基础(一) TCP建立连接并通信
- TCP 基于连接的Socket通信程序设计
- java中的基于套结字(socket)的通信 一. 一个双人单方向通信例子
- Java socket报文通信(一)socket的建立
- java socket报文通信(一) socket的建立
- Java socket报文通信(一)socket的建立
- Java socket报文通信(一)socket的建立
- unity中建立 Socket 简单通信
- 一个基于observer模式的游戏事件分发系统
- 一个基于observer模式的游戏事件分发系统
- 一个基于observer模式的游戏事件分发系统
- python中生成二维码图片
- Additive Number
- 在VM上安装CentOS minimal版
- HTML DOM (一):DOM理解
- 微软算法100道题------输入一个单向链表,输出该链表中倒数第k个结点。链表的倒数第0个结点为链表的尾指针
- [Unity通信]一个基于socket的3DARPG网络游戏(一):建立连接和事件分发
- android控件之AutoCompleteTextView下拉列表显示一片空白
- HDU 2181-哈密顿绕行世界问题(裸dfs)
- codeforce 577 B. Modulo Sum
- JavaSE__Jdbc关闭
- PHP的反射机制
- 安卓开发项目搭建项目的时候要思考的一些问题
- Java获取操作系统信息和Java版本信息
- Android的消息机制