首页 > 其他分享 >Socket基础三

Socket基础三

时间:2023-03-24 14:14:18浏览次数:64  
标签:socket buffer void 基础 new 客户端 Socket

1 流程

 

 2 示例

看下面一个服务器端的代码:

namespace MyScoketTest
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 开始监听
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btn_Start_Click(object sender, EventArgs e)
        {
            //(1)创建套接字
            Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //(2)绑定IP和端口
            int port = 2018;
            string host = "127.0.0.1";
            //指示服务器应侦听所有网络接口上的客户端活动。此字段为只读。
            IPAddress iP = IPAddress.Any;
            //创建端口号对象
            IPEndPoint point = new IPEndPoint(iP, port);
            //绑定
            socketWatch.Bind(point);
            //(3)监听
            socketWatch.Listen(10);
            ShowMsg($"监听成功");
            //(4)等待客户端连接,并且创建一个负责通信的Socket 
            Socket socket = socketWatch.Accept();
            //RemoteEndPoint是当前客户端连接成功的IP
            ShowMsg($"{socket.RemoteEndPoint.ToString()}:连接成功"); 
        }

        private void ShowMsg(string str)
        {
            this.textBox1.AppendText(str + "\r\n"); 
        } 

        private void textBox1_TextChanged(object sender, EventArgs e)
        {

        }
    }
}

运行成功之后点击开始监听按钮,结果如下:

 

 现在有2个问题,而且问题都是出现在代码 Socket socket = socketWatch.Accept()这里:

一是界面会卡死:在这个地方,会一直等待连接,如果客户端一直不连接,winform界面就会卡主,因为主线程阻塞了,界面的操作无法响应。所以我们可以开启一个新线程来执行。

二是一次只能连接一个客户端:这个问题可以通过写在循环里面来解决,这样的话就能一直接收别的客户端出来的申请连接。

 还有在窗体加载的加一段代码,防止客户端链接的时候出现跨线程的错误:

private void Form1_Load(object sender, EventArgs e)
        {
            //当某个线程不是控件的创建线程尝试访问该控件的一个或多个属性时,这通常会导致不可预知的结果。               常见的无效线程活动是对访问控件属性的错误线程的调用 Handle 。 设置 CheckForIllegalCrossThreadCalls 为 true 可以在调试时更轻松地查找和诊断此线程活动。 
            Control.CheckForIllegalCrossThreadCalls = false;
        }

更改后的代码:

namespace MyScoketTest
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 开始监听
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btn_Start_Click(object sender, EventArgs e)
        {
            //(1)创建套接字
            Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //(2)绑定IP和端口
            int port = 2018;
            string host = "127.0.0.1";
            //IPAddress.Any 指示服务器应侦听所有网络接口上的客户端活动。此字段为只读。
            // IPAddress iP = IPAddress.Any;
            IPAddress ip = IPAddress.Parse(host); 
            //创建端口号对象
            IPEndPoint point = new IPEndPoint(ip, port);
            //绑定
            socketWatch.Bind(point);
            //(3)监听
            socketWatch.Listen(10);
            ShowMsg($"监听成功");
            //使用委托异步的方式界面界面卡死
            Action<Socket> action = Listen;
            action.BeginInvoke(socketWatch, null, null);
            //Thread thread = new Thread(new ThreadStart(Listen));
        }

        private void ShowMsg(string str)
        {
            this.textBox1.AppendText(str + "\r\n");

        }

        void Listen(Socket socketWatch)
        {
            //(4)等待客户端连接,并且创建一个负责通信的Socket 
            while (true)
            {
                Socket socket = socketWatch.Accept();
                //RemoteEndPoint是当前客户端连接成功的IP
                ShowMsg($"{socket.RemoteEndPoint.ToString()}:连接成功");
            }

        }


        private void textBox1_TextChanged(object sender, EventArgs e)
        {

        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //当某个线程不是控件的创建线程尝试访问该控件的一个或多个属性时,这通常会导致不可预知的结果。                  常见的无效线程活动是对访问控件属性的错误线程的调用 Handle 。 设置 CheckForIllegalCrossThreadCalls 为 true 可以在调试时更轻松地查找和诊断此线程活动。 
            Control.CheckForIllegalCrossThreadCalls = false;
        }
    }
}

上面是服务端的代码,我们可以暂时通过telnet来进行客户端的测试,执行cmd执行,打开命令提示符界面,输入telnet 127.0.0.1 2018,

 

 点击回车,结果:

上面的代码成功实现了连接,下面就需要我们进行通信的处理,Listen方法给为如下:

void Listen(Socket socketWatch)
        {
            //(4)等待客户端连接,并且创建一个负责通信的Socket 
            while (true)
            {
                //通信的Socket对象
                Socket socket = socketWatch.Accept();
                //RemoteEndPoint是当前客户端连接成功的IP
                ShowMsg($"{socket.RemoteEndPoint.ToString()}:连接成功");
                //连接成功之后,服务器应该接收来自于客户端的消息,这是由负责通信的socket来实现
                byte[] buffer = new byte[1024 * 1024 * 2];//2M
                //realLength实际接收的有效字节数
                int realLength = socket.Receive(buffer);
                string str = Encoding.UTF8.GetString(buffer, 0, realLength);
                ShowMsg($"{socket.RemoteEndPoint.ToString()}:{str}");
            }

        }

运行起来,然后发送数据,但是却发现只能接收一次数据,后面无论怎么发送都不过去了,如下,只发送了一个字母a:  

 

 

 这是因为下面这行代码写在了循环里面:

//通信的Socket对象
Socket socket = socketWatch.Accept();

比如说第一次循环的时候连接上这个客户端,这个客户端发了消息之后再次循环的时候又调了一次上面的代码,相当于创建了一个新的Socket连接, 上一次通信的连接没有了,所以就接收不到后面的信息了。所以我们需要拿到循环外面,不止如此,还要将下面Receive方法再开辟一个线程来执行,防止卡死。这样的话既能接收多个客户端的连接信息,来一个客户端信息,就给这个客户端分配一个Socket对象,专门负责这个客户端和服务端的通信,也能为每一个客户端的信息开辟一个子线程来专门去处理信息:

如下,Listen方法:

void Listen(Socket socketWatch)
        {
            //(4)等待客户端连接,并且创建一个负责通信的Socket 
            while (true)
            {
                //通信的Socket对象,在循环中,每来一个客户端信息,就给这个客户端分配一个Socket对象,专门负责这个客户端和服务端的通信
                Socket socket = socketWatch.Accept();
                //RemoteEndPoint是当前客户端连接成功的IP
                ShowMsg($"{socket.RemoteEndPoint.ToString()}:连接成功");
                Action<Socket> action = Receive;
                action.BeginInvoke(socket, null, null);
            } 
        }
        /// <summary>
        /// 接收信息--在这里也是专门弄成每一个客户端连接信息都有专门的线程来负责。
        /// </summary>
        /// <param name="socket"></param>
        private void Receive(Socket socket)
        {
            while (true)
            {
                //连接成功之后,服务器应该接收来自于客户端的消息,这是由负责通信的socket来实现
                byte[] buffer = new byte[1024 * 1024 * 2];//2M
                //realLength实际接收的有效字节数
                int realLength = socket.Receive(buffer);
                string str = Encoding.UTF8.GetString(buffer, 0, realLength);
                ShowMsg($"{socket.RemoteEndPoint.ToString()}:{str}");
            } 
        }

此时运行之后上述问题已经解决了。但是又出现新问题了,关闭客户端之后,服务端一直在接收空信息,陷入死循环中了。

 我们现在需要知道一件事:int realLength = socket.Receive(buffer);看这行代码,如果客户端A和服务器端已经连接上了,子线程A运行到这行代码之后就会阻塞,只有客户端发来信息之后才会继续向下执行,还有就是客户端关闭连接之后子线程A也会取消在这个地方的阻塞,继续向下执行,所以我们可以判断一下接收到信息的长度,如果为0,则表示客户端已经关闭了。凡是在网络中传输数据,或者说操纵网络数据,肯定会引发各种异常,不管是断电,断网。所以我们需要在有可能出异常的地方尽量都加上try/catch,比如下面的代码直接在循环中价格try/catch,catch中不用加其它处理,万一客户端出现问题了,只要重新连接上,我们这边照样能接收发送数据。这样用户也不会看出来出问题了。

