一、服务器
1.服务器场景布局
由 显示提示信息、保存信息、清空消息按钮和信息提示Text、Scroll View组成。
信息提示:添加Content Size Fitter
Scroll View下的Content:添加Text 和Content Size Fitter
2.Loom脚本:
因为GetCompent组件必须在unity主线程下运行,使用线程进行Socket连接,返回的数据要在Text组件中显示,所以需要使用Loom脚本。
Loom脚本使用
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
using System.Threading;
using System.Linq;
public class Loom : MonoBehaviour
{
public static int maxThreads = 8;//最大线程数量
static int numThreads;
private static Loom _current;
private int _count;
//获取Loom
public static Loom Current
{
get
{
//初始化 返回_current(loom)
Initialize();
return _current;
}
}
void Awake()
{
//初始化(已拖拽脚本)
_current = this;
initialized = true;
}
static bool initialized;//true 已初始化
//在程序运行时,创建一个单例的loom组件(防止忘记拖拽脚本)
static void Initialize()
{
//单例
if (!initialized)
{
//程序运行时执行,创建一个Loom组件
if (Application.isPlaying)
{
initialized = true;
var g = new GameObject("Loom");
_current = g.AddComponent();
}
}
}
private List _actions = new List();//行为列表
//延时排队
public struct DelayedQueueItem
{
public float time;//延时
public Action action;//行为
}
private List _delayed = new List();//延时行为列表
List _currentDelayed = new List();
#region 在主线程调用方法
public static void QueueonMainThread(Action action)
{
QueueonMainThread(action, 0f);
}
public static void QueueonMainThread(Action action, float time)
{
//当前时间不为0 写入延时列表中
if (time != 0)
{
//防止锁死(防止_delayed集合在写入的时候,其他进程进行其他操作导致错误)
lock (Current._delayed)
{
Current._delayed.Add(new DelayedQueueItem { time = Time.time + time, action = action });
}
}
else
{
//不为0 写入行为列表中
lock (Current._actions)
{
Current._actions.Add(action);
}
}
}
#endregion
#region 在线程调用的方法
public static Thread RunAsync(Action a)
{
Initialize();
while (numThreads >= maxThreads)
{
Thread.Sleep(1);
}
Interlocked.Increment(ref numThreads);
ThreadPool.QueueUserWorkItem(RunAction, a);
return null;
}
private static void RunAction(object action)
{
try
{
((Action)action)();
}
catch
{
}
finally
{
Interlocked.Decrement(ref numThreads);
}
}
#endregion
void onDisable()
{
//当前loom清空
if (_current == this)
{
_current = null;
}
}
// Use this for initialization
void Start()
{
}
List _currentActions = new List();//当前行为列表
// Update is called once per frame
void Update()
{
//行为列表 防锁死
lock (_actions)
{
_currentActions.Clear();//当前行为列表 清空
_currentActions.AddRange(_actions);//将_actions列表全部添加到_currentActions列表中
_actions.Clear();//_actions列表清空
}
foreach (var a in _currentActions)
{
a();//循环执行 a 行为
}
lock (_delayed)
{
_currentDelayed.Clear();
_currentDelayed.AddRange(_delayed.Where(d => d.time <= Time.time));
foreach (var item in _currentDelayed)
_delayed.Remove(item);
}
foreach (var delayed in _currentDelayed)
{
delayed.action();
}
}
}
服务器脚本SocketServer.cs:
using UnityEngine.UI; using UnityEngine; using System.Collections; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using System; using System.Collections.Generic; using System.Net.NetworkInformation; ////// scoket服务器监听端口脚本 /// public class SocketServer : MonoBehaviour { //public static int msgIndex=0; [SerializeField] Text txt; private Thread thStartServer;//定义启动socket的线程 void Start() { //thStartServer = new Thread(StartServer); //thStartServer.Start();//启动该线程 //Loom线程中启动方法 Loom.RunAsync(() => { StartServer(); }); } void Update() { } private void StartServer() { //const int bufferSize = 8792;//缓存大小,8192字节 IPAddress ip = IPAddress.Parse(GetIP(ADDRESSFAM.IPv4)); TcpListener tlistener = new TcpListener(ip, 9999); tlistener.Start(); //执行unity主线程方法,GetComponent需要在主线程运行 Loom.QueueonMainThread(() => { txt.text += " 服务器"+ ip.ToString() + "监听启动...... " + DateTime.Now + "n"; }); //Debug.Log("Socket服务器监听启动......"); do { GameClient server = new GameClient(tlistener.AcceptTcpClient(), txt); Loom.QueueonMainThread(() => { txt.text += " " + server._clientIP + " 客户端连接 " + DateTime.Now + "n"; }); } while (true); } void onApplicationQuit() { //线程在Loom中关闭 Loom.RunAsync(() => { thStartServer.Abort();//在程序结束时杀掉线程 }); } #region 获取本地IP地址 public enum ADDRESSFAM { IPv4, IPv6 } ////// 获取本机IP /// /// 要获取的IP类型 ///private string GetIP(ADDRESSFAM Addfam) { if (Addfam == ADDRESSFAM.IPv6 && !Socket.OSSupportsIPv6) { return null; } string output = ""; foreach (NetworkInterface item in NetworkInterface.GetAllNetworkInterfaces()) { #if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN NetworkInterfaceType _type1 = NetworkInterfaceType.Wireless80211; NetworkInterfaceType _type2 = NetworkInterfaceType.Ethernet; if ((item.NetworkInterfaceType == _type1 || item.NetworkInterfaceType == _type2) && item.OperationalStatus == OperationalStatus.Up) #endif { foreach (UnicastIPAddressInformation ip in item.GetIPProperties().UnicastAddresses) { //IPv4 if (Addfam == ADDRESSFAM.IPv4) { if (ip.Address.AddressFamily == AddressFamily.InterNetwork) { output = ip.Address.ToString(); //Debug.Log("IP:" + output); } } //IPv6 else if (Addfam == ADDRESSFAM.IPv6) { if (ip.Address.AddressFamily == AddressFamily.InterNetworkV6) { output = ip.Address.ToString(); } } } } } return output; } #endregion } public class GameClient { public static Hashtable allClient = new Hashtable(); public static List ipList = new List (); private TcpClient _client; public string _clientIP; public string _clientNick; private byte[] data; Text txt; public GameClient(TcpClient client, Text _txt) { txt = _txt; _client = client; _clientIP = client.Client.RemoteEndPoint.ToString(); if (allClient.Count <= 2) { allClient.Add(_clientIP, this); ipList.Add(_clientIP); data = new byte[_client.ReceiveBufferSize]; client.GetStream().BeginRead(data, 0, Convert.ToInt32(_client.ReceiveBufferSize), RceiveMessage, null); Loom.QueueonMainThread(() => { txt.text += " 登录成功 " + DateTime.Now + "n"; }); //sendMessage("登录成功"); } else { sendMessage("连接数量为最大,连接失败"); } } public void RceiveMessage(IAsyncResult ar) { int bytesread; try { lock (_client.GetStream()) { bytesread = _client.GetStream().EndRead(ar); } if (bytesread < 1) { allClient.Remove(_clientIP); Guangbo("服务器错误"); return; } else { string messageReceived = Encoding.UTF8.GetString(data, 0, bytesread); Loom.QueueonMainThread(() => { txt.text += " 服务器得到消息 :" + messageReceived + " " + DateTime.Now + "n"; }); //Debug.Log("服务器得到消息 :" + messageReceived); if (!messageReceived.Contains("+")) { _clientNick = messageReceived; //Debug.Log(this._clientIP + this._clientNick); } else { string[] strVect = messageReceived.Split('+'); } lock (_client.GetStream()) { Loom.QueueonMainThread(() => { txt.text += "lock (this._client.GetStream()): " + DateTime.Now + "n"; }); //Debug.Log("lock (this._client.GetStream()):"); _client.GetStream().BeginRead(data, 0, Convert.ToInt32(_client.ReceiveBufferSize), RceiveMessage, null); } } sendMessage("服务器发送时间:" + DateTime.Now); } catch (Exception ex) { Loom.QueueonMainThread(() => { txt.text += "something wrong " + DateTime.Now + "n"; }); //Debug.Log("something wrong"); allClient.Remove(_clientIP); Guangbo(_clientNick + " leave"); } } public void Guangbo(string message) { Loom.QueueonMainThread(() => { txt.text += "Guangbo:" + message + " " + DateTime.Now + "n"; }); //Debug.Log("Guangbo:" + message); foreach (DictionaryEntry c in allClient) { ((GameClient)(c.Value)).sendMessage(message); } } public void sendMessage(string message) { Loom.QueueonMainThread(() => { txt.text += "服务器获取的消息:" + _clientNick + " ,发送的消息: " + message + " " + DateTime.Now + "n"; }); //Debug.Log("服务器获取的消息:" +_clientNick + " ,发送的消息: " + message); try { NetworkStream ns; lock (_client.GetStream()) { ns = _client.GetStream(); } byte[] bytestosend = Encoding.UTF8.GetBytes(message); ns.Write(bytestosend, 0, bytestosend.Length); ns.Flush(); } catch (Exception ex) { Loom.QueueonMainThread(() => { txt.text += "sendMessage_ex:" + ex.Message + " " + DateTime.Now + "n"; }); //Debug.Log("sendMessage_ex:" + ex.Message); } } }
服务器界面管理脚本MessageManager.cs:
using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine;
using System.IO;
public class MessageManager : MonoBehaviour
{
string filePath;//文件路径
[SerializeField] Text hideTxt;//提示信息
[SerializeField] Text contentTxt;
[SerializeField] Button isShowBtn;//显示隐藏提示信息
[SerializeField] Button saveBtn;
[SerializeField] Button clearBtn;
void Start()
{
filePath = Application.streamingAssetsPath + "/serverMessage.txt";
isShowBtn.onClick.AddListener(IsShowHideEvent);
saveBtn.onClick.AddListener(SaveEvent);
clearBtn.onClick.AddListener(ClearEvent);
}
void Update()
{
//if (Input.GetKeyDown(KeyCode.Escape))
// Application.Quit();//退出
}
bool isShow = true;
private void IsShowHideEvent()
{
isShowBtn.transform.GetChild(0).GetComponent().text = isShow ?"隐藏提示":"显示提示";
hideTxt.gameObject.SetActive(isShow);
isShow = !isShow;
}
private void SaveEvent()
{
//不存在创建
if (!File.Exists(filePath))
{
FileStream fileStream = File.Create(filePath);
//释放文件 否则出现程序被占用
fileStream.Dispose();
}
File.WriteAllText(filePath, contentTxt.text);//写入信息
hideTxt.text = "信息保存在:" + filePath + " 路径下";
//刷新文件 在project目录下显示
#if UNITY_EDITOR
UnityEditor.AssetDatabase.Refresh();
#endif
}
private void ClearEvent()
{
contentTxt.text = string.Empty;
}
}
直接将两个脚本挂接到相机 或空物体上,将需要的组件拖入就好,Loom组件可以不用挂载,在使用时可以自动生成。
二、客户端
1.客户端布局
2.客户端脚本SocketClient.cs:
using UnityEngine;
using System.Collections;
using System;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Collections.Generic;
using System.ComponentModel;
using UnityEngine.UI;
public class SocketClient : MonoBehaviour
{
public int portNo = 9999;
private TcpClient _client;//TcpClient 是一个独立通信线程
byte[] data;
public InputField ip;
//获取账号 消息内容
public InputField user;
public InputField messager;
public Text server_Msg;//服务器返回信息
[SerializeField] Button login;
[SerializeField] Button send;
[SerializeField] Button clearBtn;
public void Awake()
{
//instance = this;
login.onClick.AddListener(Btn_Login);
send.onClick.AddListener(Btn_Msg);
clearBtn.onClick.AddListener(ClearMessage);
}
private void ClearMessage()
{
server_Msg.text = string.Empty;
}
string idtext = "";
bool connect = false; //判断是否登录
public void Login()
{
//获取账号
idtext = user.text;
//判断账号不为空并且未登录
if (idtext != "" && !connect)
{
connect = true;//已登陆
_client = new TcpClient();
_client.Connect(ip.text, portNo);//整合IP和端口
data = new byte[_client.ReceiveBufferSize];//初始化Byte[]数组 长度等于_client数据长度
idtext = "账号" + idtext;
SocketSendMessage(idtext);//调用SendMessage方法 将账号信息发送
_client.GetStream().BeginRead(data, 0, Convert.ToInt32(_client.ReceiveBufferSize),
ReceiveMessage, null);//作用:读取数据流 调用ReceiveMessage(IAsyncResult ar)方法
}
else
{
//Server_Msg.text = "连接失败或已连接"+"n";
//Debug.Log("连接失败或已连接");
//server_Msg.text = "连接失败或已连接n";
}
}
// 接收服务器返回值
public void ReceiveMessage(IAsyncResult ar)
{
try
{
int bytesRead;//比特数组存储的数据长度
bytesRead = _client.GetStream().EndRead(ar);//将获取的信息长度存入bytesRead EndRead(ar)获取异步访问结束后的返回值 ar的值是BeginRead回调数值
if (bytesRead < 1)//数据小于1
{
// Server_Msg.text +="数据小于1n" ;
Debug.Log("数据小于1");
return;
}
else
{
//Server_Msg.text += Encoding.UTF8.GetString(data, 0, bytesRead)+"n";
//UTF8数值
//Debug.Log("服务器返回值:" + Encoding.UTF8.GetString(data, 0, bytesRead));
string message = Encoding.UTF8.GetString(data, 0, bytesRead);
//SocketSendMessage(message);
}
}
catch (Exception ex)
{
// Server_Msg.text += "发送失败"+ex+"n";
Debug.Log("发送失败:" + ex);
}
}
//发送消息
public void SocketSendMessage(string message)
{
if (connect)
{
try
{
NetworkStream ns = _client.GetStream();
//将发送的消息转换成byte存入dataMsg中
byte[] dataMsg = Encoding.UTF8.GetBytes(message);
//write信息写入(1.byte数据信息 2.下标 3.长度)
ns.Write(dataMsg, 0, dataMsg.Length);
//用户登录发送消息
server_Msg.text += user.text + ":" + message + "n";
//ns.Read();
//Encoding.UTF8.GetString(data, 0, bytesRead)
//Debug.Log("客户端发送的消息:" + message);
//发送完毕 清楚数据
ns.Flush();
}
catch (Exception ex)
{
server_Msg.text = "信息发送错误: " + ex.Message + "n";
//Debug.Log("信息发送错误:" + ex.Message);
}
}
else
{
server_Msg.text = "无连接或账号已经登陆n";
//Debug.Log("无连接或账号已经登陆");
}
}
public void Btn_Login()
{
Login();
}
public void Btn_Msg()
{
//Btn_Login();
SocketSendMessage(messager.text);
}
}
参考连接:https://blog.csdn.net/u014732824/article/details/80665786
运行结果:
客户端:



