這裡主要寫 PC 伺服器端的邏輯,UI 使用的是 WPF,因為 WPF 比普通的 WinForm 的流式布局
更容易控制,而且比 WinForm 美觀一些,顯示截圖:
一、頁面 UI
MainWindow.xaml 檔案中布局的 XAML:
<Grid ShowGridLines="True">
<Grid.Resources>
<Style TargetType="Button">
<Setter Property="Width" Value="110"/>
<Setter Property="Height" Value="30"/>
<Setter Property="Margin" Value="10,0,10,0"/>
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80*"/>
<ColumnDefinition Width="20*"/>
</Grid.ColumnDefinitions>
<Grid ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="7*"/>
<RowDefinition Height="2*"/>
</Grid.RowDefinitions>
<!--本地 IP-->
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<Style TargetType="TextBlock">
<Setter Property="Width" Value="120"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</StackPanel.Resources>
<!--伺服器端的 IP 和端口号-->
<TextBlock x:Name="txtIP" Margin="4,14,0,14" Width="144"/>
<TextBlock x:Name="txtPort" Text="5000" Margin="10, 0, 0, 0"/>
<Button x:Name="btnBeginListening" Content="開始偵聽" HorizontalAlignment="Right" Click="btnBeginListening_Click"/>
<!--<Button x:Name="btnStop" Content="停止" HorizontalAlignment="Right" Click="btnStop_Click"/>-->
</StackPanel>
<!--消息視窗和消息發送視窗-->
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="8*"/>
<RowDefinition Height="2*"/>
</Grid.RowDefinitions>
<ScrollViewer x:Name="scroll">
<!--顯示連接配接狀态、聊天消息等-->
<TextBlock x:Name="txtResult" TextWrapping="Wrap"/>
</ScrollViewer>
<!--聊天文字輸入框-->
<TextBox x:Name="txtInput" Grid.Row="1" KeyDown="txtInput_KeyDown" TextWrapping="Wrap"/>
</Grid>
<!--操作按鈕-->
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="5*"/>
<ColumnDefinition Width="5*"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal">
<!--使用者選擇的檔案的路徑-->
<TextBox x:Name="txtFilePath" Width="150" Height="40"/>
<Button Content="選擇檔案" x:Name="btnChooseFile" Click="btnChooseFile_Click"/>
</StackPanel>
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal" Grid.Column="1">
<!--發送檔案或者發送文字消息-->
<Button Content="發送檔案" x:Name="btnSendFile" Click="btnSendFile_Click"/>
<Button Content="發送消息" x:Name="btnSendMsg" Click="btnSendMsg_Click"/>
</StackPanel>
</Grid>
</Grid>
<!--顯示請求連接配接到伺服器端的 WP 端的 IP 資訊-->
<ListBox Grid.Column="1" x:Name="listbox">
</ListBox>
</Grid>
二、定義一個伺服器端的 SocketClient 類,用來封裝與 WP 端通訊所使用的 Socket
雖然 WP 和 PC 端都是使用 C# 語言開發的,但是 WP 端和 PC 端的 Socket 類在使用方法上有一些差異,
并且 WP 端明顯是經過精簡過的,這裡就不貼出來了。直接貼出 SocketClient 類:
namespace DesktopSocketServerDemo
{
/// <summary>
/// 與用戶端的 連接配接通信類(包含了一個 與用戶端 通信的 套接字,和線程)
/// </summary>
public class SocketClient
{
Socket sokMsg;
Thread threadMsg;
// 通信操作完成時調用,用來觸發在操作完成時,在宿首頁面顯示消息提示
public event EventHandler<string> Completed;
public SocketClient(Socket sokMsg)
{
this.sokMsg = sokMsg;
this.threadMsg = new Thread(ReceiveMsg);
this.threadMsg.IsBackground = true;
this.threadMsg.Start();
}
bool isRec = true;
// 負責監聽用戶端發送來的消息
void ReceiveMsg()
{
while (isRec)
{
try
{
byte[] byteSrc = new byte[CommonHelper.FileLength];
// 從綁定的 Socket 套接字接收資料,将資料存入接收緩沖區。
int length = sokMsg.Receive(byteSrc);
DataType dataType;
byte[] bytesFile;
string strMsg;
// 轉換源 byte[] 資料内容,擷取其中的 head 和 body 的實際内容
CommonHelper.ConvertByteToData(byteSrc, out dataType, out bytesFile, out strMsg);
if (dataType.IsFile == true)
{
// 如果 body 的類型檔案,則直接将檔案存儲到 PC 端的 D: 盤根路徑下
using (FileStream file = new FileStream("d:\\" + dataType.FileName + dataType.Exten, FileMode.OpenOrCreate))
{
file.Write(bytesFile, 0, bytesFile.Length);
}
// 顯示回調消息
OnCompleted("用戶端發送的檔案:" + dataType.FileName + dataType.Exten);
OnCompleted("該檔案儲存在 D: 盤的根目錄下");
}
else
{
OnCompleted(">>用戶端:" + strMsg);
}
}
catch (Exception ex)
{
// 如果抛異常,則釋放該 Socket 對象所占用的資源
CloseConnection();
OnCompleted("SocketClient.ReceiveMsg() :" + ex.Message);
}
}
}
/// <summary>
/// 向用戶端發送消息
/// </summary>
/// <param name="strMsg"></param>
public void Send(string strMsg)
{
byte[] arrMsgFinal = CommonHelper.ConvertDataToByte(new DataType { IsFile = false }, null, strMsg);
sokMsg.Send(arrMsgFinal);
}
// 向用戶端發送檔案資料
public void SendFile(string strPath)
{
try
{
byte[] arrFileFina = CommonHelper.ConvertDataToByte(new DataType { IsFile = true }, strPath, null);
//發送檔案資料
sokMsg.Send(arrFileFina);//, 0, length + 1, SocketFlags.None);
OnCompleted("發送檔案完成");
}
catch (Exception ex)
{
OnCompleted(ex.Message);
}
}
// 關閉與用戶端連接配接
public void CloseConnection()
{
isRec = false;
sokMsg.Close();
sokMsg.Dispose();
}
void OnCompleted(string strMsg)
{
if (Completed != null)
{
Completed(null, strMsg);
}
}
}
}
三、在 MainWindow.xaml 檔案中,定義偵聽用戶端連接配接的 Socket
MainWindow.xaml 檔案相應的 Codebehind 代碼:
namespace DesktopSocketServerDemo
{
/// <summary>
/// PC 端伺服器,用來接收連接配接 PC 的 Socket 連接配接,并建立 SocketClient 類與用戶端通信
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//MultiCast cast = new MultiCast();
//cast.Startup();
txtIP.Text += "本地 IP:" + strIP;
txtPort.Text = " 端口:" + Port;
}
int Port = 5000;
// 伺服器端的 IP 位址
string strIP = CommonHelper.GetIPAddress();
//負責監聽 用戶端 的連接配接請求
Socket socketWatch = null;
// 執行 socketWatch 對象監聽請求的線程
Thread threadWatch = null;
// 開啟監聽
private void btnBeginListening_Click(object sender, RoutedEventArgs e)
{
if (socketWatch == null)
{
//執行個體化 套接字 (ip4尋址協定,流式傳輸,TCP協定)
socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint endpoint = new IPEndPoint(IPAddress.Parse(strIP), Port);
//将 監聽套接字 綁定到 對應的IP和端口
socketWatch.Bind(endpoint);
//設定 監聽隊列 長度為10(同時能夠處理 10個連接配接請求)
socketWatch.Listen(10);
threadWatch = new Thread(StartWatch);
threadWatch.IsBackground = true;
threadWatch.Start();
ShowMsg("啟動伺服器成功......\r\n");
}
else
{
ShowMsg("伺服器已經啟動");
}
}
// 循環偵聽用戶端的連接配接請求
bool isWatch = true;
// 負責與用戶端通信的對象
SocketClient socketClient;
/// <summary>
/// 被線程調用 監聽連接配接端口
/// </summary>
void StartWatch()
{
while (isWatch)
{
try
{
//監聽 用戶端 連接配接請求,但是,Accept會阻斷目前線程
//監聽到請求,立即建立負責與該用戶端套接字通信的套接字
Socket socket = socketWatch.Accept();
if (socketClient != null)
socketClient.CloseConnection();
socketClient = new SocketClient(socket);
socketClient.Completed += connection_Completed;
this.Dispatcher.BeginInvoke(new Action(delegate
{
if (socket != null && socket.RemoteEndPoint != null)
//将 通信套接字的 用戶端IP端口儲存在下拉框裡
listbox.Items.Add(new TextBlock { Text = socket.RemoteEndPoint.ToString() });
ShowMsg("接收一個用戶端連接配接......");
}), null);
}
catch (Exception ex)
{
ShowMsg(ex.Message);
socketWatch.Close();
socketWatch.Dispose();
isWatch = false;
}
}
}
//發送消息到已經連接配接到 PC 的用戶端
private void btnSendMsg_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(txtInput.Text))
{
ShowMsg("輸入内容不能為空");
return;
}
if (socketClient != null)
{
ShowMsg(txtInput.Text.Trim());
// 發送文字内容
socketClient.Send(txtInput.Text.Trim());
txtInput.Text = "";
}
else
{
MessageBox.Show("還沒有建立連接配接");
}
}
//選擇要發送的檔案
private void btnChooseFile_Click(object sender, EventArgs e)
{
Microsoft.Win32.OpenFileDialog openFileDialog = new Microsoft.Win32.OpenFileDialog();
if (openFileDialog.ShowDialog() == true)
{
txtFilePath.Text = openFileDialog.FileName;
}
}
//發送檔案
private void btnSendFile_Click(object sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(txtFilePath.Text))
{
MessageBox.Show("請先選擇一個檔案");
return;
}
//connection = new SocketClient();
if (socketClient != null)
{
// 發送檔案内容
socketClient.SendFile(txtFilePath.Text.Trim());
}
else
{
MessageBox.Show("還沒有建立連接配接");
}
}
// 操作完成
void connection_Completed(object sender, string e)
{
ShowMsg(e);
}
// 向視窗追加消息
void ShowMsg(string strMsg)
{
this.Dispatcher.BeginInvoke(new Action(delegate
{
txtResult.Text += Environment.NewLine + strMsg + Environment.NewLine;
scroll.ScrollToBottom();
}), null);
}
// 當伺服器關閉時觸發
protected override void OnClosed(EventArgs e)
{
if (socketWatch != null)
{
socketWatch.Close();
socketWatch.Dispose();
}
base.OnClosed(e);
}
// 按“Enter鍵”發送消息
private void txtInput_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
btnSendMsg_Click(null, null);
}
}
// 釋放連接配接資源
//private void btnStop_Click(object sender, RoutedEventArgs e)
//{
// if (socketClient != null)
// {
// socketClient.CloseConnection();
// }
// if (sokWatch != null)
// {
// sokWatch.Close();
// sokWatch.Dispose();
// }
//}
}
}