描述:将Unity的控制台输出的信息输出到手机端
条件:有一个公网IP,或者一台服务器
原理:服务端程序在服务端运行,主要负责把消息转发给其他客户端;
客户端程序将消息发送给服务端,同时将其封装为一个dll用于在unity上引用,unity上还需要一个控制台界面,一个控制脚本。
服务端逻辑
服务端主程序.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
using System.Threading;
/// <summary>
/// 使用socket_tcp编写Unity聊天室的服务器端
/// </summary>
namespace SyncServer
{
class Program
{
/// <summary>
/// 服务器计算机所在的ip和端口
/// </summary>
//public const string m_ipAddress = "公网ip";
public const int m_port = 2233;
/// <summary>
/// 服务器Socket对象
/// </summary>
static Socket m_tcpServerServer;
/// <summary>
/// 存储连接上服务器的客户端
/// </summary>
static List<Client> m_clientList = new List<Client>();
/// <summary>
/// 线程:专门用来处理客户端的连接
/// </summary>
static Thread m_ServerThread;
static void Main(string[] args)
{
Console.WriteLine("是否启动服务器,y/n");
if (Console.ReadLine() == "y")
{
StartServerProgram();
}
}
/// <summary>
/// 启动服务器程序
/// </summary>
static void StartServerProgram()
{
m_tcpServerServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//关于IPAddress.Any,本来是想直接用外网ip的,不过我这里报错“在其上下文中,该请求的地址无效”
IPEndPoint ipEndServer = new IPEndPoint(IPAddress.Any, m_port);
try
{
m_tcpServerServer.Bind(ipEndServer);
m_tcpServerServer.Listen(10);
Console.WriteLine("服务器已经启动");
Console.WriteLine("当前的IP:" + ipEndServer.Address.ToString() + "\n" + "端口号:" + ipEndServer.Port);
}
catch (Exception ex)
{
Console.WriteLine("异常:"+ex);
}
//创建一个线程专门用来监听客户端的连接从而不影响下面要执行的代码
m_ServerThread = new Thread(AcceptClientConnect);
//监听
m_ServerThread.Start();
}
static void AcceptClientConnect()
{
while (true)
{
//监听客户端连接
Console.WriteLine("等待客户端的连接!");
Socket tcpClientSocket = m_tcpServerServer.Accept();
IPEndPoint ipEndClient = (IPEndPoint)tcpClientSocket.RemoteEndPoint;
//输出客户端的IP和端口
Console.WriteLine(ipEndClient.Address.ToString() + ":" + ipEndClient.Port.ToString());
//客户端通信逻辑放在Client里处理
Client client = new Client(tcpClientSocket);
m_clientList.Add(client);
}
}
/// <summary>
/// 将接收的消息广播出去
/// </summary>
/// <param name="message"></param>
public static void BroadCastMessage(string message)
{
if (m_clientList == null || m_clientList.Count == 0)
return;
byte[] data = Encoding.UTF8.GetBytes(message);
//用来存储已经断开连接的客户端
List<Client> notConnected = new List<Client>();
foreach (var client in m_clientList)
{
//广播
if (client.IsConnected)
{
client.SendMessageToClient(data);
}
else
{
notConnected.Add(client);
}
}
//从连接列表中删除已经断开的客户端
foreach (var item in notConnected)
{
m_clientList.Remove(item);
}
}
}
}
Client.cs 这个类还是在服务端的,用来处理通信逻辑
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading.Tasks;
using System.Threading;
using System.Net;
namespace SyncServer
{
class Client
{
/// <summary>
/// 保存与连接上来的客户端对应的Socket对象
/// </summary>
private Socket m_clientSocket;
/// <summary>
/// 获取Socket是否连接
/// </summary>
public bool IsConnected
{
get { return m_clientSocket.Connected; }
}
private Thread m_curThread;
private byte[] m_data = new byte[1024];
public Client(Socket s)
{
m_clientSocket = s;
m_curThread = new Thread(ReceiveMessage);
m_curThread.Start();
}
void ReceiveMessage()
{
while (true)
{
//在接收消息之前要判断Socket是否连接
if (m_clientSocket.Poll(10, SelectMode.SelectRead))//试图读取客户端数据,如果10毫秒内读取不到,那么判断已经断开连接,返回true
{
m_clientSocket.Shutdown(SocketShutdown.Both);
m_clientSocket.Close();
break;
}
//接收消息
int length = m_clientSocket.Receive(m_data);//如果没有接收到消息,程序会一直在这里等待
//Console.WriteLine(m_data.Length);//会输出1024
string message = Encoding.UTF8.GetString(m_data, 0, length);
//获取客户端的IP和端口
IPEndPoint ipEndClient = (IPEndPoint)m_clientSocket.RemoteEndPoint;
//输出客户端的IP和端口
if (!string.IsNullOrEmpty(message))
{
Console.WriteLine(ipEndClient.Address.ToString() + ":" + "接收到的消息:" + message);
}
//将接收的消息广播出去,将消息显示在每个客户端的聊天屏幕上
Program.BroadCastMessage(message);
}
}
/// <summary>
/// 将上面的函数再做一次优化,将字符串转换为byte数组的过程移到广播那边去,这样局只需要转换一次就能发送给所有的客户端了
/// </summary>
/// <param name="message"></param>
public void SendMessageToClient(byte[] messageData)
{
m_clientSocket.Send(messageData);
}
}
}
服务端这边就算完成了,在本地先用127.0.0.1测试之后在部署到服务器上,并运行程序等待客户端的到来。
客户端逻辑
客户端逻辑
/// <summary>
/// 这里我们不需要显示消息,只负责发送和接收
/// </summary>
public class ZyMessage
{
/// <summary>
/// 服务器计算机所在的ip和端口
/// </summary>
public const string m_ipAddress = "公网ip";
public const int m_port = 2233;
private static Socket m_clientSocket;
private static Thread m_reCeiveThread;
private static byte[] m_msgData = new byte[1024];//消息数据容器
public static string m_message;//保存消息
/// <summary>
/// 启动客户端程序
/// </summary>
public static void Start()
{
ConnentToServer();
}
/// <summary>
/// 供外部引用,将消息发送给服务器
/// </summary>
/// <param name="message"></param>
public static void RemotePrint(object message)
{
if (string.IsNullOrEmpty(message.ToString()))
{
return;
}
else
{
byte[] data = Encoding.UTF8.GetBytes(message.ToString());
m_clientSocket.Send(data);
}
}
/// <summary>
/// 接收数据方法,在新线程中执行
/// </summary>
private static void Receive()
{
while (true)
{
//在接受消息之前判断Socket是否连接
if (m_clientSocket.Connected == false)
break;
int length = m_clientSocket.Receive(m_msgData); //这里会等待接收消息,程序暂停,只有接收到消息后才会继续执行
m_message = Encoding.UTF8.GetString(m_msgData, 0, length);
}
}
/// <summary>
/// 接收消息
/// </summary>
public static void ReceiveMessage()
{
//开启一个线程专门用于接收消息
m_reCeiveThread = new Thread(Receive);
m_reCeiveThread.Start();
}
/// <summary>
/// 自定义的函数,连接到服务器
/// </summary>
public static void ConnentToServer()
{
m_clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//跟服务器建立连接
//clientSocket.Connect(new IPEndPoint(IPAddress.Parse("192.168.1.108"), 2345));
m_clientSocket.Connect(new IPEndPoint(IPAddress.Parse(m_ipAddress), m_port));
}
/// <summary>
/// 清空消息
/// </summary>
public static void Clear()
{
m_message = "";
}
/// <summary>
/// 关闭功能,断开连接
/// </summary>
public static void Shutdown()
{
//禁用Socket的发送和接收功能
m_clientSocket.Shutdown(SocketShutdown.Both);
//关闭Socket
m_clientSocket.Close();
}
}
这个类我封装为dll,在unity上直接使用。
MobileClient脚本
using UnityEngine;
using UnityEngine.UI;
using ZY;
/// <summary>
/// 这个脚本一定要挂在一个组件上,同时ZY.dll记得引用
/// 可以想象成Debug.log(),只不过你需要MobileClient._instance.Print(内容);这样来使用
/// 公司的网络可能无法使用
/// </summary>
public class MobileClient : MonoBehaviour {
public Text content; //展示消息的文本
public static MobileClient _instance; //当前的实例
private void Awake()
{
if (_instance == null)
{
_instance = this;
}
else return;
}
void Start ()
{
ZyMessage.Start(); //启动客户端
ZyMessage.ReceiveMessage(); //开启消息的接收
}
[ContextMenu("hh")] //这个方法供给单例去外部使用
public void Print(object obj)
{
ZyMessage.RemotePrint(obj); //打印到移动端,类比Debug.log(obj);
}
void Update ()
{
if (Input.GetKeyDown(KeyCode.Escape))
{
ZyMessage.Shutdown();
Application.Quit();
}
if (!string.IsNullOrEmpty(ZyMessage.m_message))
{
content.text += "\n" + ZyMessage.m_message;
ZyMessage.Clear(); //显示出来后,将消息清空一次
}
}
private void OnDestroy()
{
ZyMessage.Shutdown();
}
}
这个类完成后就可以使用了,使用方法MobileClient._instance.Print(内容);
移动端的界面搭建
一般只需要一个文本框就行了,但不能上下移动,参考(https://blog.csdn.net/wsc122181582/article/details/64438093 ),这篇博客,为了让控制台更具有cmd风格,你可以和我一样设置
然后把MobileClient脚本挂上,Text组件外部赋给脚本的content。部署到你的手机上,接下来创建一个测试场景Test,在摄像头组件添加MobileClient脚本,content不赋值,创建测试脚本
移动端的显示如上。至此,通过MobileClient._instance.Print(),你就可以在同一个场景里将消息打印到移动端,值得注意的是,如果你的打印是在Update上的,那么移动端的显示将会在一定数量后变慢,这是需要优化的,当然可以优化的地方很多,但目前于我来说够用了,如果有不足或其他问题,愿意交流。