Socket
Programming in C#
作者:JB
如果想多了解Socket,可以讀一下這些文章:
其實Socket寫法上,不論是Server或Client,都可以使用.NET提供的TCP物件或Socket物件來達到資料傳輸的目的。(因為Socket本身就是走TCP/IP的架構)
本篇文章的目的,在於建立一個Socket Server,讓多個Client可以傳文字檔到Server,然後Server上可以顯示連線過的Client數,並在資料傳輸完成後,回傳一個文字訊息給Client。
流程上大致可分為:(以WinForm為例)
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.1
將上傳檔案名稱及其內容拆成[檔案名稱長度][檔案名稱] [檔案內容],存成Byte Array。
2.2
建立TcpClient或Socket物件來連線Socket Server。
2.3 資料上傳完畢,利用NetworkStream來接收Server端回傳的訊息。
2.4 斷線。
一、 Server端程式碼
程式碼包含FTServer.cs(Socket Server 自訂類別),ClientHandler.cs(Client後端處理函式),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可接受來自任何port5656的IP的要求
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;
}
}
public Socket sock;
public bool CloseSocketServer_Flg; //結束Socket Server
/// <summary>
/// FTServer 建構
/// </summary>
public FTServer()
{
CloseSocketServer_Flg = false;
//指定IPEndPoint可接受來自任何port5656的IP的要求
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.
全域變數:包含一個FTServer、BackgroundWorker、client識別編號。
/// <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)可被取消。 若沒有設定,則不允許中斷該背景執行作業。
/// <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);
}
}
先取消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)
{
//待補
}
/// <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";
/// <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;
}
}
/// <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連線
待續…
待續…
沒有留言:
張貼留言