C# Socke t网络编程

效果展示

  ①客户端发送消息给服务器

    

  ②服务器发送消息给指定客户端

    

  ③服务器群发消息给客户端

     

   ③服务器发送文件给客户端

   ④服务器给客户端发送震动指令

1、什么是Socket网络编程

  两台计算机相互通信靠的就是Socket,类似两个人要通信靠电话,也就是说Socket就是电脑间(程序间)的电话机
  Socket英文原意是孔、插座,作为进程通信机制,取后一种意思,通常也称套接字,用于描述IP地址和端口。IP地址指向某平台服务器,端口用于连接到某一个应用程序。

    

  Socket在通信过程中所处位置(作用)理解:

    

    释义:男生要到女生宿舍找女朋友出去玩,不能直接进入女生宿舍去找,要经过宿管大妈,由宿管大妈打电话告知你的女朋友,电话打通后你们之间就能通话了。

    这里的宿管大妈就是负责监听的Socket,如果有男生(客户端发送请求)来了就创建负责通信的Socket(电话机),从而使该男生(客户端)与对应女生(服务器

    某个应用程序)可以进行通信了。

 2、Socket支持的协议(TCP协议、UDP协议)

  协议:类似与两个人打电话有一个默认协议就是都说普通话,如果张三是四川人李四是上海人他们都说家乡话(当地方言),可能他们都听不懂对方的话,在网络中常用的协议有:TCP协议、UDP协议

  TCP/IP协议(Transmission Control Protocol/Internet Protocol):传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网设计的。

  TCP协议:属于TCP/IP协议中的一种,相对于UDP协议安全稳定,在TCP传输过程中要经过3次握手,传输效率相对低。

    

     释义:客户端像服务器发送消息(你有空吗?),服务器回复(我有空),客户端再发给服务器(我晓得你有空了),经过了3次握手,客户端和服务器才开始通信。

  UDP协议(User Data Protocol):用户数据协议,与TCPx相对应的协议。他也属于TCP/IP协议的一种,它只管发,不管对方有没有接收,相对于TCP协议快速、效率

  高、不稳定,容易发生数据丢失。客户端直接发给服务器发送消息,不管服务器是否有空接收数据等,都发给服务器。类似于电报、电台广播。

  协议的选择?

    两种传输协议比较,无论好坏各有各自的优点。

    当数据传输的性能必须让位于数据传输的完整性、可控性和可靠性时,TCP协议(安全稳定)是当然的选择。

    当强调传输性能而不是传输完整性时,如:音频和多媒体应用,UDP协议是最好的选择。也就是说视频传输的时候用UDP协议(快速效率高),因为视频聊天时更希

    望看到对方流畅不卡,但是清晰度可能低一点。

  2、端口分类

  公认端口:也称为常用端口,端口号为0~1023,它们紧密的绑定一些特殊服务。通常这些端口的通信明确了某种服务协议,不可再重新定义它的作用对象。如80端口

  (http通信)、23端口(Telnet服务)。

  注册端口端口号为1024~49151,它们松散的绑定一些服务,也有许多服务绑定了这些端口,这些端口同样也用于其他目的,且多数没有明确定义对象,不同程序可以

  根据需求自己定义,常用于大企业。这些端口对网络的安全十分重要,所以对于服务器一般来说要关闭这些端口。

  动态端口/私有端口端口号为49152~65535,理论上不应该把常用服务分配在这些端口上,但实际上有较为特殊的程序,特别是木马就非常喜欢这些端口,因为这些端口

  常常不会引起人们注意,容易隐藏。

  3、Socket通信的基本流程及如何创建Socke

  ①、Socket通信的基本流程

    

   ②创建服务器界面

    创建一个解决方案名称Socket网络编程,在其中创建Windows 窗体应用程序(.NET Framework)名称SocketChatServer,窗体改名为FrmSocketServer。

    Ip:本地Ip地址

    Port:端口号(动态端口/私有端口

    UserIp:客户端的Ip地址

    

     服务器代码

public partial class FrmSocketServer : Form
    {
        public FrmSocketServer()
        {
            InitializeComponent();

            // 取消捕获对错误线程的调用(解决跨域线程的问题)
            //Control.CheckForIllegalCrossThreadCalls = false;
        }

        //定义一个键值对集合用于存储客户端的Ip、端口及负责通信的Socket
        Dictionary<string, Socket> dicSocket = new Dictionary<string, Socket>();

        List<Socket> clientCommunicationSocketList = new List<Socket>();

        private void StartMonitor_Click(object sender, EventArgs e)
        {
            // 创建负责监听的Socket
            // 第一个参数AddressFamily设置网络寻址协议,InterNetwork表示IPV4
            // 第二个参数SocketType设置数据传输方式(Socket类型),这个要根据第三个参数来设置,Stream此类型的 Socket 与单个对方主机进行通信,并且在通信开始之前需要远程主机连接
            // 第三个参数为UDP协议时,第二个参数就要为Dgram(报文)
            Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            // 绑定监听的Ip和Port
            IPAddress ip = IPAddress.Parse(textIp.Text);
            int port = Convert.ToInt32(textPort.Text);
            IPEndPoint endPoint = new IPEndPoint(ip, port);
            socketWatch.Bind(endPoint);

            ShowMsg("监听成功");
            StartMonitor.Enabled = false;

            // 设置监听队列数量(如果监听太多就要使用分流)
            socketWatch.Listen(10);

            // 创建一个线程去执行这个方法
            Thread thread = new Thread(Listen);

            // 设置后台线程
            thread.IsBackground = true;

            // 标记这个线程准备就绪了,可以随时被执行,具体什么时候执行这个线程由CPU决定
            thread.Start(socketWatch);
        }

        Socket socketCommunication;

        /// <summary>
        /// 让线程去执行负责通信的Socket
        /// </summary>
        /// <param name="obj"></param>
        private void Listen(object obj)
        {
            Socket socketWatch = obj as Socket;

            // 不断地监听客户端有没有发送请求过来
            while (true)
            {
                // 创建负责监听的Socket
                socketCommunication = socketWatch.Accept();
                dicSocket.Add(socketCommunication.RemoteEndPoint.ToString(), socketCommunication);
                clientCommunicationSocketList.Add(socketCommunication);
                ShowMsg(socketCommunication.RemoteEndPoint.ToString() + "连接成功");

                // 解决跨线程的访问
                if (cboUser.InvokeRequired)
                {
                    cboUser.Invoke(new Action(() =>
                    {
                        cboUser.Items.Add(socketCommunication.RemoteEndPoint.ToString());
                    }), null);
                }
                else
                {
                    cboUser.Items.Add(socketCommunication.RemoteEndPoint.ToString());
                }

                // 创建一个线程去执行接收客户端发送过来消息这个方法
                Thread thread = new Thread(Receive);

                // 设置后台线程
                thread.IsBackground = true;

                // 标记这个线程准备就绪了,可以随时被执行,具体什么时候执行这个线程由CPU决定
                thread.Start(socketCommunication);
            }
        }

        /// <summary>
        /// 接收客户端发送过来消息
        /// </summary>
        /// <param name="obj"></param>
        private void Receive(object obj)
        {
            Socket socketWatch = obj as Socket;

            // 定义接收数据的大小
            byte[] buffer = new byte[1024 * 1024 * 2];

            while (true)
            {
                try
                {
                    // r 表示实际接收数据的字节数
                    int r = socketWatch.Receive(buffer);

                    // 解决(关闭窗口后 r=0)
                    if (r == 0)
                    {
                        // break;
                        socketWatch.Shutdown(SocketShutdown.Both);
                        socketWatch.Close();
                        return;
                    }
                    // 字节数据转字符串
                    string str = Encoding.UTF8.GetString(buffer, 0, r);
                    // 显示消息格式:客户端Ip端口,消息
                    ShowMsg(socketWatch.RemoteEndPoint.ToString() + ":" + str);
                }
                catch
                {
                }
            }
        }

        private void ShowMsg(string str)
        {
            // 解决跨线程的访问
            if (textReceiveMsg.InvokeRequired)
            {
                textReceiveMsg.Invoke(new Action<string>(s =>
                {
                    textReceiveMsg.AppendText(s + "
");
                }), str);
            }
            else
            {
                //textReceiveMsg.Text = str + "
" + textReceiveMsg.Text;
                textReceiveMsg.AppendText(str + "
");
            }
        }

        /// <summary>
        /// 发送消息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SendMsg_Click(object sender, EventArgs e)
        {
            string str = textSendMsg.Text.Trim();

            // 字符串转字节数组
            byte[] buffer = Encoding.UTF8.GetBytes(str);

            #region 第一种思路
            byte[] newBuffer = new byte[buffer.Length + 1];
            newBuffer[0] = 0;
            Buffer.BlockCopy(buffer, 0, newBuffer, 1, buffer.Length);
            #endregion

            #region 第二种思路
            //List<byte> list = new List<byte>();
            //list.Add(0);
            //list.AddRange(buffer);
            //byte[] newBuffer = list.ToArray();
            #endregion

            string ipPort = cboUser.SelectedItem.ToString();

            // 只能给最后一个客户端发送消息
            //socketCommunication.Send(buffer);

            // 给指定客户端发送消息
            dicSocket[ipPort].Send(newBuffer);

            textSendMsg.Clear();
        }

        /// <summary>
        /// 群发消息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void BulkMsg_Click(object sender, EventArgs e)
        {
            foreach (var socket in clientCommunicationSocketList)
            {
                if (socket.Connected)
                {
                    string str = textSendMsg.Text.Trim();

                    // 字符串转字节数组
                    byte[] buffer = Encoding.UTF8.GetBytes(str);
                    
                    
                    #region 第一种思路
                    //byte[] newBuffer = new byte[buffer.Length + 1];
                    //newBuffer[0] = 0;
                    //Buffer.BlockCopy(buffer, 0, newBuffer, 1, buffer.Length);
                    #endregion

                    #region 第二种思路
                    List<byte> list = new List<byte>();
                    list.Add(0);
                    list.AddRange(buffer);
                    byte[] newBuffer = list.ToArray();
                    #endregion

                    socket.Send(newBuffer);
                }
            }
            textSendMsg.Clear();
        }

        /// <summary>
        /// 选择文件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SelectFile_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();

            // 初始目录
            ofd.InitialDirectory = @"C:UsersDHRDesktop";
            ofd.Title = "请选择要发送的文件";
            ofd.Filter = "所有文件|*.*|txt文本文档|*.txt";
            ofd.ShowDialog();

            textPath.Text = ofd.FileName;
        }

        /// <summary>
        /// 发送文件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SendFile_Click(object sender, EventArgs e)
        {
            #region 第一种思路,使用文件流方式读取
            string path = textPath.Text;
            using (FileStream fsRead = new FileStream(path, FileMode.Open, FileAccess.Read))
            {
                // 定义发送文件的大小
                byte[] buffer = new byte[1024 * 1024 * 3];
                // 实际读取到的字节数(文件存储到buffer数组中)
                int r = fsRead.Read(buffer, 0, buffer.Length);
                List<byte> list = new List<byte>();
                list.Add(1);
                list.AddRange(buffer);
                byte[] newBuffer = list.ToArray();

                string ipPort = cboUser.SelectedItem.ToString();

                // 为什么要加后面3个参数(newBuffer这里是3M+1byte 实际文件可能不会超过3M)
                dicSocket[ipPort].Send(newBuffer, 0, r + 1, SocketFlags.None);
            }
            #endregion

            #region 第二种思路,使用File.ReadAllBytes读取
            //using (OpenFileDialog ofd = new OpenFileDialog())
            //{
            //    if (ofd.ShowDialog() != DialogResult.OK)
            //    {
            //        return;
            //    }
            //    // 把文件写入到字节数组
            //    byte[] buffer = File.ReadAllBytes(ofd.FileName);
            //    byte[] newBuffer = new byte[buffer.Length + 1];
            //    newBuffer[0] = 1;
            //    Buffer.BlockCopy(buffer, 0, newBuffer, 1, buffer.Length);

            //    string ipPort = cboUser.SelectedItem.ToString();

            //    dicSocket[ipPort].Send(newBuffer, SocketFlags.None);
            //}
            #endregion
        }

        private void Shock_Click(object sender, EventArgs e)
        {
            byte[] buffer = new byte[1];
            buffer[0] = 2;
            string ipPort = cboUser.SelectedItem.ToString();

            dicSocket[ipPort].Send(buffer);
        }
    }

  ③创建客户端界面

    

     客户端代码

    public partial class FrmSocketClinet : Form
    {
        public FrmSocketClinet()
        {
            InitializeComponent();

            // 取消捕获对错误线程的调用(解决跨域线程的问题)
            //Control.CheckForIllegalCrossThreadCalls = false;
        }

        Socket socketCommunication;

        private void StartConnect_Click(object sender, EventArgs e)
        {
            // 创建负责通信的Socket
            // 第一个参数AddressFamily设置网络寻址协议,InterNetwork表示IPV4
            // 第二个参数SocketType设置数据传输方式(Socket类型),这个要根据第三个参数来设置,Stream此类型的 Socket 与单个对方主机进行通信,并且在通信开始之前需要远程主机连接
            // 第三个参数为UDP协议时,第二个参数就要为Dgram(报文)
            socketCommunication = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            // 连接到服务器
            IPAddress ip = IPAddress.Parse(textIp.Text);
            int port = Convert.ToInt32(textPort.Text);
            IPEndPoint endPoint = new IPEndPoint(ip, port);
            socketCommunication.Connect(endPoint);

            ShowMsg("连接成功");
            StartConnect.Enabled = false;

            // 创建一个线程去执行接收服务器发送过来消息这个方法
            Thread thread = new Thread(Receive);

            // 设置后台线程
            thread.IsBackground = true;

            // 标记这个线程准备就绪了,可以随时被执行,具体什么时候执行这个线程由CPU决定
            thread.Start();
        }

        /// <summary>
        /// 接收服务器发送过来消息
        /// </summary>
        /// <param name="obj"></param>
        private void Receive()
        {
            // 定义接收数据的大小
            byte[] buffer = new byte[1024 * 1024 * 2];

            while (true)
            {
                try
                {
                    // r 表示实际接收数据的字节数
                    int r = socketCommunication.Receive(buffer);

                    // 解决(服务器关闭 r=0)
                    if (r == 0)
                    {
                        // break;
                        socketCommunication.Shutdown(SocketShutdown.Both);
                        socketCommunication.Close();
                        return;
                    }
                    int n = buffer[0];

                    // 文本d
                    if (n == 0)
                    {
                        // 字节数据转字符串
                        //string str = Encoding.UTF8.GetString(buffer, 0, r);
                        string str = Encoding.UTF8.GetString(buffer, 1, r - 1);

                        // 显示消息格式:客户端Ip端口,消息
                        ShowMsg(socketCommunication.RemoteEndPoint.ToString() + ":" + str);
                    }
                    // 文件
                    else if (n == 1)
                    {
                        #region 第一种思路,使用文件流写
                        SaveFileDialog sfd = new SaveFileDialog();
                        sfd.InitialDirectory = @"C:UsersDHRDesktop";
                        sfd.Title = "请选择要保存文件的位置";
                        sfd.Filter = "所有文件|*.*|txt文本文档|*.txt";

                        // 解决跨线程的访问
                        if (this.InvokeRequired)
                        {
                            this.Invoke(new Action(() =>
                            {
                                sfd.ShowDialog(this);
                            }), null);
                        }
                        else
                        {
                            sfd.ShowDialog(this);
                        }

                        string path = sfd.FileName;
                        using (FileStream fsWrite = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write))
                        {
                            fsWrite.Write(buffer, 1, r - 1);
                        }
                        MessageBox.Show("保存成功");
                        #endregion

                        #region 第二种思路 使用File.ReadAllBytes写
                        //using (SaveFileDialog sfd = new SaveFileDialog())
                        //{
                        //    sfd.InitialDirectory = @"C:UsersDHRDesktop";
                        //    sfd.Title = "请选择要保存文件的位置";
                        //    sfd.Filter = "所有文件|*.*|txt文本文档|*.txt";
                        //    sfd.DefaultExt = "txt";

                        //    // 解决跨线程的访问
                        //    if (this.InvokeRequired)
                        //    {
                        //        this.Invoke(new Action(() =>
                        //        {
                        //            if (sfd.ShowDialog(this) != DialogResult.OK)
                        //            {
                        //                return;
                        //            }
                        //        }), null);
                        //    }
                        //    else
                        //    {
                        //        if (sfd.ShowDialog(this) != DialogResult.OK)
                        //        {
                        //            return;
                        //        }
                        //    }

                        //    byte[] newBuffer = new byte[r - 1];
                        //    Buffer.BlockCopy(buffer, 1, newBuffer, 0, r - 1);
                        //    File.WriteAllBytes(sfd.FileName, newBuffer);
                        //    MessageBox.Show("保存成功");
                        //}
                        #endregion

                    }
                    // 震动
                    else if (n == 2)
                    {
                        Shock();
                    }
                }
                catch (Exception e)
                {

                }
            }
        }

        /// <summary>
        ///震动方法
        /// </summary>
        private void Shock()
        {
            // 记录窗体的初始位置
            Point p = new Point(this.Location.X, this.Location.Y);
           
            // 初始化Random对象
            Random r = new Random();

            // 解决跨线程的访问
            if (this.InvokeRequired)
            {
                this.Invoke(new Action(() =>
                {
                    for (int i = 0; i < 3000; i++)
                    {
                        this.Location = new Point(p.X + r.Next(-2, 2), p.Y + r.Next(-2, 2));

                    }
                }), null);
            }
            else
            {
                for (int i = 0; i < 3000; i++)
                {
                    this.Location = new Point(p.X + r.Next(-10, 10), p.Y + r.Next(-10, 10));
                }
            }

        }

        private void ShowMsg(string str)
        {
            // 解决跨线程的访问
            if (textReceiveMsg.InvokeRequired)
            {
                textReceiveMsg.Invoke(new Action<string>(s =>
                {
                    textReceiveMsg.AppendText(s + "
");
                }), str);
            }
            else
            {
                //textReceiveMsg.Text = str + "
" + textReceiveMsg.Text;
                textReceiveMsg.AppendText(str + "
");
            }
        }


        /// <summary>
        /// 发送消息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SendMsg_Click(object sender, EventArgs e)
        {
            string str = textSendMsg.Text.Trim();

            // 字符串转字节数组
            byte[] buffer = Encoding.UTF8.GetBytes(str);
            socketCommunication.Send(buffer);

            textSendMsg.Clear();
        }
    }

  需要代码的关注私聊哦!

  后续会陆续更新其他资料,喜欢请关注哦!

  我的博客:https://www.cnblogs.com/duhaoran/

原文地址:https://www.cnblogs.com/duhaoran/p/12924344.html