* 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));

         // RmIpSPort分別為stringint型態, 前者為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

{

 

}

 

 

}

創作者介紹
創作者 2012.07.11 Start, 的頭像
Keep Practicing

2012.07.11 Start,

Keep Practicing 發表在 痞客邦 留言(25) 人氣( 54805 )