[Unity通信]一个基于socket的3DARPG网络游戏(一):建立连接和事件分发

原创 2015年11月19日 20:08:48

一.客户端

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 initialization
	void Start () 
    {
        socketClient = new SocketClient();
        socketClient.AsynConnect();
	}
	
	// Update is called once per frame
	void 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 initialization
	void Start () 
    {
        SocketSingletion.Instance.sendEvent += PrintInfo;
	}
	
	// Update is called once per frame
	void Update () 
    {
	
	}

    public void PrintInfo(SocketMessage sm)
    {
        print("   " + sm.Length + "   " + 
            sm.ModuleType + "   " + sm.MessageType + "   " + sm.Message);
    }
}

2.运行程序

分析:服务器端向客户端发送了三条信息,而本人使用了两个gameobject来订阅接收的事件,所以打印了6条信息。同时,在客户端中只接受到两条信息,一条字节数为62,另一条字节数为31,说明出现了粘包问题了,这里本人使用的是为每条传递的消息体前加了4个字节(int型),用来记录消息的长度,这样就可以实现分包了。同样的,服务器端只接受了一条信息,也是粘包的体现。



版权声明:欢迎转载,欢迎讨论,大家一起共同进步!呵呵哒~

用 Unity 进行网络游戏开发(一)

用 Unity 进行网络游戏开发(一) 这是我之前写的了,一直保存在电脑里,现在学习写博客。希望多和大家交流,共同进步,文章中说得不好的地方请指出,谢谢! 使用Unity3D进...
  • TRGVBZ
  • TRGVBZ
  • 2015年07月09日 16:04
  • 1513

Unity多人游戏和网络功能(一) 概述和基本概念

总述本文翻译自Unity 5.2的官方文档。如对翻译有任何建议,欢迎留言。Unity从5.1开始改进了网络系统功能,提供了一个比之前版本更灵活更强大的网络系统。它提供了一个NetworkTranspr...

游戏服务器端通信框架(C++与Socket)

这是一个小型多个对战的游戏服务器端代码,经过修改。 文件一:stdafx.h //-------------------------------------------------------...
  • my2005lb
  • my2005lb
  • 2013年03月18日 22:26
  • 11218

跨平台的游戏客户端Socket封装

依照惯例,先上代码: #pragma once #ifdef WIN32 #include #include #else #include #include #include #incl...

【Unity3D_常用模块】 Socket网络模块(超级详细完整,上线项目中稳定使用着)

介绍Socket网络连接模块: 主要分为四部分: 一、套接字管理器(SocketManager.cs) 1)、连接 2)、断开 3)、接收(线程) 4)、发送(携程) 二、消息中心:(MessageC...
  • Claine
  • Claine
  • 2016年09月23日 15:48
  • 6725

《Unity 3D游戏客户端基础框架》 protobuf网络框架

前言:         protobuf是google的一个开源项目,主要的用途是: 1.数据存储(序列化和反序列化),例如xml和json等; 2.制作网络通信协议; 一、资源下载: 1.git...

Socket.IO for Unity 简要介绍和简单应用

在项目中使用到了Socket.IO for unity这个Asset Store上免费的库,这里将简要的介绍一下它的结构,已经使用中的注意事项。目录结构 上面为包的目录结构,简单的介绍一下具体的内容...

实用的Unity3D基于TCP/IP协议的网络通信框架--客户端

自己设计的一套实用的针对中小型网络游戏的网络通信框架

使用unity UGUI 利用Socket 实现 多客户端通讯

unity  5.1.1   VS2013 服务器端:using System; using System.Collections.Generic; using System.Linq; using ...

unity3D中使用Socket进行数据通信(三)

今天跟大家继续学习下socket,由于最近有个招标参数需要给之前的一款产品做教师端以及后台数据库部分,忙了将近两个礼拜,今天刚发布了,就继续我们的socket通信部分。          之前服务端一...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:[Unity通信]一个基于socket的3DARPG网络游戏(一):建立连接和事件分发
举报原因:
原因补充:

(最多只允许输入30个字)