2012年5月14日 星期一

[C#] Socket Programming in C#


Socket Programming in C#
作者:JB

如果想多了解Socket,可以讀一下這些文章:


其實Socket寫法上,不論是ServerClient,都可以使用.NET提供的TCP物件或Socket物件來達到資料傳輸的目的。(因為Socket本身就是走TCP/IP的架構)
本篇文章的目的,在於建立一個Socket Server,讓多個Client可以傳文字檔到Server,然後Server上可以顯示連線過的Client數,並在資料傳輸完成後,回傳一個文字訊息給Client

流程上大致可分為:(WinForm為例)

1          Server

1.1         為了讓Socket Server開始Listening後,仍可顯示連線的Client資訊。 故建立Socket Server後,使用BackgroundWorker來開啟Socket Server

1.2         Socket Server每次接收到新的Client要求時,再開另一條Thread來處理上傳的資料。

1.3         處理完畢後,回傳訊息給Client

1.4         結束Client的連線。

1.5         (重要) 關閉Socket Server時,要先停止Socket Listener (即第一點的BackgroundWorker),才能關閉Socket Server

2          Client


2.1         將上傳檔案名稱及其內容拆成[檔案名稱長度][檔案名稱] [檔案內容],存成Byte Array

2.2         建立TcpClientSocket物件來連線Socket Server

2.3         資料上傳完畢,利用NetworkStream來接收Server端回傳的訊息。

2.4         斷線。

一、 Server端程式碼
        程式碼包含FTServer.csSocket Server 自訂類別),ClientHandler.csClient後端處理函式),Form1.cs(主程式)

1.          FTServer類別(Socket Server自訂類別)

class FTServer
{
public IPEndPoint ipEnd;
public Socket sock;
public bool CloseSocketServer_Flg; //
結束Socket Server

/// <summary>
/// FTServer
建構
/// </summary>
public FTServer()
{
   CloseSocketServer_Flg = false;
   //
指定IPEndPoint可接受來自任何port5656IP的要求
   ipEnd = new IPEndPoint(IPAddress.Any, 5656);
   //Socket
初始化
   sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream,                    
                     ProtocolType.IP);
   //
ipEnd繫結給Socket
   sock.Bind(ipEnd);
}

