C#编程--socket网络通讯

本文详细介绍C#中Socket编程的基础知识与实践应用,包括TCP/IP协议解释、Socket概念及其实现方式。通过具体代码示例,展示了服务器端与客户端的Socket通信流程,涉及线程池、数据收发及异常处理等关键环节。

C#编程–socket网络通讯

一、网络socket概念:

  • TCP/IP (Transmission Control Protocol / Internet Protocol)传输控制协议/网间协议。

socket 意为“插座”。通常称作“套接字”,用于描述IP地址和端口,是一个通讯链的句柄。(其实就是两个程序通讯用的。)

socket非常类似于电话插座。以一个电话网为例。电话的通话双方相当于相互通讯的2个程序,电话号码就是IP地址。任何用户在通话之前首先要占有一部电话机,相当于申请一个socket;同时要知道对方的号码,相当于对方有一个固定的socket.然后向对方拨号呼叫,相当于发出请求连接。对方加入再场并空闲,拿起电话话筒,双方就可以正式通话,相当于连接成功。双方通话的过程,相当于向socket发送数据和从socket接收数据。通话结束之后,一方挂起s电话机相当于关闭socket,撤销连接。

  • 端口

二、 socket通讯图解

在这里插入图片描述
服务器端代码:
IPAddress类提供了对IP地址的转换、处理等功能。其Parse方法可将IP地址字符串转换为IPAddress实例。

private void button1_Click(object sender, EventArgs e)
        {
         //socket服务器端代码:

            //1.创建socket对象(用来侦听的socket对象)
            //第一个参数设置 网络寻址协议。             AddressFamily.InterNetwork代表 IPV4
            //第二个参数设置 数据传输的方式。           SocketType.Stream  以流的方式传输
            //第三个参数设置 通讯的协议                ProtocolType.Tcp    TCP/IP协议
            Socket serverSocket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
            this.textBox3.Text = "服务器端socket创建成功\r\n"+textBox3.Text;

            //2、 绑定IP和端口
            //IPAddress类提供了对IP地址的转换、处理等功能。其Parse方法可将IP地址字符串转换为IPAddress实例。
            IPAddress ip=IPAddress.Parse(textBox1.Text);
            IPEndPoint ipendpoint=new IPEndPoint (ip,int.Parse(textBox2.Text));
            serverSocket.Bind(ipendpoint);
            this.textBox3.Text = "IP和端口绑定成功\r\n" + textBox3.Text;

            //3、开启侦听
            //等待的队列为:10;
            serverSocket.Listen(10);
            this.textBox3.Text = "开启侦听成功\r\n" + textBox3.Text;

            //4.开始接受客户端的连接
            //5.创建代理socket。
            //accept方法一执行,当前线程会阻塞,一直等到客户端连接上。
            Socket proxsocket = serverSocket.Accept();

            string str = DateTime.Now.ToString();
            byte[] data = Encoding.Default.GetBytes(str);

            //6.发送的消息必须大于等于1.
            proxsocket.Send(data, 0, data.Length, SocketFlags.None);

            //7.告诉对方,我轻轻的走了。给对方发送一个空消息。
            proxsocket.Shutdown(SocketShutdown.Both);
            proxsocket.Close();
            this.textBox3.Text = "服务器端代理socket关闭\r\n" + textBox3.Text;
            //serverSocket.Shutdown(SocketShutdown.Both);
            serverSocket.Close();
        }

以上方法socket监听会阻塞主线程,不可取。