/// <summary>
        /// 接收信息
        /// </summary>
        /// <param name="socket"></param>
        private void Receive(Socket socket)
        {
            while (true)
            {
                try
                {
                    //连接成功之后,服务器应该接收来自于客户端的消息,这是由负责通信的socket来实现
                    byte[] buffer = new byte[1024 * 1024 * 2];//2M
                                                              //realLength实际接收的有效字节数 
                    int realLength = socket.Receive(buffer);
                    if (realLength == 0)
                    {
                        break;
                    }

                    string str = Encoding.UTF8.GetString(buffer, 0, realLength);
                    ShowMsg($"{socket.RemoteEndPoint.ToString()}:{str}");
                }
                catch{

                }
            }

        }

其实现在还是有一个问题,那就是Listen方法中,每一次循环的时候都会调用accept方法,如果存在多个客户端和服务器连接之后,服务器端想向客户端发消息的话,就不知道到底会给谁发送消息,因为循环的原因,总是默认给最近一次连接服务器的那个客户端发消息。所以我们可以加一个下拉框,因为每次服务器接收客户端申请的时候都能知道申请的客户端的IP和端口信息,我们将其存起来,发送消息的时候选择对应的客户端就可以了。

所以最终服务器端的代码更改为:

public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 开始监听
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btn_Start_Click(object sender, EventArgs e)
        {
            try
            {
                //(1)创建套接字
                Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                //(2)绑定IP和端口
                int port = 2018;
                string host = "127.0.0.1";
                //IPAddress.Any 指示服务器应侦听所有网络接口上的客户端活动。此字段为只读。
                // IPAddress iP = IPAddress.Any;
                IPAddress ip = IPAddress.Parse(host);
                //创建端口号对象
                IPEndPoint point = new IPEndPoint(ip, port);
                //绑定
                socketWatch.Bind(point);
                //(3)监听
                socketWatch.Listen(10);
                ShowMsg($"监听成功");
                //使用委托异步的方式界面界面卡死
                Action<Socket> action = Listen;
                action.BeginInvoke(socketWatch, null, null);
                //Thread thread = new Thread(new ThreadStart(Listen));
            }
            catch
            {

            }
        }

        private void ShowMsg(string str)
        {
            this.textBox1.AppendText(str + "\r\n");
        }

        //放到方法外是因为点击发送消息按钮的时候需要通过当前的socket对象给客户端发送消息。
        Socket socket;
        Dictionary<string, Socket> dicSocket = new Dictionary<string, Socket>();
        /// <summary>
        /// 监听
        /// </summary>
        /// <param name="socketWatch"></param>
        void Listen(Socket socketWatch)
        {
            //(4)等待客户端连接,并且创建一个负责通信的Socket 
            while (true)
            {
                try
                {
                    //通信的Socket对象
                    socket = socketWatch.Accept();
                    #region  这是服务器给多个客户端中的某一个客户发送信息处理的一部分代码
                    //存储客户端信息以及处理这个客户端的socket对象
                    dicSocket[socket.RemoteEndPoint.ToString()] = socket;
                    //向下拉框添加值
                    this.comboBox1.Items.Add(socket.RemoteEndPoint.ToString());
                    #endregion
                    //RemoteEndPoint是当前客户端连接成功的IP
                    ShowMsg($"{socket.RemoteEndPoint.ToString()}:连接成功");
                    Action<Socket> action = Receive;
                    action.BeginInvoke(socket, null, null);
                }
                catch
                {

                }
            }

        } 
        /// <summary>
        /// 接收信息
        /// </summary>
        /// <param name="socket"></param>
        private void Receive(Socket socket)
        {
            while (true)
            {
                try
                {
                    //连接成功之后,服务器应该接收来自于客户端的消息,这是由负责通信的socket来实现
                    byte[] buffer = new byte[1024 * 1024 * 2];//2M
                                                              //realLength实际接收的有效字节数 
                    int realLength = socket.Receive(buffer);
                    if (realLength == 0)
                    {
                        //向下拉框移除关闭的客户端信息
                        this.comboBox1.Items.Remove(socket.RemoteEndPoint.ToString());
                        break;
                    }

                    string str = Encoding.UTF8.GetString(buffer, 0, realLength);
                    ShowMsg($"{socket.RemoteEndPoint.ToString()}:{str}");
                }
                catch
                {

                }
            }

        }


        private void textBox1_TextChanged(object sender, EventArgs e)
        {

        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //当某个线程不是控件的创建线程尝试访问该控件的一个或多个属性时,这通常会导致不可预知的结果。              常见的无效线程活动是对访问控件属性的错误线程的调用 Handle 。 设置 CheckForIllegalCrossThreadCalls 为 true 可以在调试时更轻松地查找和诊断此线程活动。 
            Control.CheckForIllegalCrossThreadCalls = false;
        }

        /// <summary>
        /// 服务器给客户端发送消息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSend_Click(object sender, EventArgs e)
        {
            try
            {
                string str = this.textBox2.Text.Trim();
                byte[] buffer = Encoding.UTF8.GetBytes(str);
                //获取选中的客户端socket对象,给对应的客户端发送信息
                string ip = this.comboBox1.SelectedItem.ToString();
                dicSocket[ip].Send(buffer); 
            }
            catch (Exception ex)
            {

            }
        }
    }

