基于Unity的socket网络通信模块(一)

建立连接和事件分发

一、客户端
1.定义一个消息体,服务器和客户端通信的时候,传输的就是这样的信息。

using System.Collections;
using System.Text;

public class SocketMessage{
	//大的模块,如登录注册、角色模块,商城购买模块等
	public int ModuleType{get;set;}
	//进一步分类细化,如登录注册中的登录和注册两种类型分开
	public int MessageType{get;set;}
	//SocketMessaged的核心内容,包括各种内容
	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.Positon;
	}
	   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.Colse();
		byteArray.Destroy();
	}

	/// <summary>
    /// 异步连接服务器
    /// </summary>
	public void AsynConnect(){
		IPEndpiont serverIp = new IPEndPoint(IPAress.Parse("127.0.0.1"),80);
		socket = new Socket(AdressFamily.InterNetWork,SocketType.Stream,ProtocolType.Tcp);
		socket.BeginConnect(serverIp,asynResult =>
		{
			socket.EndConnect(asynResult);
			Debug.Log("connect success!");

			AsynReceive();
			AsynSend(new SocketMessage(19,89,"你好,服务器"));
			AsynSend(new SocketMessage(19,89,"你好,服务器"));
			AsynSend(new SocketMessage(19,89,"你好,服务器"));
		},null);
	}
	/// <summary>
    /// 异步接受信息
    /// </summary>
    public void AsynReceive(){
    	byte[] data = new byte[1024];
    	socket.BeginReceive(data,0,data.Length,SocketFlags.None,
    	asynResult =>
    		{int length = socket.EndReceive(asyncResult);
    		byte[] temp = new byte[length];
    		Debug.log("接收到的字节数为" + length);
    		Array.Copy(data , 0 , temp ,0 ,legth);
    		byteArray.Write(temp);
			AsynReceive();},
			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.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(!hasGetMessaeLength){
				if(byteArray.GetLength() - byteArray.GetReadIndex() > 4)  		//消息长度为int ,占4个字节
				{
					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());
					SocketSingletion.Instance.Send(sm);
					hasGetMessageLength =false;
				}
			}
		}
	}
}

4 定义一个单例基类

using UnityEngine;
using System.Collection;

private 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 deglegate void SendDelegate(SocketMessage sm);
	public event SendDegegate 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("Destory socketClient")
		socketClient.Destroy();
	}
}

二、服务器端
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);
    }
}

2

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();
        }
    }
}

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型),用来记录消息的长度,这样就可以实现分包了。同样的,服务器端只接受了一条信息,也是粘包的体现。

在这里插入图片描述

在这里插入图片描述

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值