要寫到Socket, 多執行緒就變成是必然要學會的, 當然你可以用非同步的方式來完成 Socket,改由委派的方式透過類似事件的型式來避開多緒這一塊, 但個人覺得非同步背後應當也是多緒的原理, 只是程式幫你處理好了。Any way, 不管非同步如何進行, 我們這裡要討論的是如何使用多緒。Socket 之所以要用到多緒, 是因為 Server 端在執行 Accept() 等待Client端連上線時,程式會被hand住直到 Client 端連上線為止, 如此主程式便無法再做任何事!!
這裡附帶要提的是, 一般若不是因為程式會被 hand 住或者要執行某一個很複雜耗時的函式的話, 是不需用到多執行緒的, 簡單的程式或不耗時的程式用多緒反而會不效率喔!!
要使用 Socket 和 Thread 之前要分別 using System.Sockets 和 System.Threading, 一般用到 Socket 我們會多 using System.Net 方便取得一些電腦的資訊.
using System.Net;using System.Net.Sockets;using System.Threading;
Server端:
Server 部份的功能是聆聽等待 Clinet 端連線, 並分配每一個 Client 對應一個 Socket; 另外還有接收來自 Client 的資料, 或者傳送資料給 Client 端等功能.
// 在宣告區先行宣告 Socket 物件 Socket[] SckSs; // 一般而言 Server 端都會設計成可以多人同時連線. int SckCIndex; // 定義一個指標用來判斷現下有哪一個空的 Socket 可以分配給Client 端連線;string LocalIP; int SPort = 6101;
// 聆聽private void Listen(){
// 用 Resize 的方式動態增加 Socket 的數目
Array.Resize(ref SckSs, 1);
SckSs[0] = new Socket(AddressFamily.InterNetwork, SocketType.Stream,ProtocolType.Tcp);
SckSs[0].Bind(new IPEndPoint(IPAddress.Parse(LocalIP), SPort));
// 其中 LocalIP 和 SPort 分別為 string 和 int 型態, 前者為 Server 端的IP,後者為S erver 端的Port
SckSs[0].Listen(10); // 進行聆聽; Listen( )為允許 Client 同時連線的最大數
SckSsWaitAccept(); // 另外寫一個函數用來分配 Client 端的 Socket
}
// 等待Client連線
private void SckCWaitAccept()
{
// 判斷目前是否有空的 Socket 可以提供給Client端連線
bool FlagFinded = false;
for (int i = 1; i <= SckSs.Length - 1; i++)
{
// SckSs[i] 若不為 null 表示已被實作過, 判斷是否有 Client 端連線
if (SckSs[i] != null)
{
// 如果目前第 i 個 Socket 若沒有人連線, 便可提供給下一個 Client 進行連線
if (SckSs[i].Connected == false)
if (SckSs[i].Connected == false)
{
SckCIndex = i;
FlagFinded = true;
break;
}
}
}
// 如果 FlagFinded 為 false 表示目前並沒有多餘的 Socket 可供 Client 連線
if (FlagFinded == false)
{
// 增加 Socket 的數目以供下一個 Client 端進行連線
SckCIndex = SckSs.Length;
Array.Resize(ref SckSs, SckCIndex + 1);
}
// 以下兩行為多執行緒的寫法, 因為接下來 Server 端的部份要使用 Accept() 讓Cleint 進行連線;
// 該執行緒有需要時再產生即可, 因此定義為區域性的 Thread. 命名為SckSAcceptTd;
// 在 new Thread( ) 裡為要多執行緒去執行的函數. 這裡命名為SckSAcceptProc;
Thread SckSAcceptTd = new Thread(SckSAcceptProc);
SckSAcceptTd.Start(); // 開始執行 SckSAcceptTd 這個執行緒
// 這裡要點出 SckSacceptTd 這個執行緒會在 Start( ) 之後開始執行SckSAcceptProc 裡的程式碼, 同時主程式的執行緒也會繼續往下執行各做各的.
// 主程式不用等到 SckSAcceptProc 的程式碼執行完便會繼續往下執行.
}
// 接收來自Client的連線與Client傳來的資料
private void SckSAcceptProc()
{
// 這裡加入 try 是因為 SckSs[0] 若被 Close 的話, SckSs[0].Accept() 會產生錯誤
try
{
SckSs[SckCIndex] = SckSs[0].Accept(); // 等待Client 端連線
// 為什麼 Accept 部份要用多執行緒, 因為 SckSs[0] 會停在這一行程式碼直到有 Client 端連上線, 並分配給 SckSs[SckCIndex] 給 Client 連線之後程式才會繼續往下, 若是將 Accept 寫在主執行緒裡, 在沒有Client連上來之前, 主程式將會被hand在這一行無法再做任何事了!!
// 能來這表示有 Client 連上線. 記錄該 Client 對應的 SckCIndex
int Scki = SckCIndex;
// 再產生另一個執行緒等待下一個 Client 連線
SckCWaitAccept();
long IntAcceptData;
byte[] clientData = new byte[RDataLen]; // 其中RDataLen為每次要接受來自 Client 傳來的資料長度
while (true)
{
// 程式會被 hand 在此, 等待接收來自 Client 端傳來的資料
IntAcceptData = SckSs[Scki].Receive(clientData);
// 往下就自己寫接收到來自Client端的資料後要做什麼事唄~^^”
}
}
catch
{
// 這裡若出錯主要是來自 SckSs[Scki] 出問題, 可能是自己 Close, 也可能是Client 斷線, 自己加判斷吧~
}
}
// Server 傳送資料給所有Client
private void SckSSend()
{
for (int Scki = 1; Scki <= SckSs.Length - 1; Scki++)
{
if (null != SckSs [Scki] && SckSs [Scki].Connected == true)
{
try
{
// SendS 在這裡為 string 型態, 為 Server 要傳給 Client 的字串
SckSs[Scki].Send(Encoding.ASCII.GetBytes(SendS));
}
catch
{
// 這裡出錯, 主要是出在 SckSs[Scki] 出問題, 自己加判斷吧~
}
}
}
}
Client端:
Client 端連線 Sever 端的部份比 Server 端的聆聽來得簡單多了, 至於等待接收來自 Server 端的資料和傳送資料給 Server 端的部份幾乎和Server端是一樣的, 這部份就不重覆說明了。
Socket SckSPort; // 先行宣告Socket
string RmIp;
int SPort = 6101;
// 連線
private void ConnectServer()
{
try
{
SckSPort = new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp);
SckSPort.Connect(new IPEndPoint(IPAddress.Parse(RmIp), SPort));
// RmIp和SPort分別為string和int型態, 前者為Server端的IP, 後者為Server端的Port
// 同 Server 端一樣要另外開一個執行緒用來等待接收來自 Server 端傳來的資料, 與Server概念同
Thread SckSReceiveTd = new Thread(SckSReceiveProc);
SckSReceiveTd.Start();
} catch { }
}
// 接受來自 Server 端的 SckSReceiveProc 函式內容幾乎同 Server 端, 自己寫吧;至於 Send 資料給 Server 端的部份也同 Server 端要 Send 給 Client 端一樣, 只差在Client 只有一個Socket. Good Luck!! :)
refer to:
沒有留言:
張貼留言