2015年8月20日 星期四

C#: 多執行緒, Socket-Server/Clinet

要寫到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
{
                  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));
         // RmIpSPort分別為stringint型態前者為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:

沒有留言:

張貼留言