美文网首页
.NET Socket通信

.NET Socket通信

作者: Memoyu | 来源:发表于2019-04-27 08:19 被阅读0次

    认知尚浅,如有错误,愿闻其详!

    关于Socket

    Socket作为进程通信的机制,是处于网络层中的应用层,说白了就是两个程序间通信用的。
    它的形式与电话插座类似,电话的通话双方相当于两个互相通信的程序,电话号相当于IP。

    网络通信三要素

    • IP地址(网络上主机设备的唯一标识,识别一台唯一的主机)

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

    • 传输协议(用什么样的方式进行交互)
        常见协议:TCP(面向连接,提供可靠的服务),UDP(无连接,传输速度快),一般使用TCP。

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

    Socket流程.png

    重点记忆两个端的步骤:

    服务端:                          \客户端:

    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>
    

    具体服务端实现:

    
    namespace SocketDemo
    {
        /// <summary>
        /// MainWindow.xaml 的交互逻辑
        /// </summary>
        public partial class MainWindow : Window
        {
            List<Socket> clientScoketLis = new List<Socket>();//存储连接服务器端的客户端的Socket
            public MainWindow()
            {
                InitializeComponent();
                Loaded += MainWindow_Loaded;
                btnStartServer.Click += BtnStartServer_Click;//事件注册
                btnSendMsg.Click += BtnSendMsg_Click;
                Closing += MainWindow_Closing;
            }
    
    
            private void MainWindow_Loaded(object sender, RoutedEventArgs e)
            {
                ClientWindows clientWindows = new ClientWindows();
                clientWindows.Show();
            }
            /// <summary>
            /// 关闭事件
            /// </summary>
            private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
            {
                //使用foreach出现 “集合已修改;可能无法执行枚举操作”,ClientExit源于方法中对list集合进行了Remove,所造成的异常。
                //msdn的解释:foreach 语句是对枚举数的包装,它只允许从集合中读取,不允许写入集合。也就是,不能在foreach里遍历的时侯把它的元素进行删除或增加的操作的
                //foreach (var socket in clientScoketLis)
                //{
                //    ClientExit(null , socket);
                //}
                //改成for循环即可
                for (int i = 0; i < clientScoketLis.Count; i++)//向每个客户端说我下线了
                {
                    ClientExit(null, clientScoketLis[i]);
                }
            }
    
            /// <summary>
            /// 开启服务事件
            /// </summary>
            private void BtnStartServer_Click(object sender, RoutedEventArgs e)
            {
                //1、创建Socket对象
                //参数:寻址方式,当前为Ivp4  指定套接字类型   指定传输协议Tcp;
                Socket socket = new Socket(AddressFamily.InterNetwork , SocketType.Stream , ProtocolType.Tcp);
                //2、绑定端口、IP
                IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Parse(this.txtIp.Text) , int.Parse(txtPort.Text)); 
                socket.Bind(iPEndPoint);
                //3、开启侦听   10为队列最多接收的数量
                socket.Listen(10);//如果同时来了100个连接请求,只能处理一个,队列中10个在等待连接的客户端,其他的则返回错误消息。
    
                //4、开始接受客户端的连接  ,连接会阻塞主线程,故使用线程池。
                ThreadPool.QueueUserWorkItem(new WaitCallback(AcceptClientConnect),socket);
    
    
            }
            /// <summary>
            /// 线程池线程执行的接受客户端连接方法
            /// </summary>
            /// <param name="obj">传入的Socket</param>
            private void AcceptClientConnect(object obj)
            {
                //转换Socket
                var serverSocket = obj as Socket;
    
                AppendTxtLogText("服务端开始接收客户端连接!");
    
                //不断接受客户端的连接
                while (true)
                {
                    //5、创建一个负责通信的Socket
                    Socket proxSocket = serverSocket.Accept();
                    AppendTxtLogText(string.Format("客户端:{0}连接上了!", proxSocket.RemoteEndPoint.ToString()));
                    //将连接的Socket存入集合
                    clientScoketLis.Add(proxSocket);
                    //6、不断接收客户端发送来的消息
                    ThreadPool.QueueUserWorkItem(new WaitCallback(ReceiveClientMsg) , proxSocket);
                }
    
            }
            /// <summary>
            /// 不断接收客户端信息子线程方法
            /// </summary>
            /// <param name="obj">参数Socke对象</param>
            private void ReceiveClientMsg(object obj)
            {
                var proxSocket = obj as Socket;
                //创建缓存内存,存储接收的信息   ,不能放到while中,这块内存可以循环利用
                byte[] data = new byte[1020*1024];
                while (true)
                {
                    int len;
                    try
                    {
                        //接收消息,返回字节长度
                        len = proxSocket.Receive(data, 0, data.Length, SocketFlags.None);
                    }
                    catch (Exception ex)
                    {
                        //7、关闭Socket
                        //异常退出
                        try
                        {
                            ClientExit(string.Format("客户端:{0}非正常退出", proxSocket.RemoteEndPoint.ToString()), proxSocket);
                        }
                        catch (Exception)
                        {
                        }
                        return;//让方法结束,终结当前客户端数据的异步线程,方法退出,即线程结束
                    }
    
                    if (len <= 0)//判断接收的字节数
                    {
                        //7、关闭Socket
                        //小于0表示正常退出
                        try
                        {
                            ClientExit(string.Format("客户端:{0}正常退出", proxSocket.RemoteEndPoint.ToString()), proxSocket);
                        }
                        catch (Exception)
                        {
                        }
                        return;//让方法结束,终结当前客户端数据的异步线程,方法退出,即线程结束
                    }
                    //将消息显示到TxtLog
                    string msgStr = Encoding.Default.GetString(data , 0 , len);
                    //拼接字符串
                    AppendTxtLogText(string.Format("接收到客户端:{0}的消息:{1}" , proxSocket.RemoteEndPoint.ToString() , msgStr));
                }
            }
    
            /// <summary>
            /// 消息发送事件
            /// </summary>
            private void BtnSendMsg_Click(object sender, RoutedEventArgs e)
            {
                foreach (Socket proxSocket in clientScoketLis)
                {
                    if (proxSocket.Connected)//判断客户端是否还在连接
                    {
                        byte[] data = Encoding.Default.GetBytes(this.txtMsg.Text);
                        //6、发送消息
                        proxSocket.Send(data , 0 , data.Length , SocketFlags.None); //指定套接字的发送行为
                        this.txtMsg.Text = null;
                    }
                }
            }
            /// <summary>
            /// 向文本框中追加信息
            /// </summary>
            /// <param name="str"></param>
            private void AppendTxtLogText( string str)
            {
                if (!(txtLog.Dispatcher.CheckAccess()))//判断跨线程访问
                {
                    ////同步方法
                    //this.Dispatcher.Invoke(new Action<string>( s => 
                    //{
                    //    this.txtLog.Text = string.Format("{0}\r\n{1}" , s , txtLog.Text);
                    //}) ,str);
                    //异步方法
                    this.Dispatcher.BeginInvoke(new Action<string>(s =>
                    {
                        this.txtLog.Text = string.Format("{0}\r\n{1}", s, txtLog.Text);
                    }), str);
                }
                else
                { 
                this.txtLog.Text = string.Format("{0}\r\n{1}", str, txtLog.Text);
                }
            }
            /// <summary>
            /// 客户端退出调用
            /// </summary>
            /// <param name="msg"></param>
            private void ClientExit(string msg , Socket proxSocket)
            {
                AppendTxtLogText(msg);
                clientScoketLis.Remove(proxSocket);//移除集合中的连接Socket
    
                try
                {
                    if (proxSocket.Connected)//如果是连接状态
                    {
                        proxSocket.Shutdown(SocketShutdown.Both);//关闭连接
                        proxSocket.Close(100);//100秒超时间
                    }
                }
                catch (Exception ex)
                {
                }
            }
        }
    }
    

    具体客户端实现:

     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}\r\n{1}" , s , txtLog.Text);
    160                 //}) ,str);
    161                 //异步方法
    162                 this.Dispatcher.BeginInvoke(new Action<string>(s =>
    163                 {
    164                     this.txtLog.Text = string.Format("{0}\r\n{1}", s, txtLog.Text);
    165                 }), str);
    166             }
    167             else
    168             {
    169                 this.txtLog.Text = string.Format("{0}\r\n{1}", str, txtLog.Text);
    170             }
    171         }
    172     }
    173 }
    

    运行效果:

    效果.gif

    相关文章

      网友评论

          本文标题:.NET Socket通信

          本文链接:https://www.haomeiwen.com/subject/wpkhnqtx.html