NOTE!!
The source code is deprecated! Please go to my Github: KarateJB/DotNetCore.Socket.Sample,
for the latest Socket Server/Client sample code.
注意!!
本文中的程式碼已經過時,請您參考我Github上的最新Socket Server/Client範例程式碼:KarateJB/DotNetCore.Socket.Sample
|
其實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。
= > = > = > 開始實作Socket
Server。
2.1
將上傳檔案名稱及其內容拆成[檔案名稱長度][檔案名稱] [檔案內容],存成Byte Array。
2.2
建立TcpClient或Socket物件來連線Socket Server。
2.3
資料上傳完畢,利用NetworkStream來接收Server端回傳的訊息。
2.4
斷線。
= > = > = > 開始實作Socket
Client。
一、 方案參考
1. UploadSocket.Common
: 只有放Log4Net module
2. UploadSocket.Infra
: Model
3. UploadSocket.Service
:
l 封裝Socket的類別
(UploadServer)
l Client連線後的事件處理
(ClientHandler)
4. Server
and Client as WinForm application
二、 Server
side : namespace UploadSocket.Server
1. Global
//讓BackgroundWorker識別是否已啟動服務的flag
private bool _isRunning = false;
//Client 識別編號
private int _clientNo = 0;
//Socket Server
private
UploadSocket.Service.UploadServer _ftServer = null;
//Server接收檔案的資料夾路徑
private static String UPLOAD_DIR = Application.StartupPath.ToString()
+ @"\GET\";
private BackgroundWorker
_serverBgWorker = new BackgroundWorker();
|
2. 讓Socket
Server Listener以BackGroundWorker的方式執行,底下是BackGroudWorker
DoWork的Code
//指定伺服器背景作業的函式
this._serverBgWorker.DoWork
+= bgWorker_DoWork;
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
if (this._isRunning == false){
//Intial Socket Server and open
connection
this.initialSocketServer();
//Start listening
this.socketStartListen();
}
}
private void initialSocketServer()
{
this._ftServer = new
UploadSocket.Service.UploadServer();
this._ftServer.StartServer(); //啟動Socket Server
this._isRunning = true;
}
private void socketStartListen()
{
while (this._ftServer.IsCloseSocketServerFlg
== false)
{
Socket socket4Client = null;
try{
//監聽到來自 socket client 的連線要求
socket4Client = this._ftServer.SocketPrc.Accept();
}
catch (Exception){
return; }
//累加 socket client 識別編號
this._clientNo++;
//顯示於畫面上
String connMsg = String.Format("Client 編號 ({0}) =>
Server ",
this._clientNo.ToString());
lb_Msg.Items.Add(connMsg);
//用另一條Thread 處理每一個 Socket Client 的要求
using (var handler = new UploadSocket.Service.ClientHandler(
this._clientNo, socket4Client,
UPLOAD_DIR))
{
handler.DoCommunicate();
}
}
}
|
l 以無限迴圈在這一條Thread專做Server接收Client
Request。
l 在收到一個要求時,馬上丟到另一條Thread去做處理。
l 注意在強制關閉連線(如關閉程式or關掉執行緒)時,會導致Socket
Accept方法丟出Exception, 這邊採忽略不處理。
3. Server關閉連線method
private void
stopSocketServer()
{
if (_ftServer != null){
this._ftServer.IsCloseSocketServerFlg =
true;
this._ftServer.StopServer();
this._ftServer = null;
}
if (this._serverBgWorker.IsBusy){
//取消伺服器背景作業
//this._serverBgWorker.CancelAsync();
this._serverBgWorker.Dispose();
}
this._isRunning = false;
}
|
三、 Server
side : namespace UploadSocket.Service
接下來是Server端的核心程式 …
1. UploadServer
/// <summary>
/// A Upload Server
with Socket protocal
/// </summary>
public class UploadServer
{
/// <summary>
/// Stop Socket Server flag
/// </summary>
public bool IsCloseSocketServerFlg;
/// <summary>
/// Socket protocal
/// </summary>
public Socket SocketPrc = null;
//Network endpoint
private IPEndPoint _ipEnd = null;
//Max Client connection
private static int MAX_CLIENT_NUMBER = 100;
//Constructor
public UploadServer()
{
this.IsCloseSocketServerFlg = false;
//指定IPEndPoint可接受來自任何port: 5656 的要求
this._ipEnd = new IPEndPoint(IPAddress.Any, 5656);
//Socket初始化
this.SocketPrc = new Socket(
AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.IP);
//將ipEnd繫結給Socket
this.SocketPrc.Bind(_ipEnd);
}
//StartServer : 啟動Server
public void StartServer()
{
this.SocketPrc.Listen(MAX_CLIENT_NUMBER);
}
//StopServer : 停止Server
public void StopServer()
{
this.SocketPrc.Close();
this.SocketPrc = null;
this.IsCloseSocketServerFlg = true;
}
}
|
2. ClientHandler
public class ClientHandler : IDisposable
{
//Client 識別號碼
private int _clientNo;
// Socket Client Reuqest
private Socket _socketConn;
// 上傳路徑 (不含檔名)
private String _uploadDir = String.Empty;
/// <summary>
/// 建構子
/// </summary>
/// <param
name="clientNo">Socket Client 識別號碼</param>
/// <param name="socketConn">Socket Client
Reuqest</param>
/// <param
name="uploadDir">Server檔案放置資料夾路徑</param>
public ClientHandler(int clientNo, Socket socketConn, String uploadDir)
{
this._clientNo = clientNo;
this._socketConn = socketConn;
this._uploadDir = uploadDir;
}
public void Dispose()
{
if (this._socketConn != null && this._socketConn.Connected)
{
//Disconnect connection
//非必要則由Client自行斷線
//若由Server斷線但Client仍在非同步處理會引發Client side
Exception
//this._socketConn.Shutdown(SocketShutdown.Both);
//this._socketConn.Close();
//this._socketConn = null;
}
}
/// <summary>
/// Server 與 Client 相互通訊
/// </summary>
public void DoCommunicate()
{
//產生 BackgroundWorker 負責處理每一個 socket client 的 reuqest
BackgroundWorker bgClient = new BackgroundWorker();
bgClient.DoWork += new DoWorkEventHandler(bgClient_DoWork);
bgClient.RunWorkerCompleted +=
bgClient_DoWorkCompleted;
bgClient.RunWorkerAsync();
}
// 背景作業:處理Client Request
public void bgClient_DoWork(object sender, DoWorkEventArgs e)
{
byte[] clientData = new byte[1024 * 5000];
int receivedBytesLen = 0;
if (this._socketConn.Connected == true)
{
//取得socket client連線到Server的Stream
this.getStreamData(
this._socketConn, ref clientData, out
receivedBytesLen);
if (receivedBytesLen > 0)
{
var uploadFile = new UploadFile();
this.getUploadFileInfo(
clientData,
receivedBytesLen, out
uploadFile);
this.saveUploadFile(
clientData, receivedBytesLen,
uploadFile);
}
//回傳訊息給Client
this.rtnServerMsg();
}
}
private void rtnServerMsg()
{
//正確取得 client requst,再回傳給 client
string serverResponse =
"Server => clinet(" + _clientNo + ") : 已取得資料";
byte[] sendData = Encoding.UTF8.GetBytes(serverResponse);
var netStream = new NetworkStream(this._socketConn);
netStream.Write(sendData, 0,
sendData.Length);
netStream.Flush();
Array.Resize(ref sendData, 0);
sendData = null;
netStream.Close();
netStream.Dispose();
}
private void saveUploadFile(
byte[] clientData, int receivedBytesLen,
UploadFile uploadFile)
{
BinaryWriter bWrite = new BinaryWriter(
File.Open(uploadFile.SavedFullPath, FileMode.Create));
bWrite.Write(
clientData, 4 +
uploadFile.FileNameLen,
receivedBytesLen - 4 -
uploadFile.FileNameLen);
bWrite.Close();
}
private void getUploadFileInfo(
byte[] clientData, int receivedBytesLen,
out UploadFile uploadFile)
{
uploadFile = new UploadFile();
//取得Client上傳的檔案名稱長度
uploadFile.FileNameLen =
Convert.ToInt16(Encoding.ASCII.GetString(clientData, 0,
4));
//取得Client上傳的檔案名稱
uploadFile.FileName =
Encoding.ASCII.GetString(clientData, 4,
uploadFile.FileNameLen);
//取得Client上傳的檔案內容
uploadFile.FileContent =
Encoding.UTF8.GetString(clientData,
4 + uploadFile.FileNameLen,
receivedBytesLen - 4 -
uploadFile.FileNameLen);
//如果檔案不存在,則先Create file
uploadFile.SavedFullPath = this.createFile(
Path.Combine(this._uploadDir, uploadFile.FileName));
}
private void getStreamData(
Socket mySocket, ref byte[] clientData, out int receivedBytesLen)
{
receivedBytesLen = 0;
#region 使用NetworkStream
NetworkStream netStream = new NetworkStream(mySocket);
if (netStream.DataAvailable)
{
receivedBytesLen =
netStream.Read(clientData, 0, clientData.Length);
}
netStream.Close();
netStream.Dispose();
#endregion
#region 使用System.Net.Sockets.Socket
Receive方法
//receivedBytesLen =
mySocket.Receive(clientData);
#endregion
}
private String createFile(String fileFullPath)
{
if (!File.Exists(fileFullPath))
{
File.Create(fileFullPath).Close(); //產生LOG檔
}
return fileFullPath;
}
}
|
l getStreamData方法中有兩種方式可以取得Socket Stream data。
l Socket Stream data編碼方式為:
第0
~ 3 Bytes = 檔名長度;
第4 ~ 4 + 檔名長度 = 檔名長度;
後面其餘Bytes為檔案資料。
四、 Infra
: namespace
UploadSocket.Infra
Server
side用到的Model …
public class UploadFile
{
/// <summary>
/// 檔案名稱長度
/// </summary>
public Int16 FileNameLen { get; set; }
/// <summary>
/// 檔案名稱
/// </summary>
public String FileName { get; set; }
/// <summary>
/// 檔案內容
/// </summary>
public String FileContent { get; set; }
/// <summary>
/// Server存放的完整檔案路徑
/// </summary>
public String SavedFullPath { get; set; }
}
|
五、 Server
side : Snapshot
沒有留言:
張貼留言