服务器端稳定的完整代码:

    public partial class MainForm1 : Form
    {
        //用来存储代理socket的集合。
        List<Socket> proxSocketList = new List<Socket>();
        public MainForm1()
        {
            InitializeComponent();
        }

        private void btStart_Click(object sender, EventArgs e)
        {
            //1.创建服务器端socket对象(用来监听客户端Socket)
            Socket serverSocket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);

            //2.绑定IP和端口
            IPAddress ip = IPAddress.Parse(textIp.Text);
            IPEndPoint ipendpoint = new IPEndPoint(ip,int.Parse(textPort.Text));
            serverSocket.Bind(ipendpoint);

            //3.开启侦听
            //设置等待队列。
            serverSocket.Listen(10); //链接: 同时来了100个链接请求,只能处理一个链接,队列里面放10个等待链接的客户端,其他的返回错误消息。

            //4.开启线程池,服务器端接受客户端的连接
            ThreadPool.QueueUserWorkItem(new WaitCallback(AcceptClientConnect),serverSocket);

        }
        public void AcceptClientConnect(object socket)
        {
            var serverSocket = socket as Socket;
            //4. 服务器端开始接受客户端的连接。
            //此处应该循环一直接收客户端的连接。
            while(true)
            {
                //创建代理socket,用来与客户端进行通讯。
                //由于代理socket是用来与客户端通讯的,所以应该在其他地方也能访问到。
                this.AppendToTextMessagr("服务器端开始接受客户端的连接请求。");
                Socket proxSocket= serverSocket.Accept();
                //将代理socket放到窗体级别中的proxSocketList(代理Socket集合)。可以让整个窗体访问到该代理prosocket,以便通讯。
                proxSocketList.Add(proxSocket);
                AppendToTextMessagr(string.Format("客户端:{0}已经连接上。",proxSocket.RemoteEndPoint.ToString()));
                //当客户端连接上之后,服务器端就应该不停的接收客户端可能发来的消息。
                //proxSocket.Receive(); //该方法也会阻塞当前线程,不能因为接收一个客户端的消息,而阻塞了当前线程对客户端的监听。
              //因此此处考虑使用线程。
                ThreadPool.QueueUserWorkItem(new WaitCallback(ReceiveClientMessage),proxSocket);
            }


        }
        /// <summary>
        /// 发送消息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btSend_Click(object sender, EventArgs e)
        {
            //将所要发送的内容存储到字节数组中。
            byte[] data = Encoding.Default.GetBytes(textSend.Text);
            //要想发送消息,首先要拿到代理Socket对象。
            //使用foreach循环,遍历整个proxSocketList(代理Socket集合),拿到代理Socket.
            foreach(var proxSocket in proxSocketList)
            {
                //首先判断当前proxSocket(当前代理Socket)是否是连接状态
                if(proxSocket.Connected)
                {
                    proxSocket.Send(data, 0, data.Length, SocketFlags.None);
                }
            }
        }

        /// <summary>
        /// 接收客户端消息。
        /// </summary>
        /// <param name="Socket"></param>
        public void ReceiveClientMessage(object socket)
        {
            var proxSocket = socket as Socket;
            byte[] data1 = new byte[1024];
            while(true)
            {
                // proxSocket.Receive() 方法返回值为实际接受到的字节数。
                int len1 = 0;
                try
                {
                    len1 = proxSocket.Receive(data1, 0, data1.Length, SocketFlags.None);
                }
                catch
                {
                    //异常退出。
                    AppendToTextMessagr(string.Format("客户端{0}非正常退出。", proxSocket.RemoteEndPoint.ToString()));
                    proxSocketList.Remove(proxSocket);
                    return;  //让方法结束(结束线程。) 终结当前接收客户端数据的异步线程。
                }   
                //根据len1的值判断客户端是否正常退出
                if(len1<=0)
                {
                    AppendToTextMessagr(string.Format("客户端{0}正常退出。", proxSocket.RemoteEndPoint.ToString()));
                    //在代理Socket集合中将该proxSocket移除掉。
                    proxSocketList.Remove(proxSocket);
                    return; //让方法结束(结束线程。) 终结当前接收客户端数据的异步线程。
                }
                //调用函数将接受到的数据放到文本框中。
                string str = Encoding.Default.GetString(data1, 0, len1);
                AppendToTextMessagr(string.Format("接收到客户端{0}的消息为:{1}",proxSocket.RemoteEndPoint.ToString(),str));
            }
        }

        /// <summary>
        /// 由于许多地方都要将数据放到Message文本框中,因此专门写一个函数来实现往文本框中追加数据。
        /// </summary>
        /// <param name="str"></param>
        public void AppendToTextMessagr(string str)
        {
            
            if(textReceive.InvokeRequired)
            {
                //考虑跨线程访问控件。
                textReceive.Invoke(new Action<string>(s => 
                {
                    this.textReceive.Text = string.Format("{0}\r\n{1}", s, textReceive.Text);
                }), str);
            }
            else
            {
                //不考虑跨线程访问控件的话向文本框追加内容如下。
                this.textReceive.Text = string.Format("{0}\r\n{1}", str, textReceive.Text);
            }
           
        }

    }

