• .NET下WPF学习之Socket通信


    Socket通信

    关于Socket

    Socket作为进程通信的机制,是处于网络层中的应用层,说白了就是两个程序间通信用的。

    它的形式与电话插座类似,电话的通话双方相当于两个互相通信的程序,电话号相当于IP。

    网络通信三要素

    • IP地址(网络上主机设备的唯一标识,识别一台唯一的主机)
    • 端口号(定位程序,确定两个通信的程序)

        有效端口:0~65535,其中0~1023由系统使用,称为公认端口,他们紧密绑定与一些服务。从1024~49151是一些松散的绑定于一些服务,需要注册的一些端口,称为注册端口,剩下的49152~65535为动态端口、私有端口,我们一般开发都是使用这一频段的端口.

    • 传输协议(用什么样的方式进行交互)

           常见协议:TCP(面向连接,提供可靠的服务),UDP(无连接,传输速度快),一般使用TCP。

    服务端于客户端Socket通信流程

     重点记忆两个端的步骤:

    服务端:                                                                                         客户端:

    1、创建Socket对象(负责侦听)               1、创建Socket对象

    2、绑定端口                         2、连接服务器端

    3、开启侦听                         3、发送消息、接受消息

    4、开始接受客户端连接(不断接收,涉及多线程)        4、停止连接

    5、创建一个代理Socket对象(负责通信)                     5、关闭Socket对象

    6、发送、接收消息

    7、关闭Socket对象

    实现代码

    服务端XAML代码(客户端类似):

     1 <Window x:Class="SocketDemo.MainWindow"
     2         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     3         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     4         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
     5         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
     6         xmlns:local="clr-namespace:SocketDemo"
     7         mc:Ignorable="d"
     8         Title="MainWindow" Height="472.5" Width="605">
     9     <StackPanel>
    10         <Canvas Margin="10,20" Height="30">
    11             <Label Content="IP:" Height="30" Width="30"  FontSize="18" HorizontalContentAlignment="Center" Canvas.Left="8"/>
    12             <TextBox x:Name="txtIp" Text="192.168.0.4" Height="30" Width="150" FontSize="20" HorizontalContentAlignment="Center" Canvas.Left="41" />
    13             <Label Content="Port:" Height="30" Width="50" FontSize="18" HorizontalContentAlignment="Center" Canvas.Left="210"/>
    14             <TextBox x:Name="txtPort" Text="45000"  Height="30" Width="150" FontSize="20" HorizontalContentAlignment="Center" Canvas.Left="263"  />
    15             <Button x:Name="btnStartServer" Content="开启服务" Height="30" Width="100" Canvas.Left="460"/>
    16         </Canvas>
    17         <TextBox Name="txtLog" Height="300" AcceptsReturn="True" TextWrapping="Wrap"></TextBox>
    18         <Canvas Margin="0,20" Height="30">
    19             <TextBox x:Name="txtMsg" Height="30" Width="450" FontSize="20" HorizontalContentAlignment="Center" Canvas.Left="0" />
    20             <Button x:Name="btnSendMsg" Content="发送消息" Height="30" Width="100" Canvas.Left="470" />
    21         </Canvas>
    22     </StackPanel>
    23 </Window>
    服务端XAML(客户端与服务端相似)

    具体服务端实现:

      1 using System;
      2 using System.Collections.Generic;
      3 using System.Linq;
      4 using System.Net;
      5 using System.Net.Sockets;
      6 using System.Text;
      7 using System.Threading;
      8 using System.Threading.Tasks;
      9 using System.Windows;
     10 using System.Windows.Controls;
     11 using System.Windows.Data;
     12 using System.Windows.Documents;
     13 using System.Windows.Input;
     14 using System.Windows.Media;
     15 using System.Windows.Media.Imaging;
     16 using System.Windows.Navigation;
     17 using System.Windows.Shapes;
     18 
     19 namespace SocketDemo
     20 {
     21     /// <summary>
     22     /// MainWindow.xaml 的交互逻辑
     23     /// </summary>
     24     public partial class MainWindow : Window
     25     {
     26         List<Socket> clientScoketLis = new List<Socket>();//存储连接服务器端的客户端的Socket
     27         public MainWindow()
     28         {
     29             InitializeComponent();
     30             Loaded += MainWindow_Loaded;
     31             btnStartServer.Click += BtnStartServer_Click;//事件注册
     32             btnSendMsg.Click += BtnSendMsg_Click;
     33             Closing += MainWindow_Closing;
     34         }
     35 
     36 
     37         private void MainWindow_Loaded(object sender, RoutedEventArgs e)
     38         {
     39             ClientWindows clientWindows = new ClientWindows();
     40             clientWindows.Show();
     41         }
     42         /// <summary>
     43         /// 关闭事件
     44         /// </summary>
     45         private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
     46         {
     47             //使用foreach出现 “集合已修改;可能无法执行枚举操作”,ClientExit源于方法中对list集合进行了Remove,所造成的异常。
     48             //msdn的解释:foreach 语句是对枚举数的包装,它只允许从集合中读取,不允许写入集合。也就是,不能在foreach里遍历的时侯把它的元素进行删除或增加的操作的
     49             //foreach (var socket in clientScoketLis)
     50             //{
     51             //    ClientExit(null , socket);
     52             //}
     53             //改成for循环即可
     54             for (int i = 0; i < clientScoketLis.Count; i++)//向每个客户端说我下线了
     55             {
     56                 ClientExit(null, clientScoketLis[i]);
     57             }
     58         }
     59 
     60         /// <summary>
     61         /// 开启服务事件
     62         /// </summary>
     63         private void BtnStartServer_Click(object sender, RoutedEventArgs e)
     64         {
     65             //1、创建Socket对象
     66             //参数:寻址方式,当前为Ivp4  指定套接字类型   指定传输协议Tcp;
     67             Socket socket = new Socket(AddressFamily.InterNetwork , SocketType.Stream , ProtocolType.Tcp);
     68             //2、绑定端口、IP
     69             IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Parse(this.txtIp.Text) , int.Parse(txtPort.Text)); 
     70             socket.Bind(iPEndPoint);
     71             //3、开启侦听   10为队列最多接收的数量
     72             socket.Listen(10);//如果同时来了100个连接请求,只能处理一个,队列中10个在等待连接的客户端,其他的则返回错误消息。
     73 
     74             //4、开始接受客户端的连接  ,连接会阻塞主线程,故使用线程池。
     75             ThreadPool.QueueUserWorkItem(new WaitCallback(AcceptClientConnect),socket);
     76 
     77 
     78         }
     79         /// <summary>
     80         /// 线程池线程执行的接受客户端连接方法
     81         /// </summary>
     82         /// <param name="obj">传入的Socket</param>
     83         private void AcceptClientConnect(object obj)
     84         {
     85             //转换Socket
     86             var serverSocket = obj as Socket;
     87 
     88             AppendTxtLogText("服务端开始接收客户端连接!");
     89 
     90             //不断接受客户端的连接
     91             while (true)
     92             {
     93                 //5、创建一个负责通信的Socket
     94                 Socket proxSocket = serverSocket.Accept();
     95                 AppendTxtLogText(string.Format("客户端:{0}连接上了!", proxSocket.RemoteEndPoint.ToString()));
     96                 //将连接的Socket存入集合
     97                 clientScoketLis.Add(proxSocket);
     98                 //6、不断接收客户端发送来的消息
     99                 ThreadPool.QueueUserWorkItem(new WaitCallback(ReceiveClientMsg) , proxSocket);
    100             }
    101 
    102         }
    103         /// <summary>
    104         /// 不断接收客户端信息子线程方法
    105         /// </summary>
    106         /// <param name="obj">参数Socke对象</param>
    107         private void ReceiveClientMsg(object obj)
    108         {
    109             var proxSocket = obj as Socket;
    110             //创建缓存内存,存储接收的信息   ,不能放到while中,这块内存可以循环利用
    111             byte[] data = new byte[1020*1024];
    112             while (true)
    113             {
    114                 int len;
    115                 try
    116                 {
    117                     //接收消息,返回字节长度
    118                     len = proxSocket.Receive(data, 0, data.Length, SocketFlags.None);
    119                 }
    120                 catch (Exception ex)
    121                 {
    122                     //7、关闭Socket
    123                     //异常退出
    124                     try
    125                     {
    126                         ClientExit(string.Format("客户端:{0}非正常退出", proxSocket.RemoteEndPoint.ToString()), proxSocket);
    127                     }
    128                     catch (Exception)
    129                     {
    130                     }
    131                     return;//让方法结束,终结当前客户端数据的异步线程,方法退出,即线程结束
    132                 }
    133 
    134                 if (len <= 0)//判断接收的字节数
    135                 {
    136                     //7、关闭Socket
    137                     //小于0表示正常退出
    138                     try
    139                     {
    140                         ClientExit(string.Format("客户端:{0}正常退出", proxSocket.RemoteEndPoint.ToString()), proxSocket);
    141                     }
    142                     catch (Exception)
    143                     {
    144                     }
    145                     return;//让方法结束,终结当前客户端数据的异步线程,方法退出,即线程结束
    146                 }
    147                 //将消息显示到TxtLog
    148                 string msgStr = Encoding.Default.GetString(data , 0 , len);
    149                 //拼接字符串
    150                 AppendTxtLogText(string.Format("接收到客户端:{0}的消息:{1}" , proxSocket.RemoteEndPoint.ToString() , msgStr));
    151             }
    152         }
    153 
    154         /// <summary>
    155         /// 消息发送事件
    156         /// </summary>
    157         private void BtnSendMsg_Click(object sender, RoutedEventArgs e)
    158         {
    159             foreach (Socket proxSocket in clientScoketLis)
    160             {
    161                 if (proxSocket.Connected)//判断客户端是否还在连接
    162                 {
    163                     byte[] data = Encoding.Default.GetBytes(this.txtMsg.Text);
    164                     //6、发送消息
    165                     proxSocket.Send(data , 0 , data.Length , SocketFlags.None); //指定套接字的发送行为
    166                     this.txtMsg.Text = null;
    167                 }
    168             }
    169         }
    170         /// <summary>
    171         /// 向文本框中追加信息
    172         /// </summary>
    173         /// <param name="str"></param>
    174         private void AppendTxtLogText( string str)
    175         {
    176             if (!(txtLog.Dispatcher.CheckAccess()))//判断跨线程访问
    177             {
    178                 ////同步方法
    179                 //this.Dispatcher.Invoke(new Action<string>( s => 
    180                 //{
    181                 //    this.txtLog.Text = string.Format("{0}
    {1}" , s , txtLog.Text);
    182                 //}) ,str);
    183                 //异步方法
    184                 this.Dispatcher.BeginInvoke(new Action<string>(s =>
    185                 {
    186                     this.txtLog.Text = string.Format("{0}
    {1}", s, txtLog.Text);
    187                 }), str);
    188             }
    189             else
    190             { 
    191             this.txtLog.Text = string.Format("{0}
    {1}", str, txtLog.Text);
    192             }
    193         }
    194         /// <summary>
    195         /// 客户端退出调用
    196         /// </summary>
    197         /// <param name="msg"></param>
    198         private void ClientExit(string msg , Socket proxSocket)
    199         {
    200             AppendTxtLogText(msg);
    201             clientScoketLis.Remove(proxSocket);//移除集合中的连接Socket
    202 
    203             try
    204             {
    205                 if (proxSocket.Connected)//如果是连接状态
    206                 {
    207                     proxSocket.Shutdown(SocketShutdown.Both);//关闭连接
    208                     proxSocket.Close(100);//100秒超时间
    209                 }
    210             }
    211             catch (Exception ex)
    212             {
    213             }
    214         }
    215     }
    216 }
    服务器实现代码

    具体客户端实现:

      1 using System;
      2 using System.Collections.Generic;
      3 using System.Linq;
      4 using System.Net;
      5 using System.Net.Sockets;
      6 using System.Text;
      7 using System.Threading;
      8 using System.Threading.Tasks;
      9 using System.Windows;
     10 using System.Windows.Controls;
     11 using System.Windows.Data;
     12 using System.Windows.Documents;
     13 using System.Windows.Input;
     14 using System.Windows.Media;
     15 using System.Windows.Media.Imaging;
     16 using System.Windows.Shapes;
     17 
     18 namespace SocketDemo
     19 {
     20     /// <summary>
     21     /// ClientWindows.xaml 的交互逻辑
     22     /// </summary>
     23     public partial class ClientWindows : Window
     24     {
     25         private Socket _socket;
     26         public ClientWindows()
     27         {
     28             InitializeComponent();
     29             btnSendMsg.Click += BtnSendMsg_Click;//注册事件
     30             btnConnect.Click += BtnConnect_Click;
     31             Closing += ClientWindows_Closing;
     32         }
     33         /// <summary>
     34         /// 窗口关闭事件
     35         /// </summary>
     36         private void ClientWindows_Closing(object sender, System.ComponentModel.CancelEventArgs e)
     37         {
     38             ServerExit(null,_socket);//向服务端说我下线了。
     39         }
     40 
     41         /// <summary>
     42         /// 连接按钮事件
     43         /// </summary>
     44         private void BtnConnect_Click(object sender, RoutedEventArgs e)
     45         {
     46             //1、创建Socket对象
     47             Socket socket = new Socket(AddressFamily.InterNetwork , SocketType.Stream , ProtocolType.Tcp);
     48             _socket = socket;
     49             //2、连接服务器,绑定IP 与 端口
     50             IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Parse(txtIp.Text) , int.Parse(txtPort.Text));   
     51             try
     52             {
     53                 socket.Connect(iPEndPoint);
     54             }
     55             catch (Exception)
     56             {
     57                 MessageBox.Show("连接失败,请重新连接!","提示");
     58                 return;
     59             }
     60             //3、接收消息
     61             ThreadPool.QueueUserWorkItem(new WaitCallback(ReceiveServerMsg),socket);
     62         }
     63 
     64         /// <summary>
     65         /// 不断接收客户端信息子线程方法
     66         /// </summary>
     67         /// <param name="obj">参数Socke对象</param>
     68         private void ReceiveServerMsg(object obj)
     69         {
     70             var proxSocket = obj as Socket;
     71             //创建缓存内存,存储接收的信息   ,不能放到while中,这块内存可以循环利用
     72             byte[] data = new byte[1020 * 1024];
     73             while (true)
     74             {
     75                 int len;
     76                 try
     77                 {
     78                     //接收消息,返回字节长度
     79                     len = proxSocket.Receive(data, 0, data.Length, SocketFlags.None);
     80                 }
     81                 catch (Exception ex)
     82                 {
     83                     //7、关闭Socket
     84                     //异常退出
     85                     try
     86                     {
     87                         ServerExit(string.Format("服务端:{0}非正常退出", proxSocket.RemoteEndPoint.ToString()), proxSocket);
     88                     }
     89                     catch (Exception)
     90                     {
     91  
     92                     }
     93                     return;//让方法结束,终结当前客户端数据的异步线程,方法退出,即线程结束
     94                 }
     95 
     96                 if (len <= 0)//判断接收的字节数
     97                 {
     98                     //7、关闭Socket
     99                     //小于0表示正常退出
    100                     try
    101                     {
    102                         ServerExit(string.Format("服务端:{0}正常退出", proxSocket.RemoteEndPoint.ToString()), proxSocket);
    103                     }
    104                     catch (Exception)
    105                     {
    106 
    107                     }
    108                     return;//让方法结束,终结当前客户端数据的异步线程,方法退出,即线程结束
    109                 }
    110                 //将消息显示到TxtLog
    111                 string msgStr = Encoding.Default.GetString(data, 0, len);
    112                 //拼接字符串
    113                 AppendTxtLogText(string.Format("接收到服务端:{0}的消息:{1}", proxSocket.RemoteEndPoint.ToString(), msgStr));
    114             }
    115         }
    116 
    117         /// <summary>
    118         /// 客户端退出调用
    119         /// </summary>
    120         /// <param name="msg"></param>
    121         private void ServerExit(string msg, Socket proxSocket)
    122         {
    123             AppendTxtLogText(msg);
    124             try
    125             {
    126                 if (proxSocket.Connected)//如果是连接状态
    127                 {
    128                     proxSocket.Shutdown(SocketShutdown.Both);//关闭连接
    129                     proxSocket.Close(100);//100秒超时间
    130                 }
    131             }
    132             catch (Exception ex)
    133             {
    134             }
    135         }
    136 
    137         /// <summary>
    138         /// 发送信息按钮事件
    139         /// </summary>
    140         private void BtnSendMsg_Click(object sender, RoutedEventArgs e)
    141         {
    142             byte[] data = Encoding.Default.GetBytes(this.txtMsg.Text);
    143             //6、发送消息
    144             _socket.Send(data, 0, data.Length, SocketFlags.None); //指定套接字的发送行为
    145             this.txtMsg.Text = null;
    146         }
    147 
    148         /// <summary>
    149         /// 向文本框中追加信息
    150         /// </summary>
    151         /// <param name="str"></param>
    152         private void AppendTxtLogText(string str)
    153         {
    154             if (!(txtLog.Dispatcher.CheckAccess()))//判断跨线程访问
    155             {
    156                 ////同步方法
    157                 //this.Dispatcher.Invoke(new Action<string>( s => 
    158                 //{
    159                 //    this.txtLog.Text = string.Format("{0}
    {1}" , s , txtLog.Text);
    160                 //}) ,str);
    161                 //异步方法
    162                 this.Dispatcher.BeginInvoke(new Action<string>(s =>
    163                 {
    164                     this.txtLog.Text = string.Format("{0}
    {1}", s, txtLog.Text);
    165                 }), str);
    166             }
    167             else
    168             {
    169                 this.txtLog.Text = string.Format("{0}
    {1}", str, txtLog.Text);
    170             }
    171         }
    172     }
    173 }
    客户端实现代码

    运行展示:

  • 相关阅读:
    sqlserver OpenRowSet 对应的三种数据库驱动
    讨论贴:在sp_executesql 中生成的临时表的可见性
    避免创建表的情况下,执行存储过程插入临时表
    讨论贴:Sqlserver varbinary 是二进制数据,却是十六进制的表现形式
    对于unallocated space的翻译 我想说几句话
    增删改查 的一些不常用的小技巧
    转 TextBox的EnableViewState属性问题
    总结 output 用法
    VSIX 插件右键菜单(2)
    MVC使用记录
  • 原文地址:https://www.cnblogs.com/memoyu/p/10764884.html
Copyright © 2020-2023  润新知