/// <summary>
/// StartServer :
啟動Server
/// </summary>
public void StartServer()
{
   Console.WriteLine("Socket Server
啟動...");
   //
設定此Socket Server最多同時可連線Client數目
   sock.Listen(100);
}

/// <summary>
/// StopServer :
停止Server
/// </summary>
public void StopServer()
{
   try
   {
      sock.Close();
      Console.WriteLine("Socket Server
關閉。");
   }
   catch (Exception ex)
   {
      throw ex;
   }
}
}

2.          全域變數:包含一個FTServerBackgroundWorkerclient識別編號

 /// <summary>
/// Socket Server 類別
/// </summary>
FTServer _ftServer = null;

/// <summary>
/// 用來處理 Listener 工作的執行緒
/// </summary>
BackgroundWorker Server_bgWorker = new BackgroundWorker();

/// <summary>
/// Client 識別編號
/// </summary>
private int _ClientNo = 0;

3.          啟動Server事件(包含建立Socket Server Socket Listener
/// <summary>
/// bt_StartServer_Click事件 : 啟動Server
/// </summary>
private void bt_StartServer_Click(object sender, EventArgs e)
{
  
if (_ftServer == null)
  
{
     
_ftServer = new FTServer();
  
}

  
//指定伺服器背景作業的函式
  
Server_bgWorker.DoWork += bgWorker_DoWork;
  
//設定伺服器背景作業listener可取消
  
Server_bgWorker.WorkerSupportsCancellation = true;

  
if (!Server_bgWorker.IsBusy) //當作業背景不忙碌時
  
{
     
//設定Thread可存取元件
     
Form.CheckForIllegalCrossThreadCalls = false;
     
Server_bgWorker.RunWorkerAsync(); //執行bgWorker_DoWork
  
}
}

說明:中間的程式碼 Server_bgWorker.WorkerSupportsCancellation = true;
目的在於設定此背景執行作業(
Socket Listener)可被取消。 若沒有設定,則不允許中斷該背景執行作業。

4.          停止Server事件

先取消Socket Listener背景作業 → 停止Socket Server



/// <summary>
/// StopSocketServer 停止 Socket Server 的服務
/// </summary>
private void StopSocketServer()
{
   
try
  
{
     
if (Server_bgWorker.IsBusy)
     
{
        
//取消伺服器背景作業
        
this.Server_bgWorker.CancelAsync();
        
this.Server_bgWorker.Dispose();
     
}
     
if (_ftServer!=null && _ftServer.CloseSocketServer_Flg == false)
     
{
        
_ftServer.CloseSocketServer_Flg = true;
        
_ftServer.StopServer();
     
}
  
}
  
catch (Exception ex)
  
{
     
MessageBox.Show("[ERR] 停止 Socket Server 的服務失敗:"+ ex.Message);
  
}
}
5.          Socket Listener
/// <summary>
/// bgWorker_DoWork  : 背景作業
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
   //
待補
}

二、 Client端程式碼

1.          全域變數
/// <summary>
/// socket client 物件(連接遠端 socket server )
/// </summary>
private TcpClient _TcpClient;

/// <summary>
/// Client上傳檔案路徑
/// </summary>
private String filePath_org = "Q:\\FTP\\UploadFile.txt";

2.          將檔案名稱及內容轉換成Byte Array

/// <summary>
/// 回傳要上傳檔案的格式化成 Byte Array [檔案名稱長度][檔案名稱] [檔案內容]
/// </summary>
/// <returns></returns>
private byte[] Get_Send_Byte_Stream()
{
  
try
  
{
     
/* 取得檔案名稱 和 路徑 */
     
filePath_org = filePath_org.Replace("\\", "/");
     
String filePath = "" ;
     
String fileName = "";

     
filePath = filePath_org.Substring(0, fileName.LastIndexOf("/") + 1);
     
fileName = filePath_org.Substring(fileName.LastIndexOf("/") + 1);

     
//讀取檔案名稱並轉為Byte[]
     
byte[] fileNameByte = Encoding.ASCII.GetBytes(fileName);
     
if (fileNameByte.Length > 850 * 1024)
     
{
        
//MessageBox.Show("檔案大小超過850kb, 請嘗試其它檔案。");
        
throw new Exception("檔案大小超過850kb, 請嘗試其它檔案。");
     
}
     
//讀取檔案內容並轉為Byte[]
     
byte[] fileData = File.ReadAllBytes(filePath + fileName);

     
//取得檔案名稱的長度並轉為Byte[]
     
//byte[] fileNameLen = BitConverter.GetBytes(fileNameByte.Length);
      
byte[] fileNameLen = Encoding.ASCII.GetBytes(fileNameByte.Length.ToString());

     
//定義 clientData : 存放[檔案名稱長度][檔案名稱] [檔案內容]
     
byte[] clientData = new byte[4 + fileNameByte.Length + fileData.Length];

     
//開始放入資料到 clientData : 存放[檔案名稱長度][檔案名稱] [檔案內容]
     
fileNameLen.CopyTo(clientData, 0);
     
fileNameByte.CopyTo(clientData, 4);
     
fileData.CopyTo(clientData, 4 + fileNameByte.Length);

     
/* 測試 */
     
//String str_clientData_FileNameLen =
         System.Text.Encoding.UTF8.GetString(clientData, 0, 4);

     
//Int16 i_clientData_FileNameLen = Convert.ToInt16(str_clientData_FileNameLen);
     
//String str_clientData_FileName =
        System.Text.Encoding.UTF8.GetString(clientData, 4, fileNameByte.Length);

     
//String str_clientData_Context =
        System.Text.Encoding.UTF8.GetString(
        clientData, 4+fileNameByte.Length, clientData.Length - (4 + fileNameByte.Length));

     
//Console.WriteLine(str_clientData_Context);
     
return clientData;
  
}
  
catch (Exception ex)
  
{
     
throw ex;
  
}
}

3.          使用Socket建立Client連線
待續


4.          使用TcpClient建立Client連線
待續

沒有留言:

張貼留言