这里主要写 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);pleted += 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);}// 按“回车键”发送消息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();// }//} }}