首先就是创建文件夹 最好的是一个项目名的文件夹 里面
当然这也不是强制性的为了方便保存和查找
剩下就是客户端和服务器都敲上 消息中心和泛型单利 为了以后传网络 数据等
不会的同学跳转到本人的 (3条消息) 消息中心-泛型单利C#_慕容鑫非的博客-CSDN博客
先说Server 部分的代码
连接需要用到的类
using System.Net.Sockets;
using System.Threading;
//文件路径 看个人 无需一样
namespace ZG_Server
{
public class Client
{
public Socket socket;
public string name;
public byte[] data = new byte[1024];
public MyMemoryStream my = new MyMemoryStream();
public byte[] stream = new byte[0];
public Timer timer;
public int id;
public PlayerData player;
}
}
网络连接部分
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using Google.Protobuf;
namespace ZG5_Server
{
class NetManager: Singleton<NetManager>
{
Socket server;
public List<Client> clients = new List<Client>();
public void Init()
{
server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint ip = new IPEndPoint(IPAddress.Any, 12345);
server.Bind(ip);
server.Listen(20);
Console.WriteLine("服务器开启");
server.BeginAccept(OnAccept, null);
}
private void OnAccept(IAsyncResult ar)
{
Socket client = server.EndAccept(ar);
IPEndPoint iP = client.RemoteEndPoint as IPEndPoint;
Client cli = new Client();
cli.socket = client;
cli.name = iP.Address + ":" + iP.Port;
clients.Add(cli);
Console.WriteLine(DateTime.Now.ToString()+ " " + iP.Port + "链接");
cli.socket.BeginReceive(cli.data, 0, cli.data.Length, SocketFlags.None, OnReceive, cli);
server.BeginAccept(OnAccept, null);
}
private void OnReceive(IAsyncResult ar)
{
try
{
Client cli = ar.AsyncState as Client;
int len = cli.socket.EndReceive(ar);
if (len <= 0)
{
cli.socket.Shutdown(SocketShutdown.Both);
cli.socket.Close();
clients.Remove(cli);
Console.WriteLine(DateTime.Now.ToString() + " " + cli.name + "下线");
Leave s2c = new Leave();
s2c.PlayerId = cli.player.Id;
foreach (var item in clients)
{
if (item.player != null)
{
Send(MsgID.S2C_LEAVE, s2c.ToByteArray(), item);
}
}
}
else if (cli.socket.Connected)
{
byte[] data = new byte[len];
Buffer.BlockCopy(cli.data, 0, data, 0, data.Length);
//创建一个临时流
byte[] liu = new byte[cli.stream.Length + data.Length];
Buffer.BlockCopy(cli.stream, 0, liu, 0, cli.stream.Length);
Buffer.BlockCopy(data, 0, liu, cli.stream.Length, data.Length);
//把流替换为临时流
cli.stream = liu;
//流长度大于等于2说明流内有数据
while (cli.stream.Length >= 2)
{
//获取一个包的长度
ushort onelen = BitConverter.ToUInt16(cli.stream, 0);
//如果流的长度大于等于包的长度+2说明有一个完整包
if (cli.stream.Length >= 2 + onelen)
{
//获取一个完整包
byte[] onedata = new byte[onelen];
Buffer.BlockCopy(cli.stream, 2, onedata, 0, onedata.Length);
//获取包的消息号
int id = BitConverter.ToInt32(onedata, 0);
//获取包的消息体
byte[] body = new byte[onedata.Length - 4];
Buffer.BlockCopy(onedata, 4, body, 0, body.Length);
//消息分发
Console.WriteLine("收到消息:id" + id);
MessageCenter.Ins.Send(id, body, cli);
//剩余长度
int sylen = cli.stream.Length - (2 + onelen);
//剩余长度大于0说明还有包
if (sylen > 0)
{
byte[] sydata = new byte[sylen];
Buffer.BlockCopy(cli.stream, 2 + onelen, sydata, 0, sylen);
cli.stream = sydata;
}
else
{
cli.stream = new byte[0];
}
}
else
{
break;
}
}
#region MyRegion
//byte[] data = new byte[len];
//Buffer.BlockCopy(cli.data, 0, data, 0, len);
将收到的消息写入流
//cli.my.Write(data, 0, data.Length);
流长度大于等于2说明有内容(2是包长度ushort的长度)
//while (cli.my.Length >= 2)
//{
// //读取的起始点
// cli.my.Position = 0;
// int oneLen = cli.my.ReadUshort();
// //判断是否有完整包
// if (cli.my.Length >= 2 + oneLen)
// {
// //获取一个包
// byte[] oneData = new byte[oneLen];
// cli.my.Read(oneData, 0, oneData.Length);
// //解析并分发
// int id = BitConverter.ToInt32(oneData, 0);
// byte[] body = new byte[oneLen - 4];
// Buffer.BlockCopy(oneData, 4, body, 0, body.Length);
// MessageCenter.Ins().Broadcast(id, body, cli);
// int syLen = (int)cli.my.Length - (2 + oneLen);
// //判断流内是否还有剩余内容
// if (syLen > 0)
// {
// //复制出剩余内容
// byte[] syDate = new byte[syLen];
// cli.my.Read(syDate, 0, syDate.Length);
// //清空流
// cli.my.SetLength(0);
// cli.my.Position = 0;
// //将剩余内容写入流
// cli.my.Write(syDate, 0, syDate.Length);
// }
// else
// {
// //清空流
// cli.my.SetLength(0);
// cli.my.Position = 0;
// }
// }
// else
// {
// cli.my.Position = cli.my.Length;
// break;
// }
#endregion
cli.socket.BeginReceive(cli.data, 0, cli.data.Length, SocketFlags.None, OnReceive, cli);
}
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
public void Send(int id, byte[] body, Client cli)
{
byte[] head = BitConverter.GetBytes(id);
byte[] data = new byte[head.Length + body.Length];
Buffer.BlockCopy(head, 0, data, 0, head.Length);
Buffer.BlockCopy(body, 0, data, head.Length, body.Length);
//手动拼接长度
byte[] mylen = BitConverter.GetBytes((ushort)data.Length);
byte[] myData = new byte[mylen.Length + data.Length];
Buffer.BlockCopy(mylen, 0, myData, 0, mylen.Length);
Buffer.BlockCopy(data, 0, myData, mylen.Length, data.Length);
cli.socket.BeginSend(myData, 0, myData.Length, SocketFlags.None, OnSend, cli);
}
private void OnSend(IAsyncResult ar)
{
Client cli = ar.AsyncState as Client;
int len = cli.socket.EndSend(ar);
Console.WriteLine(len);
}
}
}
然后就是主函数了
namespace ZG5_Server
{
class Program
{
static void Main(string[] args)
{
NetManager.Ins.Init();
while (true)
{
}
}
}
}
Server 部分可以告一段落了
开始Client部分了
敲上 消息中心和泛型单利 为了以后传网络 数据等
不会的同学跳转到本人的 (3条消息) 消息中心-泛型单利C#_慕容鑫非的博客-CSDN博客
连接部分很重要
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using UnityEngine;
public class NetClient : Single<NetClient>
{
public Socket m_Sockets;//客户端的通讯类
public Queue<byte[]> m_que = new Queue<byte[]>();//客户端队列用于保存流数据ID和包
public byte[] m_Data = new byte[1024];//缓存
public byte[] m_Stream = new byte[0];//流
public void Init()
{
m_Sockets = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//创建通讯类
m_Sockets.BeginConnect("127.0.0.1", 12345, OnConnect, null);//给服务器发消息127.0.0.1是本机ID 12345是服务器端口
}
private void OnConnect(IAsyncResult ar)
{
m_Sockets.EndConnect(ar);
m_Sockets.BeginReceive(m_Data, 0, m_Data.Length, SocketFlags.None, OnReceive, null);//接收服务器发送的数据
}
private void OnReceive(IAsyncResult ar)
{
int len = m_Sockets.EndReceive(ar);//接收的长度
Debug.Log("服务器发回来长度:"+len);
if (len > 0)
{
byte[] data = new byte[len];
Buffer.BlockCopy(m_Data, 0, data, 0, len);
m_Stream = m_Stream.Concat(data).ToArray();
while (m_Stream.Length > 2)
{
ushort bodyLen = BitConverter.ToUInt16(m_Stream, 0);
int allLen = bodyLen + 2;
if (m_Stream.Length >= allLen)
{
byte[] oneData = new byte[bodyLen];
Buffer.BlockCopy(m_Stream, 2, oneData, 0, bodyLen);
m_que.Enqueue(oneData);
int syLen = m_Stream.Length - allLen;
if (syLen > 0)
{
byte[] syBody = new byte[syLen];
Buffer.BlockCopy(m_Stream, allLen, syBody, 0, syLen);
m_Stream = syBody;
}
else
{
m_Stream = new byte[0];
break;
}
}
else
{
break;
}
}
m_Sockets.BeginReceive(m_Data, 0, m_Data.Length, SocketFlags.None, OnReceive, null);
}
}
public void Send(int id, byte[] body)
{
byte[] head = BitConverter.GetBytes(id);
byte[] len = BitConverter.GetBytes((ushort)(head.Length + body.Length));
byte[] data = new byte[0];
data = data.Concat(len).ToArray();
data = data.Concat(head).ToArray();
data = data.Concat(body).ToArray();
m_Sockets.BeginSend(data, 0, data.Length, SocketFlags.None, OnSend, null);
}
private void OnSend(IAsyncResult ar)
{
int len = m_Sockets.EndSend(ar);
Debug.Log("客户端发送长度:" + len);
}
public void Updata()
{
if (m_que.Count > 0)
{
byte[] oneData = m_que.Dequeue();
int id = BitConverter.ToInt32(oneData, 0);
byte[] body = new byte[oneData.Length - 4];
Buffer.BlockCopy(oneData, 4, body, 0, body.Length);
MessageManager.Instance.Send(id, body);
}
}
}
游戏开始连上网络 和每帧监听服务器发送过来的消息
using UnityEngine;
public class GameManager : MonoBehaviour
{
void Start()
{
NetClient.Instance.Init();
}
void Update()
{
NetClient.Instance.Updata();
}
}
忘记说了MsgID
C2S是客服端发给服务器 S2C是服务器发给客户端 都有ID号一定要一样
class MsgId
{
public static int C2S_CHAT = 1000;
public static int S2C_CHAT = 1001;
//登录消息的ID
public static int C2S_LOGIN = 1002;
public static int S2C_LOGIN = 1003;
//上下线获取ID
//获取在线列表
public static int C2S_OTHER = 1004;
public static int S2C_OTHER = 1005;
//通知其他玩家,有用户上线了
public static int S2C_ONLINE = 1006;
//通知用户下线
public static int S2C_LEVEL = 1007;
//移动
public static int C2S_Move = 1008;
public static int S2C_Move = 1009;
//攻击其它玩家
public static int C2S_HURT = 10014;
public static int S2C_HURT = 10015;
//子弹ID
public static int C2S_BULLET = 10016;
public static int S2C_BULLET = 10017;
//创建房间
public static int C2S_CREATEROOM = 10018;
public static int S2C_CREATEROOM = 10019;
//获取房间列表
public static int C2S_GETROOMLIST = 10020;
public static int S2C_GETROOMLIST = 10021;
//加入房间
public static int C2S_ADDROOM = 10022;
public static int S2C_ADDROOM = 10023;
public static int S2C_OTHERADDROOM = 10024; //其他用户加入房间
好了 rubbish的简易网络框架差不多已经搭建好了
举个使用的例子吧
客户端
using UnityEngine;
using UnityEngine.UI;
using Google.Protobuf;
public class Login : MonoBehaviour
{
public InputField inputFieldUser, inputFieldPwd;
public Button buttonZC, buttonDR;
void Start()
{
buttonZC.onClick.AddListener(() =>
{
C2S_Login c2S = new C2S_Login();
c2S.Account = inputFieldUser.text;
c2S.Password = inputFieldPwd.text;
NetClient.Instance.Send(MsgID.C2S_REGISTER, c2S.ToByteArray());
});
buttonDR.onClick.AddListener(() =>
{
C2S_Login c2S = new C2S_Login();
c2S.Account = inputFieldUser.text;
c2S.Password = inputFieldPwd.text;
NetClient.Instance.Send(MsgID.C2S_LOGIN, c2S.ToByteArray());
});
MessageManager.Instance.Add(MsgID.S2C_LOGIN,OnLogin);
MessageManager.Instance.Add(MsgID.S2C_REGISTER, OnRegister);
}
private void OnRegister(object obj)
{
object[] arr = obj as object[];
byte[] data = arr[0] as byte[];
S2C_Register s2C_Register = S2C_Register.Parser.ParseFrom(data);
if (s2C_Register.Result )
{
Debug.Log(s2C_Register.Error.ToString());
}
else
{
Debug.Log(s2C_Register.Error.ToString());
}
}
private void OnLogin(object obj)
{
object[] arr = obj as object[];
byte[] data = arr[0] as byte[];
S2C_Login s2c = S2C_Login.Parser.ParseFrom(data);
if(s2c.Result)
{
Debug.Log("登入成功");
Destroy(gameObject);
}
else
{
Debug.Log(s2c.Error.ToString());
}
}
}
服务器
using System.Collections.Generic;
using Google.Protobuf;
namespace ZG5_Server
{
class LoginManager : Singleton<LoginManager>
{
public static Dictionary<string, AccountData> accountDic = new Dictionary<string, AccountData>();
public static Dictionary<int, PlayerData> playerDic = new Dictionary<int, PlayerData>();
public static List<AccountData> accountList;
public static List<PlayerData> playerList;
public void Init()
{
accountList = JsonTools.Ins.ParseJson<AccountData>("account.json");
foreach (var item in accountList)
{
accountDic.Add(item.account, item);
}
playerList = JsonTools.Ins.ParseJson<PlayerData>("player.json");
foreach (var item in playerList)
{
playerDic.Add(item.Id, item);
}
MessageCenter.Ins.Add(MsgID.C2S_REGISTER, OnRegister);
MessageCenter.Ins.Add(MsgID.C2S_LOGIN, OnLogin);
MessageCenter.Ins.Add(MsgID.C2S_LEAVE, OnLeave);
}
private void OnLeave(object obj)
{
object[] arr = obj as object[];
byte[] data = arr[0] as byte[];
Client cli = arr[1] as Client;
Leave s2c = new Leave();
s2c.PlayerId = cli.player.Id;
foreach (var item in NetManager.Ins.clients)
{
if (item != cli && item.player != null)
{
NetManager.Ins.Send(MsgID.S2C_LEAVE, s2c.ToByteArray(), item);
}
}
}
private void OnRegister(object obj)
{
object[] arr = obj as object[];
byte[] data = arr[0] as byte[];
Client cli = arr[1] as Client;
C2S_Register c2s = C2S_Register.Parser.ParseFrom(data);
S2C_Register s2c = new S2C_Register();
if (accountDic.ContainsKey(c2s.Account))
{
s2c.Result = false;
s2c.Error = "该账号已被注册";
}
else
{
AccountData account = new AccountData();
account.account = c2s.Account;
account.passwprd = c2s.Password;
account.id = accountDic.Count;
accountDic.Add(account.account, account);
accountList.Add(account);
JsonTools.Ins.WriteJson("account.json", accountList);
PlayerData player = new PlayerData();
player.Id = account.id;
player.Nick = account.account;
player.Hpmax = 100;
player.Hp = 100;
player.Pos = new V3();
player.Rot = new V3();
player.Skills.Add(0);
player.Skills.Add(1);
player.Skills.Add(2);
playerDic.Add(player.Id, player);
playerList.Add(player);
JsonTools.Ins.WriteJson("player.json", playerList);
s2c.Result = true;
s2c.Error = "注册成功";
}
NetManager.Ins.Send(MsgID.S2C_REGISTER, s2c.ToByteArray(), cli);
}
private void OnLogin(object obj)
{
object[] arr = obj as object[];
byte[] data = arr[0] as byte[];
Client cli = arr[1] as Client;
C2S_Login c2s = C2S_Login.Parser.ParseFrom(data);
S2C_Login s2c = new S2C_Login();
if (accountDic.ContainsKey(c2s.Account) && accountDic[c2s.Account].passwprd == c2s.Password)
{
s2c.Result = true;
s2c.Userid = accountDic[c2s.Account].id;
cli.player = playerDic[s2c.Userid];
foreach (var item in NetManager.Ins.clients)
{
if (item.player != null)
{
s2c.Players.Add(item.player);
if (item != cli)
{
NetManager.Ins.Send(MsgID.S2C_OTHER, cli.player.ToByteArray(), item);
}
}
}
}
else
{
s2c.Result = false;
s2c.Error = "账号或密码错误";
}
NetManager.Ins.Send(MsgID.S2C_LOGIN, s2c.ToByteArray(), cli);
}
}
}
别忘了添加Main
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ZG5_Server
{
class Program
{
static void Main(string[] args)
{
NetManager.Ins.Init();
LoginManager.Ins.Init();
while (true)
{
}
}
}
}
UI 部分
效果演示