* 2016/5/10 已將範例修改成 Server 端和 Client 端可以互傳固定字串~ Server端和Client端的程式碼可以直接copy使用~ 有問題歡迎提問~
要寫到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 = "xxx.xxx.xxx.xxx"; // 其中 xxx.xxx.xxx.xxx 為本機IP
int SPort = 6101;
int RDataLen = 5; // 這裡的RDataLen為要傳送資料的長度, 這裡我隨用5個長度, 傳送 "ABCDE" 給Client端
// Hi All, 因為我寫Socket都是在傳電文用, 所以我習慣傳送固定長度~ ,此文沒有在處理非固定長度的資料喔~
// 聆聽
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 同時連線的最大數
SckSWaitAccept(); // 另外寫一個函數用來分配 Client 端的 Socket
}
// 等待Client連線
private void SckSWaitAccept()
{
// 判斷目前是否有空的 Socket 可以提供給Client端連線
bool FlagFinded = false;
for (int i = 1; i < SckSs.Length; i++)
{
// SckSs[i] 若不為 null 表示已被實作過, 判斷是否有 Client 端連線
if (SckSs[i] != null)
{
// 如果目前第 i 個 Socket 若沒有人連線, 便可提供給下一個 Client 進行連線
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 連線
SckSWaitAccept();
long IntAcceptData;
byte[] clientData = new byte[RDataLen]; // 其中RDataLen為每次要接受來自 Client 傳來的資料長度
while (true)
{
// 程式會被 hand 在此, 等待接收來自 Client 端傳來的資料
IntAcceptData = SckSs[Scki].Receive(clientData);
// 往下就自己寫接收到來自Client端的資料後要做什麼事唄~^^”
// 因為Client端傳ABCDE過來, 所以可以試著將Byte陣列轉成字串列印出來看看~
string S = Encoding.Default.GetString(clientData);
Console.WriteLine(S);
}
}
catch
{
// 這裡若出錯主要是來自 SckSs[Scki] 出問題, 可能是自己 Close, 也可能是 Client 斷線, 自己加判斷吧~
}
}
// Server 傳送資料給所有Client
private void SckSSend()
{
for (int Scki = 1; Scki < SckSs.Length; Scki++)
{
if (null != SckSs [Scki] && SckSs [Scki].Connected == true)
{
try
{
string SendS = "ABCDE"; // SendS 在這裡為 string 型態, 為 Server 要傳給 Client 的字串, 我測試傳送 字串 "ABCDE" 給Client端
SckSs[Scki].Send(Encoding.ASCII.GetBytes(SendS));
}
catch
{
// 這裡出錯, 主要是出在 SckSs[Scki] 出問題, 自己加判斷吧~
}
}
}
}
Client端:
Client 端連線 Sever 端的部份比 Server 端的聆聽來得簡單多了, 至於等待接收來自 Server 端的資料和傳送資料給 Server 端的部份幾乎和Server端是一樣的, 這部份就不重覆說明了。
Socket SckSPort; // 先行宣告Socket
string RmIp = "xxx.xxx.xxx.xxx"; // 其中 xxx.xxx.xxx.xxx 為Server端的IP
int SPort = 6101;
int RDataLen = 5; // 此文Server端和Client端都是用固定長度5在傳送資料~ 可以針對自己的需要改長度
// 連線
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 { }
}
// SckSReceiveProc 是接受來自 Server 端的資料其函式內容幾乎同 Server 端的 SckSAcceptProc 接收資料的Code~ , 只差在 Client 只有一個Socket.
private void SckSReceiveProc()
{
try
{
long IntAcceptData;
byte[] clientData = new byte[RDataLen];
while (true)
{
// 程式會被 hand 在此, 等待接收來自 Server 端傳來的資料
IntAcceptData = SckSPort.Receive(clientData);
// 往下就自己寫接收到來自Server端的資料後要做什麼事唄~^^”
string S = Encoding.Default.GetString(clientData);
Console.WriteLine(S);
}
}
catch
{
}
}
// 當然 Client 端也可以傳送資料給Server端~ 和 Server 端的SckSSend一樣, 只差在Client端只有一個Socket
private void SckSSend()
{
try
{
string SendS = "ABCDE";
SckSPort.Send(Encoding.ASCII.GetBytes(SendS));
}
catch
{
}
}
請先 登入 以發表留言。