客户端代码:

public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        //客户端发送信息的Socket
        Socket socketSend;
        /// <summary>
        /// 客户端连接
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnConnection_Click(object sender, EventArgs e)
        {
            try
            {
                 
            //(1)创建套接字
            socketSend = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //(2)连接 这是服务器应用程序的IP和端口号
            int port = 2018;
            string host = "127.0.0.1";
            IPAddress ip = IPAddress.Parse(host);
            IPEndPoint point = new IPEndPoint(ip, port);
            socketSend.Connect(point);
            ShowMsg("连接成功");
            //开启子线程,不停的接收服务器消息
            Action action = Receive;
            Task.Run(action);
            }
            catch
            {

            }


        }

        /// <summary>
        /// 接收服务端信息  
        /// 记住,要想接收到服务端的消息,必须拿到当前客户端和服务端
        /// 通信的Socket对象:socketSend,通过它来获取消息
        /// </summary> 
        private void Receive()
        {
            while (true)
            {
                try
                {
                    //连接成功之后,服务器应该接收来自于客户端的消息,这是由负责通信的socket来实现
                    byte[] buffer = new byte[1024 * 1024 * 2];//2M
                                                              //realLength实际接收的有效字节数 
                    int realLength = socketSend.Receive(buffer);
                    if (realLength == 0)
                    {
                        break;
                    }

                    string str = Encoding.UTF8.GetString(buffer, 0, realLength);
                    ShowMsg($"{socketSend.RemoteEndPoint.ToString()}:{str}");
                }
                catch
                {

                }
            }

        }

        /// <summary>
        /// 展示传来的消息
        /// </summary>
        /// <param name="str"></param>
        private void ShowMsg(string str)
        {
            this.textBox1.AppendText(str + "\r\n");

        }
        /// <summary>
        /// 发送消息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSend_Click(object sender, EventArgs e)
        {
            byte[] buffer = Encoding.UTF8.GetBytes(textBox2.Text.Trim());
            //发送信息
            socketSend.Send(buffer);

        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //取消
            Control.CheckForIllegalCrossThreadCalls = false;
        }
    }

先启动服务端,开启监听,然后启动客户端,连接,发送消息,结果:

扩展 

上面代码实现了一个简答的聊天发送接收,下面实现文件的发送接收:

 

 完整服务器端:

namespace MyScoketTest
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 开始监听
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btn_Start_Click(object sender, EventArgs e)
        {
            try
            {
                //(1)创建套接字
                Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                //(2)绑定IP和端口
                int port = 2018;
                string host = "127.0.0.1";
                //IPAddress.Any 指示服务器应侦听所有网络接口上的客户端活动。此字段为只读。
                // IPAddress iP = IPAddress.Any;
                IPAddress ip = IPAddress.Parse(host);
                //创建端口号对象
                IPEndPoint point = new IPEndPoint(ip, port);
                //绑定
                socketWatch.Bind(point);
                //(3)监听
                socketWatch.Listen(10);
                ShowMsg($"监听成功");
                //使用委托异步的方式界面界面卡死
                Action<Socket> action = Listen;
                action.BeginInvoke(socketWatch, null, null);
                //Thread thread = new Thread(new ThreadStart(Listen));
            }
            catch
            {

            }
        }

        private void ShowMsg(string str)
        {
            this.textBox1.AppendText(str + "\r\n");
        }

        //放到方法外是因为点击发送消息按钮的时候需要通过当前的socket对象给客户端发送消息。
        Socket socket;
        Dictionary<string, Socket> dicSocket = new Dictionary<string, Socket>();
        /// <summary>
        /// 监听
        /// </summary>
        /// <param name="socketWatch"></param>
        void Listen(Socket socketWatch)
        {
            //(4)等待客户端连接,并且创建一个负责通信的Socket 
            while (true)
            {
                try
                {
                    //通信的Socket对象
                    socket = socketWatch.Accept();
                    #region  这是服务器给多个客户端中的某一个客户发送信息处理的一部分代码
                    //存储客户端信息以及处理这个客户端的socket对象
                    dicSocket[socket.RemoteEndPoint.ToString()] = socket;
                    //向下拉框添加值
                    this.comboBox1.Items.Add(socket.RemoteEndPoint.ToString());
                    #endregion
                    //RemoteEndPoint是当前客户端连接成功的IP
                    ShowMsg($"{socket.RemoteEndPoint.ToString()}:连接成功");
                    Action<Socket> action = Receive;
                    action.BeginInvoke(socket, null, null);
                }
                catch
                {

                }
            }

        }


        /// <summary>
        /// 接收信息
        /// </summary>
        /// <param name="socket"></param>
        private void Receive(Socket socket)
        {
            while (true)
            {
                try
                {
                    //连接成功之后,服务器应该接收来自于客户端的消息,这是由负责通信的socket来实现
                    byte[] buffer = new byte[1024 * 1024 * 2];//2M
                                                              //realLength实际接收的有效字节数 
                    int realLength = socket.Receive(buffer);
                    if (realLength == 0)
                    {
                        //向下拉框移除关闭的客户端信息
                        this.comboBox1.Items.Remove(socket.RemoteEndPoint.ToString());
                        break;
                    }

                    string str = Encoding.UTF8.GetString(buffer, 0, realLength);
                    ShowMsg($"{socket.RemoteEndPoint.ToString()}:{str}");
                }
                catch
                {

                }
            }

        }


        private void textBox1_TextChanged(object sender, EventArgs e)
        {

        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //当某个线程不是控件的创建线程尝试访问该控件的一个或多个属性时,这通常会导致不可预知的结果。 常见的无效线程活动是对访问控件属性的错误线程的调用 Handle 。              设置 CheckForIllegalCrossThreadCalls 为 true 可以在调试时更轻松地查找和诊断此线程活动。 
            Control.CheckForIllegalCrossThreadCalls = false;
        }

        /// <summary>
        /// 服务器给客户端发送消息(仅限于文字消息)
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSend_Click(object sender, EventArgs e)
        {
            try
            {
                string str = this.textBox2.Text.Trim();
                byte[] buffer = Encoding.UTF8.GetBytes(str);

                #region  为了既能够发送文本消息,也能够发送文件,所以在这里对发送的数据做处理
                List<byte> list = new List<byte>();
                //定义传送文本消息第一位是0
                list.Add(0);
                list.AddRange(buffer);
                byte[] newBuffer = list.ToArray();
                #endregion 
                //获取选中的客户端socket对象,给对应的客户端发送信息
                string ip = this.comboBox1.SelectedItem.ToString();
                dicSocket[ip].Send(newBuffer);
            }
            catch (Exception ex)
            {

            }
        }
        /// <summary>
        /// 选择要发送的文件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button1_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            //ofd.InitialDirectory = ""; 可以定义默认路径
            ofd.Title = "请选择要发送的文件";
            ofd.Filter = "所有文件|*.*";
            ofd.ShowDialog();
            //返回的是选中文件的绝对路径
            this.textBox3.Text = ofd.FileName;
        }
        /// <summary>
        /// 发送文件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button2_Click(object sender, EventArgs e)
        {
            //获取要发送的文件路径
            string filePath = this.textBox3.Text.Trim();
            using (FileStream fsRead = new FileStream(filePath, FileMode.Open, FileAccess.Read))
            {
                //5M
                byte[] buffer = new byte[1024 * 1024 * 5];
                int r = fsRead.Read(buffer, 0, buffer.Length);
                List<byte> list = new List<byte>();
                list.Add(1);
                list.AddRange(buffer);
                byte[] bufferNew = list.ToArray();
                // r + 1:因为加了一位
                dicSocket[this.comboBox1.SelectedItem.ToString()].Send(bufferNew, 0, r + 1, SocketFlags.None);
            }
        }
        /// <summary>
        /// 震动
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button3_Click(object sender, EventArgs e)
        {
            byte[] buffer = new byte[1];
            buffer[0] = 2;
            dicSocket[comboBox1.SelectedItem.ToString()].Send(buffer);
        }
    }
}

