测试是在本机测试,客户端服务器都在一个机器上,IP就用127.0.0.1
服务端
> 处理TCP通讯以及监听功能
class Server
{
Dictionary<string, Player> dics=new Dictionary<string,Player>();//所有客户端信息
static void Main(string[] args)
{
new Server().Start() ;//实例化一个对象并调用方法
}
//创建套接字 Socket所有功能
public void Start()
{
IPAddress ip = IPAddress.Parse("127.0.0.1");//给IP赋值
IPEndPoint point = new IPEndPoint(ip,500);// 端点 Ip地址和端口号
// 创建Socket IP类型 (IPV4地址) Socket类型 字节流 协议类型 TCP协议
Socket mySocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//服务器端的Socket才会做绑定和监听的工作
mySocket.Bind(point);// 绑定 (端点和Socket绑定,Socket中加入IP地址和端口号) 只有在服务器端用 表明是服务器端的套接字
mySocket.Listen(20);//监听是否有连接请求 20 队列 很多人同时发
while (true)//服务器端Socket核心
{//时刻监听是否有客户端连接
//等待状态 等待客户端连接 执行了这一句 等client有数据后 客户端连接进来才会向下执行
Socket client= mySocket.Accept();//阻断函数 接收客户端的Socket 等待一个Socket调用Connect方法
Console.WriteLine("由客户端连接进来");
//主线程卡住,继续监听是否有其他客户端连接进来
//客户端连接进来后数据交互的方法:分出两条分线程 分别处理两个客户端与服务端之间的数据交互
//client.Send();//发送信息给另一方 发送的时候用的byte数组
//client.Receive();//接收对方发送的消息 也是阻断方法 接收的时候也要用byte数组
//连接进来后开辟一条分线程
SocketThread st = new SocketThread(client,dics);
new Thread(new ThreadStart(st.Run)).Start();//没有添加对象直接实例化并调用方法
}
}
}
> 服务端开辟新线程处理连接进来的客户端,进行数据交互
//SocketThread线程类 每连接进来一个客户端就开一个线程
namespace SocketServer
{
class SocketThread
{
//每一个玩家的基本信息 ’‘’‘’‘’可以封装到Player中,打点调用就可以
public const int LOGIN = 1001;//请求类型
public const int TALK = 1002;
Player player;//读一无二的
Socket socket;
string name;
//也记录一份 所有玩家数据
Dictionary<string, Player> dics;
//构造函数
public SocketThread(Socket clientSocket, Dictionary<string, Player> dics)
{
this.socket = clientSocket;//得到自己的Socket
this.dics = dics;//当前有多少玩家在线
}
//分线程进行的地方
public void Run()//deleget 回调函数
{//Socket交互,是时刻进行的,服务器和客户端必须有一方断开才能断开
//添加死循环并有阻断函数
while (true)
{
//接收数据的时候在字节流中接收
//命令类型
Byte[] bytes = new Byte[4];
socket.Receive(bytes);//阻断方法 服务器Socket接收到bytes数组中
int cmd = TypeConvert.getInt(bytes, true);//字节转int true,false倒序和正序
//也可通过字符串类型进行判定
//判断命令类型
switch (cmd)
{
case LOGIN:
//封装成方法
//信息长度 字符串长度
Byte[] str_lenth = new Byte[4];
socket.Receive(str_lenth);
int length = TypeConvert.getInt(str_lenth, true);
//信息内容 名字
Byte[] byteInfo = new Byte[length];
socket.Receive(byteInfo);
name = Encoding.UTF8.GetString(byteInfo, 0, length);//字节转字符串
//将客户端名字进行存储
if (dics.ContainsKey(name))
{
Console.WriteLine("用户名重复");
//发送给客户端 登录失败
SendPlayer("用户名重复");
}
else
{
Console.WriteLine(name + "进入房间");
//用服务器中的结构进行接收
player = new Player();
player.Name = name;
player.MySocket = socket;
dics.Add(player.Name, player);//存储
//发送给客户端登录成功
SendPlayer("登录成功 已进入房间");
}
break;
case TALK:
//字符串长度
Byte[] lenth = new Byte[4];
socket.Receive(lenth);
int strlen = TypeConvert.getInt(lenth, true);
Byte[] infos = new Byte[strlen];
socket.Receive(infos);
string content = Encoding.UTF8.GetString(infos);
content = name + "说:" + content;//构建字符串
Console.WriteLine(content);
//向客户端广发
SendAllPlayers(content);
break;
}
}
}
public void SendPlayer(string msg)
{
int length = Encoding.UTF8.GetBytes(msg).Length;
player.MySocket.Send(TypeConvert.getBytes(length, true));
player.MySocket.Send(Encoding.UTF8.GetBytes(msg));
}
void SendAllPlayers(string msg)
{
int length = Encoding.UTF8.GetBytes(msg).Length;
Byte[] lengthBytes = TypeConvert.getBytes(length,true);
Byte[] msgBytes = Encoding.UTF8.GetBytes(msg);
foreach (var it in dics)
{
Player playerData = it.Value;
playerData.MySocket.Send(lengthBytes);
playerData.MySocket.Send(msgBytes);
}
}
}
}
> 另外新线程中处理数据的时候用到字节和其它类型的转换,提供一个类型转换脚本
> 这个脚本在服务端以及客户端都需要有一份,才能保证两边解析byte数组正确
namespace LSocket.Type
{
using System.Collections;
public class TypeConvert
{
public TypeConvert()
{
}
public static byte[] getBytes(float s,bool asc){
int buf = (int)(s * 100);
return getBytes(buf,asc);
}
public static float getFloat(byte[] buf,bool asc){
int i=getInt(buf,asc);
float s=(float)i;
return s/100;
}
public static byte[] getBytes(short s, bool asc)
{
byte[] buf = new byte[2];
if (asc)
{
for (int i = buf.Length - 1; i >= 0; i--)
{
buf[i] = (byte)(s & 0x00ff);
s >>= 8;
}
}
else
{
for (int i = 0; i < buf.Length; i++)
{
buf[i] = (byte)(s & 0x00ff);
s >>= 8;
}
}
return buf;
}
public static byte[] getBytes(int s, bool asc)
{
byte[] buf = new byte[4];
if (asc)
for (int i = buf.Length - 1; i >= 0; i--)
{
buf[i] = (byte)(s & 0x000000ff);
s >>= 8;
}
else
for (int i = 0; i < buf.Length; i++)
{
buf[i] = (byte)(s & 0x000000ff);
s >>= 8;
}
return buf;
}
public static byte[] getBytes(long s, bool asc)
{
byte[] buf = new byte[8];
if (asc)
for (int i = buf.Length - 1; i >= 0; i--)
{
buf[i] = (byte)(s & 0x00000000000000ff);
s >>= 8;
}
else
for (int i = 0; i < buf.Length; i++)
{
buf[i] = (byte)(s & 0x00000000000000ff);
s >>= 8;
}
return buf;
}
public static short getShort(byte[] buf, bool asc)
{
if (buf == null)
{
//throw new IllegalArgumentException("byte array is null!");
}
if (buf.Length > 2)
{
//throw new IllegalArgumentException("byte array size > 2 !");
}
short r = 0;
if (!asc)
for (int i = buf.Length - 1; i >= 0; i--)
{
r <<= 8;
r |= (short)(buf[i] & 0x00ff);
}
else
for (int i = 0; i < buf.Length; i++)
{
r <<= 8;
r |= (short)(buf[i] & 0x00ff);
}
return r;
}
public static int getInt(byte[] buf, bool asc)
{
if (buf == null)
{
// throw new IllegalArgumentException("byte array is null!");
}
if (buf.Length > 4)
{
//throw new IllegalArgumentException("byte array size > 4 !");
}
int r = 0;
if (!asc)
for (int i = buf.Length - 1; i >= 0; i--)
{
r <<= 8;
r |= (buf[i] & 0x000000ff);
}
else
for (int i = 0; i < buf.Length; i++)
{
r <<= 8;
r |= (buf[i] & 0x000000ff);
}
return r;
}
public static long getLong(byte[] buf, bool asc)
{
if (buf == null)
{
//throw new IllegalArgumentException("byte array is null!");
}
if (buf.Length > 8)
{
//throw new IllegalArgumentException("byte array size > 8 !");
}
long r = 0;
if (!asc)
for (int i = buf.Length - 1; i >= 0; i--)
{
r <<= 8;
r |= (buf[i] & 0x00000000000000ff);
}
else
for (int i = 0; i < buf.Length; i++)
{
r <<= 8;
r |= (buf[i] & 0x00000000000000ff);
}
return r;
}
}
}
客户端
> 处理客户端网络连接 Socket连接 以及简单的创建UI界面
public class ClientSocket : MonoBehaviour {
SocketThread st; //客户端的分线程
public string message = " ";
public string talk = " ";
//服务器中用名字判断是否注册过
public string username = "请输入用户名";
void OnGUI()
{
if (st == null)
{
username = GUILayout.TextField(username);//玩家输入
if (GUILayout.Button("连接服务器"))
{
Connect();
}
}
else
{
Talk();
}
}
void Connect()
{
IPAddress ip = IPAddress.Parse("192.168.1.4");
IPEndPoint point = new IPEndPoint(ip, 500);
Socket mySocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//客户端连接Socket(套接字:记录了IP,端口,TCP协议)
mySocket.Connect(ip,500);//连接 先三次握手,仅仅是三次握手 成功后,服务器中的client才能接收到客户端的数据
//开辟自己用于网络交互的线程
st = new SocketThread(mySocket,this);//this 当前类型的对象
st.myThread = new Thread(new ThreadStart(st.Run));
st.myThread.Start();
//向服务器发送数据
st.DoLogin(username);
}
void Talk()
{
message=GUI.TextArea(new Rect(0,0,200,300),message);//显示框 创建多行文本字段
talk = GUI.TextField(new Rect(0,300,200,50),talk);//输入框 创建单行文本字段
if (GUI.Button(new Rect(200, 300, 80, 50), "发送"))
{
st.SendMessage(talk);
}
}
}
> 处理与服务端的信息
//玩家发出的命令类型
class CommandID
{
public static int LOGIN = 1001;
public static int TALK = 1002;
}
//客户端也要开线程
public class SocketThread {
public Thread myThread;//我的分线程
Socket socketClient;//我的套接字
ClientSocket demo;//记录绑定在人物身上的脚本
//构造方法
public SocketThread(Socket socket, ClientSocket demo)
{
this.socketClient = socket;
this.demo = demo;
}
//跑线程
public void Run()
{
while (true)
{
try
{
string info = ReceiveString();
demo.message += "\r\n" + info;//“\r\n”换行符
}
catch (System.Exception ex)
{
MonoBehaviour.print(ex);
myThread.Abort();//终止此线程
}
}
}
//把数据发送给服务器 登录
public void DoLogin(string username)
{
try
{
//发送用户名给服务器
Send(CommandID.LOGIN);//请求类型
Send(username);//字符串
}
catch(System.Exception ex)
{//出错了打印出来
MonoBehaviour.print(ex.ToString());//新的打印语句
}
}
//发送消息
public void SendMessage(string talk)
{
try
{
Send(CommandID.TALK);
Send(talk);
}
catch(System.Exception ex)
{//错误输出
MonoBehaviour.print(ex.ToString());
}
}
//类型为int型
void Send(int data)
{//先转化 int-》byte[]
byte[] datas = TypeConvert.getBytes(data,true);
//Send在服务器和客户端都可以调用
socketClient.Send(datas);//Socket中的静态方法 当前套接字将数据传给和我连接的另一个套接字
}
//用户名等信息为字符串
void Send(string data)
{//先转化 string->byte[]
byte[] datas = Encoding.UTF8.GetBytes(data);
//调用上面的Send方法,传递的是int值 字符串的长度
Send(datas.Length);//先将长度传递过去,在服务器开辟号后面的信息字节长度
socketClient.Send(datas);
}
string ReceiveString()
{
int length =ReveiveInt();//接收到消息的长度
byte[] recvBytes=new byte[length];//为消息内容开的字节数组长度
socketClient.Receive(recvBytes);
string data = Encoding.UTF8.GetString(recvBytes);//byte数组转化 为字符串
return data;
}
int ReveiveInt()
{
byte[] bytes=new byte[4];
socketClient.Receive(bytes,4,0);//??参数意义
int data=TypeConvert.getInt(bytes,true);
return data;
}
}
> 客户端也要保存一份上面的TypeConvert脚本保证两边解析一样
同时注意的是,客户端和服务端都需要在连接成功后开辟新的线程处理数据交互,否则主线程会卡死。
对应代码注意添加引用,尤其是TypeConvert脚本的引用
下面是运行截图: