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