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