2019年10月19日─大幅度修改了程式碼,使用起來更直覺了
新的程式在此
======================================================================================================
2016年6月24日 00:51,修改了程式碼,刪掉多餘的部分和BUG
C#的Socket連線要使用非同步的方式設計!不然在等待連線和資料的時候就會卡著不動了...
至於非同步的方式可以和這影片所用的方式一樣,或者和我一樣使用Thread的方式編寫。
有幾篇文章建議先看一下
或許有人在想為什麼要用Socket呢?Unity不是有內建連線功能了?
但這只能用在Unity之間的連線,若是要不同語言甚至是不同IDE做出的程式連線(e.g.使用VS做的程式和Unity之間連線),就要使用到Socket
先創兩個Project,分別命名為Server和Client
(當然,想在同個Project內也是可以,不過要分成兩個Scene,然後分別將這兩個Scene輸出)
在Server儲存一個場景和一些C# script,內容如下
Client也是一樣的步驟
都完成後就開始撰寫程式碼了~~
首先編寫ServerThread.cs
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
class ServerThread
{
//結構,儲存IP和Port
private struct Struct_Internet
{
public string ip;
public int port;
}
private Socket serverSocket;//伺服器本身的Socket
private Socket clientSocket;//連線使用的Socket
private Struct_Internet internet;//宣告結構物件
public string receiveMessage;
private string sendMessage;
private Thread threadConnect;//連線的Thread
private Thread threadReceive;//接收資料的Thread
public ServerThread(AddressFamily family, SocketType socketType, ProtocolType protocolType, string ip, int port)
{
serverSocket = new Socket(family, socketType, protocolType);//new server socket object
internet.ip = ip;//儲存IP
internet.port = port;//儲存Port
receiveMessage = null;//初始化接受的資料
}
//開始傾聽連線需求
public void Listen()
{
//伺服器本身的IP和Port
serverSocket.Bind(new IPEndPoint(IPAddress.Parse(internet.ip), internet.port));
serverSocket.Listen(1);//最多一次接受多少人連線
}
//開始連線
public void StartConnect()
{
//由於連線成功之前程式都會停下,所以必須使用Thread
threadConnect = new Thread(Accept);
threadConnect.IsBackground = true;//設定為背景執行續,當程式關閉時會自動結束
threadConnect.Start();
}
//停止連線
public void StopConnect()
{
try
{
clientSocket.Close();
}
catch (Exception)
{
}
}
//寄送訊息
public void Send(string message)
{
if (message == null)
throw new NullReferenceException("message不可為Null");
else
sendMessage = message;
SendMessage();//由於資料傳遞速度很快,沒必要使用Thread
}
public void Receive()
{
//先判斷原先的threadReceive若還在執行接收檔案的工作,則直接結束
if (threadReceive != null && threadReceive.IsAlive == true)
return;
//由於在接收到所有資料前都會停下,所以必須使用Thread
threadReceive = new Thread(ReceiveMessage);
threadReceive.IsBackground = true;//設定為背景執行續,當程式關閉時會自動結束
threadReceive.Start();
}
private void Accept()
{
try
{
clientSocket = serverSocket.Accept();//等到連線成功後才會往下執行
//連線成功後,若是不想再接受其他連線,可以關閉serverSocket
//serverSocket.Close();
}
catch (Exception)
{
}
}
private void SendMessage()
{
try
{
if (clientSocket.Connected == true)//若成功連線才傳遞資料
{
//將資料進行編碼並轉為Byte後傳遞
clientSocket.Send(Encoding.ASCII.GetBytes(sendMessage));
}
}
catch (Exception)
{
}
}
private void ReceiveMessage()
{
if (clientSocket.Connected == true)
{
byte[] bytes = new byte[256];//用來儲存傳遞過來的資料
long dataLength = clientSocket.Receive(bytes);//資料接收完畢之前都會停在這邊
//dataLength為傳遞過來的"資料長度"
receiveMessage = Encoding.ASCII.GetString(bytes);//將傳過來的資料解碼並儲存
}
}
}
再來是Server.cs
using UnityEngine;
using System.Net.Sockets;
using System.Collections;
public class Server : MonoBehaviour
{
private ServerThread st;
private bool isSend;//儲存是否發送訊息完畢
private void Start()
{
//開始連線,設定使用網路、串流、TCP
st = new ServerThread(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp, "127.0.0.1", 8000);
st.Listen();//讓Server socket開始監聽連線
st.StartConnect();//開啟Server socket
isSend = true;
}
private void Update()
{
if (st.receiveMessage != null)
{
Debug.Log("Client:" + st.receiveMessage);
st.receiveMessage = null;
}
if (isSend == true)
StartCoroutine(delaySend());//延遲發送訊息
st.Receive();
}
private IEnumerator delaySend()
{
isSend = false;
yield return new WaitForSeconds(1);//延遲1秒後才發送
st.Send("Hello~ My name is Server");
isSend = true;
}
private void OnApplicationQuit()//應用程式結束時自動關閉連線
{
st.StopConnect();
}
}
再來是ClientThread.cs,和ServerThread.cs類似
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
class ClientThread
{
public struct Struct_Internet
{
public string ip;
public int port;
}
private Socket clientSocket;//連線使用的Socket
private Struct_Internet internet;
public string receiveMessage;
private string sendMessage;
private Thread threadReceive;
private Thread threadConnect;
public ClientThread(AddressFamily family, SocketType socketType, ProtocolType protocolType, string ip, int port)
{
clientSocket = new Socket(family, socketType, protocolType);
internet.ip = ip;
internet.port = port;
receiveMessage = null;
}
public void StartConnect()
{
threadConnect = new Thread(Accept);
threadConnect.Start();
}
public void StopConnect()
{
try
{
clientSocket.Close();
}
catch(Exception)
{
}
}
public void Send(string message)
{
if (message == null)
throw new NullReferenceException("message不可為Null");
else
sendMessage = message;
SendMessage();
}
public void Receive()
{
if (threadReceive != null && threadReceive.IsAlive == true)
return;
threadReceive = new Thread(ReceiveMessage);
threadReceive.IsBackground = true;
threadReceive.Start();
}
private void Accept()
{
try
{
clientSocket.Connect(IPAddress.Parse(internet.ip), internet.port);//等待連線,若未連線則會停在這行
}
catch (Exception)
{
}
}
private void SendMessage()
{
try
{
if (clientSocket.Connected == true)
{
clientSocket.Send(Encoding.ASCII.GetBytes(sendMessage));
}
}
catch (Exception)
{
}
}
private void ReceiveMessage()
{
if (clientSocket.Connected == true)
{
byte[] bytes = new byte[256];
long dataLength = clientSocket.Receive(bytes);
receiveMessage = Encoding.ASCII.GetString(bytes);
}
}
}
再來是Client.cs,和Server.cs類似
using UnityEngine;
using System.Collections;
using System.Net.Sockets;
public class Client : MonoBehaviour
{
private ClientThread ct;
private bool isSend;
private bool isReceive;
private void Start()
{
ct = new ClientThread(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp, "127.0.0.1", 8000);
ct.StartConnect();
isSend = true;
}
private void Update()
{
if (ct.receiveMessage != null)
{
Debug.Log("Server:" + ct.receiveMessage);
ct.receiveMessage = null;
}
if (isSend == true)
StartCoroutine(delaySend());
ct.Receive();
}
private IEnumerator delaySend()
{
isSend = false;
yield return new WaitForSeconds(1);
ct.Send("Hello~ My name is Client");
isSend = true;
}
private void OnApplicationQuit()
{
ct.StopConnect();
}
}
都好之後就可以直接Run了~~
結果如下
ServerThread.cs
ClientThread.cs
這兩個程式碼可以直接搬到VS使用(當然只能給C#,不過若是打包成dll也可以給其他語言使用)
由於當前程式碼只能1對1連線,若要1對多則ServerThread內的clientSocket要改為陣列或List,當有人連線後就將該clientSocket內的一個元素分配給該人

非常感謝!非常完整又有註解。
照著這個程式碼打了之後無法使用 沒有跳出回傳訊息....
若在Unity上可以執行,手機上卻無法執行(以Android來說),那就是新的作業系統對網路連線的需求有改動,要重新去找新的解法 若再Unity上無法執行,請確認IP是否皆為區網內或者是實體IP
請問文中的方式跟影片中的方式最大的差別在哪裡呢?
另開Thread的方式無法使用一些限用於main thread的元件,而Ansy可以 Thread設計方式較為簡單,Ansy稍微複雜
可以請教你一些問題嗎? 我現在有一個軟體的client端是以get方式傳遞 我用您的server只會讀取一次就停止了,讓我困惑很久
啊啊啊...痞客邦沒有來信通知有人留言,這麼晚才看到你的留言 get...是透過網頁的方式進行連線嗎?如果是的話可以參考這篇看看能否解決您的問題 https://dotblogs.com.tw/yc421206/archive/2013/11/11/127593.aspx
Fatal error in gc too many threads 是怎麼了,呼叫太多次嗎?
GC指的是垃圾回收機制 根據這錯誤訊息,推斷可能是記憶體不足(特效開太多或無用的資源沒有銷毀) 另外Unity的Update不管上個frame的update是否有執行完畢,在當前frame都會再次呼叫,有可能是這個原因導致你跳出這個錯誤
怎麼會有人把server寫在unity裡面 太ㄏ了吧
應用情況百百種,只是你做的少沒遇到而以 再者也沒有人說不能把server寫在Unity內
我尝试按照最后的的说明将client改成了List但还是无法接入多个客户端,可以说明的再详细一些吗
同上 請問有多人相關的文件可以參考嗎? 小弟將client改為陣列後 不太知道如何去分配