客户端代码:

    public partial class Form1 : Form
    {
        //客户端Socket.
        public Socket clientSocket { get;set;}  //属性

        public Form1()
        {
            InitializeComponent();
        }

        private void btuStart_Click(object sender, EventArgs e)
        {
            //客户端连接服务器端
            //1. 创建socket对象
            Socket socketClient = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
            
            //为属性赋值。
            clientSocket = socketClient;

            //2. 连接服务器端
            try
            {
                socketClient.Connect(IPAddress.Parse(textIP.Text), int.Parse(textPort.Text));
                AppendToTextMessagr(string.Format("当前客户端连接成功。"));
            }
            catch(Exception ex)
            {
                //Thread.Sleep(500);
                //btuStart_Click(this, ex);
                MessageBox.Show("连接失败,重新连接");
                return;
            }
            Thread thread = new Thread(ReceiveClientMessage);
            thread.IsBackground = true;
            thread.Start();  
        }

        #region 客户端持续向服务器端发送数据(测试稳定性)
        /// <summary>
        /// 持续向服务器端发送数据
        /// </summary>
        public void ReceiveData()
        {
            byte[] data1 = new byte[1024];
            byte[] data2 = new byte[1024];
            int len1 = 0;
            int len2 = 0;
            //客户端连接上之后,首先向服务器端发送a
            data1 = stringToByte("a");
            clientSocket.Send(data1, 0, data1.Length, SocketFlags.None);

            while (true)
            {
                //接收服务器端发送来的消息。
                len1 = 0;
                try
                {
                    len1 = clientSocket.Receive(data2, 0, data2.Length, SocketFlags.None);
                    if (len1 <= 0)
                    {
                        //服务器端断开连接
                        AppendToTextMessagr(string.Format("服务器端断开连接,正常退出。"));
                        StopConnect();
                        return;
                    }
                    else
                    {
                        string firstData = byteToString(data2, len1);
                        //AppendToTextMessagr(string.Format("服务器端发送消息:{0}",firstData));
                        if (firstData == "q")
                        {
                            data1 = stringToByte("@3111");
                            clientSocket.Send(data1, 0, data1.Length, SocketFlags.None);
                            Thread.Sleep(1000);
                        }
                        else
                        {
                            continue;
                        }
                    }
                }
                catch
                {
                    AppendToTextMessagr(string.Format("服务器端断开连接,正常退出。"));
                    StopConnect();
                    return;
                }
                len2 = 0;
                try
                {
                    len2 = clientSocket.Receive(data2, 0, data2.Length, SocketFlags.None);
                    if (len2 <= 0)
                    {
                        //服务器端断开连接
                        AppendToTextMessagr(string.Format("服务器端断开连接,正常退出。"));
                        StopConnect();
                        return;
                    }
                    else
                    {
                        string firstData2 = byteToString(data2, len1);
                        //AppendToTextMessagr(string.Format("服务器端发送消息:{0}", firstData2));
                        if (firstData2 == "b")
                        {
                            data1 = stringToByte("@3111");
                            clientSocket.Send(data1, 0, data1.Length, SocketFlags.None);
                            Thread.Sleep(1000);
                        }
                        else
                        {
                            continue;
                        }
                    }
                }
                catch
                {
                    AppendToTextMessagr(string.Format("服务器端断开连接,正常退出。"));
                    StopConnect();
                    return;
                }
            }
        }
        #endregion


        /// <summary>
        /// 接收客户端消息。
        /// </summary>
        /// <param name="Socket"></param>
        public void ReceiveClientMessage(object socket)
        {
            var proxSocket = socket as Socket;
            byte[] data1 = new byte[1024];
            while(true)
            {
                // proxSocket.Receive() 方法返回值为实际接受到的字节数。
                int len1 = 0;
                try
                {
                    len1 = proxSocket.Receive(data1, 0, data1.Length, SocketFlags.None);
                }
                catch
                {
                    //异常退出。
                    AppendToTextMessagr(string.Format("服务器端{0}非正常退出。", proxSocket.RemoteEndPoint.ToString()));
                    StopConnect();
                    return;  //让方法结束(结束线程。) 终结当前接收客户端数据的异步线程。
                }   
                //根据len1的值判断客户端是否正常退出
                if(len1<=0)
                {
                    AppendToTextMessagr(string.Format("服务器端{0}正常退出。", proxSocket.RemoteEndPoint.ToString()));
                    StopConnect();
                    return; //让方法结束(结束线程。) 终结当前接收客户端数据的异步线程。
                }
                //调用函数将接受到的数据放到文本框中。
                string str = Encoding.Default.GetString(data1, 0, len1);
                AppendToTextMessagr(string.Format("接收到客户端{0}的消息为:{1}",proxSocket.RemoteEndPoint.ToString(),str));
            }
        }

        private void StopConnect()
        {
            try
            {
                if(clientSocket.Connected)
                {
                    clientSocket.Shutdown(SocketShutdown.Both);
                    clientSocket.Close(100);
                }
            }
            catch
            {
                MessageBox.Show("关闭连接出错");
            }
        }
        /// <summary>
        /// 发送消息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button1_Click(object sender, EventArgs e)
        {
            //3. 发送消息,接收消息。
            //客户端的Socket会自动分配一个随机的端口。 
            if(clientSocket.Connected)
            {
                byte[] data = Encoding.ASCII.GetBytes(textMessage.Text);
                clientSocket.Send(data, 0, data.Length, SocketFlags.None);  //发送消息
            }
        }

                /// <summary>
        /// 由于许多地方都要将数据放到Message文本框中,因此专门写一个函数来实现往文本框中追加数据。
        /// </summary>
        /// <param name="str"></param>
        public void AppendToTextMessagr(string str)
        {
            
            if(textMessage.InvokeRequired)
            {
                //考虑跨线程访问控件。
                textMessage.Invoke(new Action<string>(s => 
                {
                    this.textMessage.Text = string.Format("{0}\r\n{1}", s, textMessage.Text);
                }), str);
            }
            else
            {
                //不考虑跨线程访问控件的话向文本框追加内容如下。
                this.textMessage.Text = string.Format("{0}\r\n{1}", str, textMessage.Text);
            }
           
        }

        #region stringToByte 将字符串转换为字节流
        /// <summary>
        /// 将想要发送的字符串编码为字节数组
        /// </summary>
        /// <param name="data"></param>
        public static byte[] stringToByte(string sendStr)
        {
            byte[] data1 = Encoding.ASCII.GetBytes(sendStr);
            return data1;
        }
        #endregion
        #region byteToString 将字节流转换为字符串
        /// <summary>
        /// 将接收到的字节数组重新编码为string字符串
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public static string byteToString(byte[] data, int Length)
        {
            string str = Encoding.ASCII.GetString(data, 0, Length);   //将数组byte中的数据按ASCII的编码格式转化成string类型。重载(data,0(从第几位开始转化),Length(转换的字节长度))
            return str;
        }
        #endregion

    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值