美文网首页
.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