界面:

 

 

 

完整客户端:

namespace SocketClient
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        //客户端发送信息的Socket
        Socket socketSend;
        /// <summary>
        /// 客户端连接
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnConnection_Click(object sender, EventArgs e)
        {
            try
            {

                //(1)创建套接字
                socketSend = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                //(2)连接 这是服务器应用程序的IP和端口号
                int port = 2018;
                string host = "127.0.0.1";
                IPAddress ip = IPAddress.Parse(host);
                IPEndPoint point = new IPEndPoint(ip, port);
                socketSend.Connect(point);
                ShowMsg("连接成功");
                //开启子线程,不停的接收服务器消息
                Action action = Receive;
                Task.Run(action);
            }
            catch
            {

            }


        }

        /// <summary>
        /// 接收服务端信息  
        /// 记住,要想接收到服务端的消息,必须拿到当前客户端和服务端
        /// 通信的Socket对象:socketSend,通过它来获取消息
        /// </summary> 
        private void Receive()
        {
            while (true)
            {
                try
                {
                    //连接成功之后,服务器应该接收来自于客户端的消息,这是由负责通信的socket来实现
                    byte[] buffer = new byte[1024 * 1024 * 2];//2M  
                    //realLength实际接收的有效字节数 
                    int realLength = socketSend.Receive(buffer);
                    if (realLength == 0)
                    {
                        break;
                    }

                    if (buffer[0] == 0)
                    {
                        //文字消息
                        //从第二位开始
                        string str = Encoding.UTF8.GetString(buffer, 1, realLength - 1);
                        ShowMsg($"{socketSend.RemoteEndPoint.ToString()}:{str}");
                    }
                    else if (buffer[0] == 1)
                    {
                        //文件 注意在这里是不知道传来的文件后缀是什么的,到底是txt还是jpg,还是其它,要想实现这个功能也可以自己再定义一个规则,比如第二位来标志:0是txt,1是jpg,2是png,等等等。还有这里也不支持大文件的传输
                        SaveFileDialog saveFileDialog = new SaveFileDialog();
                        saveFileDialog.Title = "请选择要保存的路径";
                        saveFileDialog.Filter = "所有文件|*.*";
                        saveFileDialog.ShowDialog(this);
                        //选中的保存路径
                        string path = saveFileDialog.FileName;
                        using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write))
                        {
                            fs.Write(buffer, 1, realLength - 1);
                        }

                    }
                    else if (buffer[0] == 2)
                    {
                        //震动 用界面的移动表示震动
                        for (int i = 0; i < 500; i++)
                        {
                            this.Location = new Point(200,200);
                            this.Location = new Point(200, 200);
                        } 
                    } 
                     
                }
                catch
                {

                }
            }

            //       socketSend.Close();

        }

        /// <summary>
        /// 展示传来的消息
        /// </summary>
        /// <param name="str"></param>
        private void ShowMsg(string str)
        {
            this.textBox1.AppendText(str + "\r\n");

        }
        /// <summary>
        /// 发送消息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSend_Click(object sender, EventArgs e)
        {
            byte[] buffer = Encoding.UTF8.GetBytes(textBox2.Text.Trim());
            //发送信息
            socketSend.Send(buffer);

        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //取消
            Control.CheckForIllegalCrossThreadCalls = false;
        }
    }
}

界面:

 

 

  

关于上面代码为什么会使用过IPAddress.Any的补充:

IPAddress.Any 解决本地ip和服务器ip切换问题 

IPAddress.Any表示本机ip,换言之,如果服务器绑定此地址,则表示侦听本机所有ip对应的那个端口(本机可能有多个ip或只有一个ip)
IPAddress.Any微软给出的解释是:Provides an IP address that indicates that the server must listen for client activity on all network interfaces. This field is read-only.翻译过来就是:提供一个iP地址来指示服务器必须监听所有网卡上的客户端活动。此字段为只读的。也就是说,对双卡网或者多网卡的机器,每个网卡都会有一个独立的ip,如果使用了IPAddress.Any就表示服务器必须监听本机所有网卡上的指定端口。
比如双网卡机器,内网ip为192.168.0.1,外网ip为120.210.1.1,服务器可以同时监听192.168.0.1:80和120.210.1.1:80。 

localipAddress = Dns.Resolve(IPAddress.Any.ToString()).AddressList[0];

m_RecSocket = new TcpListener(localipAddress, m_localPort);

的写法可以改成

m_RecSocket = new TcpListener(IPAddress.Any, m_localPort);

 Socket心跳包

 优秀的socket的文章 

 

参考:https://www.bilibili.com/video/BV1FW411v7as?p=9&spm_id_from=pageDriver

 相关知识补充

Http协议详解

socket心跳包

socket心跳包,这个也可以

 

转载自安静点老大:

https://www.cnblogs.com/anjingdian/p/15323326.html

 

标签:socket,buffer,void,基础,new,客户端,Socket
From: https://www.cnblogs.com/BluceLee/p/17251353.html

相关文章

  • .Net MVC 实现WebSocket
    WebSocket 1.基于Html5,IIS8.0版本以上,前端代码和服务器都必须支持WebSocket才能使用;2.请求必须以WS:开头下面是后台接收前端websocket申请的方法:///<summary>......
  • 数据通信基础
    1、数据1、组成:字母和数据2、如:图片、视频、文字等2、数据通信1、数据交换的过程2、传输介质来进行数据传输3、传输介质:网线,无线,光纤等3、数据通信系统的组成1、发送方2、接......
  • css基础 CSS 导航栏、CSS 下拉菜单
    阅读目录导航栏导航栏=链接列表垂直导航栏垂直导航条实例激活/当前导航条实例创建链接并添加边框全屏高度的固定导航条水平导航栏内联列表项浮动列表项水平导航条实例链接右......
  • css基础 CSS 图片廊、CSS 图像透明/不透明、CSS 图像拼合技术
    阅读目录CSS图片廊公共的HTML布局CSS创建图片廊响应式图片廊CSS图像透明/不透明创建一个透明图像图像的透明度–悬停效果透明的盒子中的文字CSS图像拼合技术图像拼合......
  • css基础 CSS 组合选择符、CSS 伪类、CSS 伪元素
    阅读目录CSS组合选择符后代选择器子元素选择器相邻兄弟选择器后续兄弟选择器CSS伪类(Pseudo-classes)伪类和CSS类CSS:first-child伪类匹配第一个``元素匹配所有......
  • css基础 CSS 布局 – Overflow、Float 浮动、CSS 布局 – 水平 垂直居中对齐
    阅读目录CSS布局–Overflowoverflow:visibleoverflow:hiddenoverflow:scrolloverflow:autoverflow-x和overflow-y所有CSSOverflow属性CSSFloat浮动float属性......
  • JS基础 网络请求
    阅读目录基础知识XMLHttpRequest基本使用响应类型响应结果使用示例发送表单封装请求类FETCH请示步骤响应头解析响应内容解析实例操作发送请求get发送请求post基础知识浏览......
  • JS基础 空间坐标
    阅读目录视口与文档视口与文档尺寸几何尺寸方法列表getComputedStylegetBoundingClientRectgetClientRects坐标点元素元素集合底层元素滚动控制方法列表文档滚动位置元素滚......
  • JS基础 任务管理
    阅读目录任务管理原理分析脚本加载定时器微任务实例操作进度条任务分解任务管理JavaScript语言的一大特点就是单线程,也就是说同一个时间只能处理一个任务。为了协调事件、......
  • JS基础 模块设计
    阅读目录模块设计使用分析实现原理基础知识标签使用模块路径延迟解析模块默认运行在严格模式模块都有独立的顶级作用域预解析导入导出导出模块具名导入批量导入导入建议别名......