1.主要思路:
(1) 服务端开启监听线程,等待客户端的连接。 每个socket连接放到独立线程中处理。
(2) 服务端和客户端使用约定的消息格式通信。对于比较复杂的消息(如向服务端传递一个实例),可以使用json封装传输。
(3) 每个连接的客户端,注册唯一的ClientID,在服务端以此来区分消息的来源。
2.代码构成
实现该样例包含两个cs的客户端程序。 分别为 服务端 和 客户端。
3.主要代码:
(1) 服务端 xaml
<Window x:Class="WpfApplication1.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="432" Width="817"> <Grid> <TextBox Height="23" HorizontalAlignment="Left" Margin="16,20,0,0" Name="tbox_listner_ip" Text="127.0.0.1" VerticalAlignment="Top" Width="114" /> <TextBox Height="23" HorizontalAlignment="Left" Margin="136,20,0,0" Name="tbox_listner_port" Text="2000" VerticalAlignment="Top" Width="43" /> <Button Content="开启监听" Height="23" HorizontalAlignment="Right" Margin="0,22,440,0" Name="button2" VerticalAlignment="Top" Width="75" Click="button2_Click" /> <RichTextBox Height="132" HorizontalAlignment="Left" Margin="470,20,0,0" Name="richTextBox1" VerticalAlignment="Top" Width="313" /> <Grid Margin="19,82,354,287"> <Grid.ColumnDefinitions> <ColumnDefinition Width="260" /> <ColumnDefinition Width="80" /> <ColumnDefinition Width="60*" /> </Grid.ColumnDefinitions> <Label Grid.Column="0" Content="来自于" FontWeight="Bold" Padding="0" Height="18"></Label> <Label Grid.Column="1" Content="消息号" FontWeight="Bold" Padding="0" Height="18"></Label> <Label Grid.Column="2" Content="是否处理" FontWeight="Bold" Padding="0" Height="18"></Label> </Grid> <ListBox Height="278" HorizontalAlignment="Left" Margin="19,103,0,0" Name="listBox2" VerticalAlignment="Top" Width="422"> <ListBox.ItemTemplate> <DataTemplate> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="260" /> <ColumnDefinition Width="80" /> <ColumnDefinition Width="60*" /> </Grid.ColumnDefinitions> <Label Grid.Column="0" Padding="0" Height="25" Content="{Binding Path=clientID}"></Label> <Label Grid.Column="1" Padding="0" Height="25" Content="{Binding Path=messageContent}"></Label> <RadioButton Grid.Column="2" Margin="0,5,0,0" Height="25" IsChecked="{Binding Path=handleFlag}"></RadioButton> </Grid> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <RichTextBox Height="132" HorizontalAlignment="Left" Margin="470,193,0,0" Name="richTextBox2" VerticalAlignment="Top" Width="313" /> </Grid></Window>
(2) 服务端 cs
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Windows;using System.Windows.Controls;using System.Windows.Data;using System.Windows.Documents;using System.Windows.Input;using System.Windows.Media;using System.Windows.Media.Imaging;using System.Windows.Shapes;using System.Net.Sockets;using System.Threading;using System.Net;using Newtonsoft.Json;namespace WpfApplication1{ /// <summary> /// Window1.xaml 的交互逻辑 /// </summary> public partial class Window1 : Window { int BufferSize = 1024; private bool executeFlag = false; List<MySocketMessage> list_message = new List<MySocketMessage>(); private int ClientFlag = 1; private Socket socket_server; public Window1() { InitializeComponent(); } private void button2_Click(object sender, RoutedEventArgs e) { button2.IsEnabled = false; int listner_port = Convert.ToInt32(tbox_listner_port.Text); string listner_ip = tbox_listner_ip.Text; executeFlag = true; IPAddress ip_server = IPAddress.Parse(listner_ip); IPEndPoint ipEndPoint = new IPEndPoint(ip_server, listner_port); socket_server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket_server.Bind(ipEndPoint); socket_server.Listen(10); //开始监听 (有监听就接收) 设置最多十个排队连接请求 Thread thread_serve = new Thread(new ThreadStart(CreateListner)); thread_serve.Name = "server thread"; thread_serve.Start(); } private void CreateListner() { while (true) { Socket socketConn = socket_server.Accept(); //为建立的连接创建socket EndPoint point = socketConn.RemoteEndPoint; AppendStrToRtbox("\n连接来自:" + point.ToString()); Thread receiveThread = new Thread(ServerHandleMessage); receiveThread.Start(socketConn); } } /// <summary> /// 接收消息 /// </summary> /// <param name="clientSocket"></param> private void ServerHandleMessage(object clientSocket) { Socket socketConn = clientSocket as Socket; string clientID = ""; while (true) { if (socketConn.Connected == true) { try { string recvStr = GetReceiveStr(socketConn); if (recvStr != "") { if (recvStr.Contains("connectRequest")) { int i_pos = recvStr.IndexOf("connectRequest") + "connectRequest".Length; AppendStrToRtbox("分割字符串前:" + recvStr); recvStr = recvStr.Substring(i_pos, recvStr.Length - i_pos); Send_Mess(socketConn, "Confirm"); AppendStrToRtbox(" 连接已被确认,客户ID:" + recvStr); clientID = recvStr; continue; } else if (recvStr.Contains("jsonMessage:")) { int i_pos = recvStr.IndexOf("jsonMessage:") + "jsonMessage:".Length; AppendStrToRtbox("分割字符串前:" + recvStr); recvStr = recvStr.Substring(i_pos, recvStr.Length - i_pos); AppendStrToRtbox("分割字符串后:" + recvStr); MySocketMessage socketMessage = JsonConvert.DeserializeObject<MySocketMessage>(recvStr); list_message.Add(socketMessage); } MySocketMessage message_tmp = list_message.Find(p => p.clientID == clientID && p.isDoing == true); if (message_tmp != null) { message_tmp.isDoing = false; message_tmp.dt_message_handleTime = DateTime.Now; RefreshListBox(); string sendStr = "ReturnCode:" + message_tmp.messageContent; Send_Mess(socketConn, sendStr); AppendStrToRtbox_two(String.Format("消息来自{0},指令:{1}", clientID, message_tmp.messageContent)); AppendStrToRtbox(" 执行请求指令:" + recvStr); continue; } string sendStr2 = "Confirm"; Send_Mess(socketConn, sendStr2); } } catch (Exception ce) { executeFlag = false; AppendStrToRtbox("服务端检测到客户端连接断开"); } } else { executeFlag = false; AppendStrToRtbox("服务端检测到客户端连接断开"); } Thread.Sleep(50); } } public string GetReceiveStr(Socket socket_tmp) { //接受从服务器返回的信息 byte[] recvBytes = new byte[BufferSize]; int bytes; string recvStr = ""; bytes = socket_tmp.Receive(recvBytes, recvBytes.Length, 0); //从服务器端接受返回 if (bytes > 0) { recvStr += Encoding.ASCII.GetString(recvBytes, 0, bytes); Console.WriteLine("\nGetReceiveStr 客户端接收到服务端的返回:" + recvStr); } return recvStr; } /// <summary> /// 发送消息指令 /// </summary> /// <param name="sendMessage"></param> public void Send_Mess(Socket socket_tmp,string sendMessage) { if (socket_tmp.Connected == false) { return; } byte[] send_bytes = Encoding.ASCII.GetBytes(sendMessage); //把字符串编码为字节 if (send_bytes.Length > 0) { socket_tmp.Send(send_bytes, send_bytes.Length, 0); } } private void AppendStrToRtbox(string content) { this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(() => { richTextBox1.AppendText("\n" + content); richTextBox1.ScrollToEnd(); } )); } private void AppendStrToRtbox_two(string content) { this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(() => { richTextBox2.AppendText("\n" + content); richTextBox2.ScrollToEnd(); } )); } private void RefreshListBox() { this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(() => { listBox2.ItemsSource = new List<MySocketMessage>(); listBox2.ItemsSource = list_message; } )); } } public class MySocketMessage { public MySocketMessage() { handleFlag = false; isDoing = false; } public string clientID { get; set; } public string messageContent { get; set; } public DateTime dt_message_addTime { get; set; } public DateTime dt_message_handleTime { get; set; } public bool handleFlag { get; set; } public bool isDoing { get; set; } }}
(3) 客户端 xaml
<Window x:Class="WpfApplication1.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="432" Width="734"> <Grid> <TextBox Height="23" HorizontalAlignment="Left" Margin="18,40,0,0" Name="tbox_client_ip" Text="127.0.0.1" VerticalAlignment="Top" Width="114" /> <TextBox Height="23" HorizontalAlignment="Left" Margin="138,40,0,0" Name="tbox_client_port" Text="2000" VerticalAlignment="Top" Width="43" /> <Button Content="建立连接,并发送消息" Height="23" HorizontalAlignment="Left" Margin="199,42,0,0" Name="button3" VerticalAlignment="Top" Width="147" Click="button3_Click" /> <RichTextBox Height="132" HorizontalAlignment="Left" Margin="373,21,0,0" Name="richTextBox1" VerticalAlignment="Top" Width="313" /> <Button Content="发送指令" Height="23" HorizontalAlignment="Left" Margin="199,123,0,0" Name="button4" VerticalAlignment="Top" Width="95" Click="button4_Click" /> <ListBox Height="69" HorizontalAlignment="Left" Margin="18,123,0,0" Name="listBox1" VerticalAlignment="Top" Width="163"> <ListBoxItem Content="1001" /> <ListBoxItem Content="1002" /> <ListBoxItem Content="1003" /> <ListBoxItem Content="1004" /> <ListBoxItem Content="1005" /> </ListBox> <Label Content="指令序列" Height="28" HorizontalAlignment="Left" Margin="21,89,0,0" Name="label1" VerticalAlignment="Top" /> <Grid Margin="21,223,531,142"> <Grid.ColumnDefinitions> <ColumnDefinition Width="100" /> <ColumnDefinition Width="60" /> </Grid.ColumnDefinitions> <Label Grid.Column="0" Content="消息内容" FontWeight="Bold" Padding="0" Height="18"></Label> <Label Grid.Column="1" Content="是否处理" FontWeight="Bold" Padding="0" Height="18"></Label> </Grid> <ListBox Height="103" HorizontalAlignment="Left" Margin="18,249,0,0" Name="listBox2" VerticalAlignment="Top" Width="163"> <ListBox.ItemTemplate> <DataTemplate> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="60" /> <ColumnDefinition Width="60" /> </Grid.ColumnDefinitions> <Label Grid.Column="0" Padding="0" Height="25" Content="{Binding Path=messageContent}"></Label> <RadioButton Grid.Column="1" Margin="0,5,0,0" Height="25" IsChecked="{Binding Path=handleFlag}"></RadioButton> </Grid> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <RichTextBox Height="132" HorizontalAlignment="Left" Margin="373,200,0,0" Name="richTextBox2" VerticalAlignment="Top" Width="313" /> </Grid></Window>
(4) 客户端 cs
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Windows;using System.Windows.Controls;using System.Windows.Data;using System.Windows.Documents;using System.Windows.Input;using System.Windows.Media;using System.Windows.Media.Imaging;using System.Windows.Shapes;using System.Net.Sockets;using System.Threading;using System.Net;using Newtonsoft.Json;namespace WpfApplication1{ /// <summary> /// Window1.xaml 的交互逻辑 /// </summary> public partial class Window1 : Window { int BufferSize = 1024; private bool executeFlag = false; List<MySocketMessage> list_message = new List<MySocketMessage>(); private int ClientFlag = 1; private string clientGUID = ""; Socket socket_client; public Window1() { InitializeComponent(); } private void button3_Click(object sender, RoutedEventArgs e) { int port = Convert.ToInt32(tbox_client_port.Text); string host = tbox_client_ip.Text; CreateConnect(host, port); } private void CreateConnect(string host, int port) { //创建终结点EndPoint IPAddress ip = IPAddress.Parse(host); IPEndPoint ipe = new IPEndPoint(ip, port); //把ip和端口转化为IPEndPoint的实例 //创建Socket并连接到服务器 socket_client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // 创建Socket Console.WriteLine("Connecting..."); socket_client.Connect(ipe); //连接到服务器 //向服务器发送信息 Thread thread_client = new Thread(ClientMethod); thread_client.Name = "client thread" + ClientFlag.ToString(); thread_client.Start(socket_client); ClientFlag++; } private void ClientMethod(object socket) { Socket socket_client = socket as Socket; clientGUID = Guid.NewGuid().ToString(); string sendStr_connect = "connectRequest" + clientGUID; Send_Mess(socket_client, sendStr_connect); try { byte[] recvBytes = new byte[1024]; while (true) { List<MySocketMessage> list_not_handle = list_message.FindAll(p => p.handleFlag == false); if (list_not_handle != null && list_not_handle.Count > 0) { //每次只处理第一条 string sendStr = list_not_handle.First().messageContent; list_not_handle.First().isDoing = true; list_not_handle.First().handleFlag = true; string jsonStr = "jsonMessage:" + JsonConvert.SerializeObject(list_not_handle.First()); Send_Mess(socket_client, jsonStr); string receiveStr = GetReceiveStr(socket_client); //收到的内容 if (receiveStr.Contains("ReturnCode:")) { if (receiveStr.Contains(list_not_handle.First().messageContent)) { int i_pos = receiveStr.IndexOf("ReturnCode:") + 11; AppendStrToRtbox("分割字符串前:" + receiveStr); receiveStr = receiveStr.Substring(i_pos, receiveStr.Length - i_pos); AppendStrToRtbox("发送指令后,服务端返回:" + receiveStr + "线程号:" + Thread.CurrentThread.Name); RefreshListBox(); } } } else { string receiveStr = GetReceiveStr(socket_client); //收到的内容 if (receiveStr.Equals("Confirm")) { AppendStrToRtbox_two("发送指令后,服务端返回:" + receiveStr); } Send_Mess(socket_client, "online"); } Thread.Sleep(50); } } catch (Exception ce) { executeFlag = false; AppendStrToRtbox("客户端检测到服务端连接断开"); } } public string GetReceiveStr(Socket socket_tmp) { //接受从服务器返回的信息 byte[] recvBytes = new byte[BufferSize]; int bytes; string recvStr = ""; bytes = socket_tmp.Receive(recvBytes, recvBytes.Length, 0); //从服务器端接受返回 if (bytes > 0) { recvStr += Encoding.ASCII.GetString(recvBytes, 0, bytes); Console.WriteLine("\nGetReceiveStr 客户端接收到服务端的返回:" + recvStr); } return recvStr; } /// <summary> /// 发送消息指令 /// </summary> /// <param name="sendMessage"></param> public void Send_Mess(Socket socket_tmp,string sendMessage) { if (socket_tmp.Connected == false) { return; } byte[] send_bytes = Encoding.ASCII.GetBytes(sendMessage); //把字符串编码为字节 if (send_bytes.Length > 0) { socket_tmp.Send(send_bytes, send_bytes.Length, 0); } } private void AppendStrToRtbox(string content) { this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(() => { this.Title = "客户端号:" + clientGUID; if (richTextBox1.Document.Blocks.Count > 100) { richTextBox1.Document.Blocks.Clear(); } richTextBox1.AppendText("\n" + content); richTextBox1.ScrollToEnd(); } )); } private void AppendStrToRtbox_two(string content) { this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(() => { this.Title = "客户端号:" + clientGUID; if (richTextBox2.Document.Blocks.Count > 100) { richTextBox2.Document.Blocks.Clear(); } richTextBox2.AppendText("\n" + content); richTextBox2.ScrollToEnd(); } )); } private void RefreshListBox() { this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(() => { listBox2.ItemsSource = new List<MySocketMessage>(); listBox2.ItemsSource = list_message; } )); } private void button4_Click(object sender, RoutedEventArgs e) { ListBoxItem lbi = listBox1.SelectedItem as ListBoxItem; if (lbi == null) { return; } string message = lbi.Content.ToString(); MySocketMessage message_tmp = new MySocketMessage(); message_tmp.messageContent = message; message_tmp.dt_message_addTime = DateTime.Now; message_tmp.clientID = clientGUID; list_message.Add(message_tmp); listBox2.ItemsSource = new List<MySocketMessage>(); listBox2.ItemsSource = list_message; } } public class MySocketMessage { public MySocketMessage() { handleFlag = false; isDoing = false; } public string clientID { get; set; } public string messageContent { get; set; } public DateTime dt_message_addTime { get; set; } public DateTime dt_message_handleTime { get; set; } public bool handleFlag { get; set; } public bool isDoing { get; set; } }}
4.未考虑的问题
(1) 消息的优先级
(2) 消息传递中的异常处理
ps: 最近工作中看到厂家的socket的接口,他们提供了非常标准的指令文档。在该厂家接口的定义中,对服务端的所有请求一定有返回消息。因此,我的样例代码中,服务端和客户端之间一直有消息传递。
以上代码的价值仅仅在于描述了服务端和客户端保持通信的一种简单实现,不少代码来自互联网。