初学服务器 对本地服务器的使用理解 (异步)
通过异步开启线程 利用 Socket 来进行连接服务器和客户端 服务器需要绑定ip地址 端口 客户端通过端口连接服务器 在数据传递中只能进行字节数组传递
异步连接 在异步中有开始就有结束 开启异步连接 在回调函数中endAccept() 递归进行下一个用户的连接 通过 IPEndPoint 获取终端信息 连接成功开始异步接收消息
在进行接发消息的时候 是通过异步进行接收 在接收消息中进行递归接收 进行消息解码 byte[] 数组中前四位是消息号(是我们自己定义的,后期传递消息估计是任何类型 )通过解码 去除前四位是接收的消息 通过消息中心、传来的消息号 进行发送给侦听该消息号的位置 需要传递 消息 和 传来消息的客户端 这样在回消息的时候知道发给哪个客户端
服务端发送消息也是一样 需要将消息号提前编码在发送的字节数组中 让客户端进行解码 发消息
服务器代码 (利用C# 控制台应用 创建项目)
单例模式
public class Singleton<T> where T : class, new()
{
static T instance;
public static T Ins
{
get
{
if (instance == null)
{
instance = new T();
}
return instance;
}
}
}
服务器管理类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
/// 服务器 网络管理类
/// 管理网络 所有连接你服务器的网络用户)
/// 1 统计连接了服务器的客户端 等待客户端连接的方法
/// 2 收取客户端发来的消息
/// 3 发送消息给客户端
/// </summary>
public class NetMgr : Singleton<NetMgr>
{
Socket socket;
List<Client> sockets = new List<Client>();
public void InitServe()
{
Console.WriteLine("服务器开启");
//地址族 inter net 数据流:一套用于传输数据的类型 协议
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// 继承关系 ip地址访问权限 端口
socket.Bind(new IPEndPoint(IPAddress.Any, 9999));
socket.Listen(10); //最大连接数
开启线程 同步等待连接 避免多开线程
//Thread thread = new Thread(OnAccept); //委托类型
开启线程
//thread.Start();
//异步等待连接
socket.BeginAccept(AsyOnAccept, null);
}
/// <summary>
/// 异步连接
/// </summary>
/// <param name="ar"></param>
private void AsyOnAccept(IAsyncResult ar) //异步的结果
{
try //try 报错 避免服务器宕机 保持原有功能的实现
{
//创建客户端数据
Socket sc_Client = socket.EndAccept(ar); //最终执行给谁
IPEndPoint iPEndPoint = sc_Client.RemoteEndPoint as IPEndPoint;
Client client = new Client();
client.CliSocket = sc_Client;
client.name = iPEndPoint.Port.ToString();
sockets.Add(client);
Console.WriteLine(client.name + "上线");
//开始异步接收
sc_Client.BeginReceive(client.data, 0, client.data.Length, SocketFlags.None, AsyOnReceive, client);
//AsyOnSend(UTF8Encoding.UTF8.GetBytes("Hello Client"), client);
//递归下一个上线
socket.BeginAccept(AsyOnAccept, null);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
/// <summary>
/// 异步发送消息
/// </summary>
/// <param name="data"></param>
/// <param name="client"></param>
public void AsyOnSend(int msgId, byte[] data, Client client)
{
byte[] endData = new byte[0];
endData = endData.Concat(BitConverter.GetBytes(msgId).Concat(data)).ToArray();
client.CliSocket.BeginSend(endData, 0, endData.Length, SocketFlags.None, AsyOnSend, client);
}
/// <summary>
/// 异步发送数据
/// </summary>
/// <param name="ar"></param>
private void AsyOnSend(IAsyncResult ar)
{
try
{
Client client = ar.AsyncState as Client;
int len = client.CliSocket.EndSend(ar);
Console.WriteLine("服务端发送字节长度为:" + len);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
/// <summary>
/// 异步接收数据
/// </summary>
/// <param name="ar"></param>
private void AsyOnReceive(IAsyncResult ar)
{
try
{
Client cli = ar.AsyncState as Client; //此api可以获取 BeginReceive 传来的 object
int len = cli.CliSocket.EndReceive(ar);
Console.WriteLine("服务器接收:" + len);
// 前四个是消息号 后面字节全是内容 需要转类型
//这里的消息一定是由 消息号 和 内容 组成的
//拆除消息号和内容
//消息号 int32 4个字节
//获取前四位的字节 是消息号
int msgId = BitConverter.ToInt32(cli.data, 0); //获取全部消息
byte[] info = new byte[len - 4]; //去掉消息号的字节数组
Buffer.BlockCopy(cli.data, 4, info, 0, info.Length); //字节数组复制
MsgData msgData = new MsgData();
msgData.data = info; //内容
msgData.client = cli; //发给谁
//消息发送
MessageCenter<MsgData>.Ins.Dispatch(msgId, msgData);
//重复接收
cli.CliSocket.BeginReceive(cli.data, 0, cli.data.Length, SocketFlags.None, AsyOnReceive, cli);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
/// <summary>
/// 同步接收
/// </summary>
private void OnAccept()
{
while (true)
{
//创建一个客户端接收者
Socket socket_clinet = socket.Accept(); //分出 给客户端连接 在套接字执行操作时while不执行 停在上一句话的等待
//获取终端信息
IPEndPoint iPEndPoint = socket_clinet.RemoteEndPoint as IPEndPoint; //转换终端ip
//上线的入集合 赋初始值
Client client = new Client();
client.CliSocket = socket_clinet;
client.name = iPEndPoint.Port.ToString();
sockets.Add(client);
//上线成功
Console.WriteLine("客户端{0}链接成功{1}", iPEndPoint.Address, iPEndPoint.Port);
Console.WriteLine(sockets.Count);
//客户端接收消息
while (true)
{
//客户端和服务器之间只能通过字节发送
int len = socket_clinet.Receive(client.data); //套接字的API 等待客户端发送消息才往下走
Console.WriteLine("收到消息:" + UTF8Encoding.UTF8.GetString(client.data));
//发送给客户端 单个客户端
OnSendClinet(UTF8Encoding.UTF8.GetBytes("Hello Client"), client);
}
}
}
/// <summary>
/// 发送给客户端 单个客户端
/// </summary>
/// <param name="data"></param>
/// <param name="client"></param>
public void OnSendClinet(byte[] data, Client client)
{
client.CliSocket.Send(data);
}
}
消息类型
public class MsgData
{
public byte[] data; //储存发送的内容
public Client client; //客户端 (发给谁)
}
消息中心
public class MessageCenter<T> : Singleton<MessageCenter<T>>
{
Dictionary<int, Action<T>> msgs_Dic = new Dictionary<int, Action<T>>();
public void AddListener(int msgId, Action<T> action)
{
if (msgs_Dic.ContainsKey(msgId))
{
msgs_Dic[msgId] += action;
}
else
{
msgs_Dic.Add(msgId, action);
}
}
public void RemoveListener(int msgId, Action<T> action)
{
if (msgs_Dic.ContainsKey(msgId))
{
msgs_Dic[msgId] -= action;
if (msgs_Dic[msgId] == null)
{
msgs_Dic.Remove(msgId);
}
}
}
public void Dispatch(int msgId, T t)
{
if (msgs_Dic.ContainsKey(msgId))
{
msgs_Dic[msgId]?.Invoke(t);
}
}
}
///消息id
public class MessageId
{
public const int CS_LOGIN = 1001; //上行协议
public const int SC_LOGIN_CALL = 1002; //下行协议
}
客户端代码(unity代码)
接下来就是客户端代码了 下面文字是介绍主要主要是怎么连接本地服务器的
异步连接服务器 持有 socket 通过socket进行连接 开始连接 客户端ip地址 服务器端口 回调函数
在回调函数中 可检测是否连接成功 开始接收数据
在此接收数据和服务器接收数据无二致 区别在于 服务器需要管理多个账户 用于接受的字节数组不固定 因为在客户端只有本身 所以数据声明一个总字节数组
消息号要和服务端的消息号一致避免解码消息号不对应
客户端管理类
using System.Net.Sockets;
using UnityEngine;
using System.Text;
using System.Linq;
using System.Threading;
using System;
/// <summary>
/// 网络管理类
/// </summary>
public class NetMgr : Singleton<NetMgr>
{
Socket socket; //相当于链接的两个线 客户端 服务 端都有一个
byte[] data = new byte[1024]; //需要存东西 没有空间存不了
public void ContentNet() //连接服务器
{
// 地址族 套接字类型 数据流 协议类型 Tcp
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//socket.Connect("10.161.54.108", 815); //客户端链接服务端的接口
//开始线程 在线程中接收发送消息
//Thread thread = new Thread(OnAccept);
//thread.Start();
//客户端改成异步线程 开始终端连接
socket.BeginConnect("10.161.54.108", 9999, AsyOnConnect, null);
//还有终端异步接发消息
}
/// <summary>
/// 异步开始发送消息
/// </summary>
/// <param name="msgId"></param>
/// <param name="data"></param>
public void AsyOnSend(int msgId, byte[] data)
{
byte[] endData = new byte[0];
endData = endData.Concat(BitConverter.GetBytes(msgId)).Concat(data).ToArray();
Debug.Log(socket);
socket.BeginSend(endData, 0, endData.Length, SocketFlags.None, AsyOnSend, null);
}
/// <summary>
/// 异步发送消息 只需发一次
/// </summary>
/// <param name="ar"></param>
private void AsyOnSend(IAsyncResult ar)
{
try
{
int len = socket.EndSend(ar);
Debug.Log("client send message that :" + len);
}
catch (Exception ex)
{
Debug.Log(ex);
}
}
/// <summary>
/// 异步连接
/// </summary>
/// <param name="ar"></param>
private void AsyOnConnect(IAsyncResult ar)
{
try
{
//终端连接
socket.EndConnect(ar);
Debug.Log("连接成功!!");
socket.BeginReceive(data, 0, data.Length, SocketFlags.None, AsyOnReceive, null);
}
catch (Exception ex)
{
Debug.Log(ex);
}
}
/// <summary>
/// 异步接收消息
/// </summary>
/// <param name="ar"></param>
private void AsyOnReceive(IAsyncResult ar)
{
try
{
int len = socket.EndReceive(ar);
int megId = BitConverter.ToInt32(data, 0); //消息号
byte[] info = new byte[len - 4]; //真实消息内容
Buffer.BlockCopy(data, 4, info, 0, info.Length);
MessageCenter<byte[]>.Ins.Dispatch(megId, info);
socket.BeginReceive(data, 0, data.Length, SocketFlags.None, AsyOnReceive, null);
}
catch (Exception)
{
throw;
}
}
private void OnAccept(object obj)
{
while (true)
{
int len = socket.Receive(data); //收消息
Debug.Log("服务器回消息:" + UTF8Encoding.UTF8.GetString(data));
}
}
public void OnSend(byte[] data)
{
int len = socket.Send(data); //发消息
Debug.Log("发送消息字节长度为" + len);
}
}
最后测试是否连接成功以及是否发送消息成功(拿登录举例)
客户端
using System;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
public class LoginUI : MonoBehaviour
{
public Button login_Btn;
private void Start()
{
MessageCenter<byte[]>.Ins.AddListener(MessageId.SC_LOGIN_CALL, Login);
login_Btn.onClick.AddListener(() =>
{
string acc = "123";
string pwd = "123";
string all = acc + "|" + pwd;
NetMgr.Ins.AsyOnSend(MessageId.CS_LOGIN, UTF8Encoding.UTF8.GetBytes(all));
});
}
private void Login(byte[] obj)
{
bool flag = BitConverter.ToBoolean(obj, 0);
if (flag)
{
Debug.Log("登录成功");
}
else
{
Debug.Log("登录失败");
}
}
}
服务端
using System;
using System.Collections.Generic;
using System.Text;
public class LoginManager : Singleton<LoginManager>
{
Dictionary<string, string> account_Dic = new Dictionary<string, string>();
public void Init()
{
MessageCenter<MsgData>.Ins.AddListener(MessageId.CS_LOGIN, CheckLogin);
}
private void CheckLogin(MsgData msg)
{
//未发送成功 字符串为数字串 字符串转换为题
string content = UTF8Encoding.UTF8.GetString(msg.data);
string acc = content.Split('|')[0];
string pwd = content.Split('|')[1];
bool flag = false;
if (account_Dic.ContainsKey(acc))
{
if (account_Dic[acc] == pwd)
{
flag = true;
}
}
NetMgr.Ins.AsyOnSend(MessageId.SC_LOGIN_CALL, BitConverter.GetBytes(flag), msg.client);
}
}
开启服务器 客户端连接
点击按钮发送消息 以及服务端发送消息
最后
客户端的消息中心以及消息号没有展示代码 主要是和服务端一样的 这里不进行重复说明
由此 便连接unity 和 本地服务器
简单说明了两者是如何建立联系的 如何传递数据的 这个方式在进行多个消息处理时 会出现粘包现象 这篇文章不进行讲解 可在我主页查询相关代码