never_four 发表于 2020-2-28 23:04

Socket通信

本帖最后由 never_four 于 2020-2-28 23:08 编辑

C# ===> WinForm   ====> Socket 通信

一.服务端UI


   https://attach.52pojie.cn//forum/202002/28/224843vkxh9nwxnnsyhsrn.png?l


二.客户端UI


   https://attach.52pojie.cn//forum/202002/28/225004mrkkd825lynr8j1z.png?l

三.Socket通信在Vs中的整体结构图


   https://attach.52pojie.cn//forum/202002/28/225221igs96yg3fb17b9k7.png?l

四.服务端代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace _001_Server
{
    public partial class FrmServer : Form
    {
      public FrmServer()
      {
            InitializeComponent();
      }

      private void FrmServer_Load(object sender, EventArgs e)
      {
            txtIp.Text = GetLocalIp.IpAddress();
            txtPort.Text = "5000";

            //关闭对文本框的非法线程操作检查
            Control.CheckForIllegalCrossThreadCalls = false;
      }

      private void btnStartServer_Click(object sender, EventArgs e)
      {
            try
            {
                //1.创建监听的Socket
                Socket listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                //2.获取当前Ip地址
                IPAddress ip = IPAddress.Parse(txtIp.Text);

                //3.组成IP地址和Port号
                IPEndPoint point = new IPEndPoint(ip, int.Parse(txtPort.Text));

                //4.服务器开始对当前应用程序进行监听 (负责监听的Socket绑定IP地址和端口号)
                listenSocket.Bind(point);

                //5.设置监听队列
                listenSocket.Listen(10);//监听队列挂起的最大长度

                //6.显示监听成功的信息
                ShowReceiveMsg("监听成功,正在等待客户端的连接......");

                //7.接收客户端发送过来的信息
                //定义线程
                Thread th = new Thread(ReConnClientMsg);

                //将线程设置为后台线程 (防止影响UI线程)
                th.IsBackground = true;

                //传入参数 并启动线程
                th.Start(listenSocket);

            }
            catch { }


      }

      //定义键值对集合 存放连接服务器的客户端的 ip地址 和 Socket 信息
      Dictionary<string, Socket> dicSocketList = new Dictionary<string, Socket>();
      private void ReConnClientMsg(object socketInfo)
      {

            try
            {
                //循环 不停的刷新来自客户端的信息
                while (true)
                {
                  //类型转换
                  Socket listenSocket = socketInfo as Socket;

                  //1.新建负责和客户端建立连接的 Socket
                  Socket reConnClientSocket = listenSocket.Accept();

                  //给键值对集合添加 连接服务器的客户端的 ip 地址 和Socket 信息
                  dicSocketList.Add(reConnClientSocket.RemoteEndPoint.ToString(), reConnClientSocket);
                  //给下拉选框中添加 连接服务器客户端的 ip 地址 和端口号
                  cmbAddressList.Items.Add(reConnClientSocket.RemoteEndPoint.ToString());


                  //2.显示成功连接客户端的信息
                  ShowReceiveMsg(reConnClientSocket.RemoteEndPoint.ToString() + ": 连接成功!!!");

                  //3.连接成功后开始接收来自客户端的信息

                  Thread th = new Thread(ReClientMsg);   //这里的ReclientMsg 是将ReClientMsg()方法,当作 参数使用   

                  //将线程设置为后台线程
                  th.IsBackground = true;

                  //启动线程
                  th.Start(reConnClientSocket);
                }
            }
            catch { }

      }

      private void ReClientMsg(object sockInfo)      //使用线程调用方法是 参数必须是Object 类型
      {
            //类型转换
            Socket ReClientMsgInfo = sockInfo as Socket;

            try
            {
                while (true)
                {
                  //1.新建缓冲区
                  byte[] buffer = new byte;

                  //2.将来自客户端的数据放入缓冲区中
                  int byteNums = ReClientMsgInfo.Receive(buffer);
                  //如果接收数据的字节大小为0 则终止在聊天区域显示
                  if (byteNums == 0) return;

                  //3.定义变量,将缓冲区中的数据转换成字符串
                  string msg = Encoding.UTF8.GetString(buffer, 0, byteNums);

                  //4.显示接收到的信息
                  ShowReceiveMsg(ReClientMsgInfo.RemoteEndPoint.ToString() + ": " + msg);
                }
            }
            catch { }

      }

      private void btnSendMsg_Click(object sender, EventArgs e)
      {
            string ip = string.Empty;
            try
            {
                //拿到下拉选框中的IP地址
                ip = cmbAddressList.SelectedItem.ToString();

            }
            catch (Exception)
            {

                MessageBox.Show("请在地址选择要发送信息的对象", "提示:", MessageBoxButtons.OK, MessageBoxIcon.Error);
                #region 三个参数的含义
                //第一个参数是:提示内容
                //第二个参数是:提示框标题
                //的三个参数是:突时框中的错误图标
                #endregion

                return;
            }

            if (string.IsNullOrEmpty(txtSendMsg.Text.Trim().ToString()))
            {
                MessageBox.Show("消息内容不能为空", "提示:", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            //将待发送的信息转换为字节数组
            byte[] buffer = Encoding.UTF8.GetBytes(txtSendMsg.Text.Trim());

            //给客户端发送消息
            dicSocketList.Send(buffer);

            //在聊天区域显示服务端发送的信息
            ShowSendMsg(txtSendMsg.Text.Trim());

            //给客户端发送一次信息清空一次文本框中的信息
            txtSendMsg.Clear();
      }


      #region 显示信息
      public void ShowReceiveMsg(string str)
      {
            txtChatList.AppendText(DateTime.Now.ToString() + "\r\n" + str + "\r\n");
      }
      public void ShowSendMsg(string str)
      {
            txtChatList.AppendText(DateTime.Now.ToString() + "\r\n" + "我: " + str + "\r\n");
      }
      #endregion
    }
}


五.服务端获取本机IP地址代码


using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace _001_Server
{
publicclass GetLocalIp
    {
      public static string IpAddress()
      {
            try
            {
                //1.得到主机名
                string hostName = Dns.GetHostName();
                //2.新建IpHostEntry容器,存放Ip地址
                IPHostEntry hostEntry = Dns.GetHostEntry(hostName);
                //3.从hostEntry容器中得到IPV4地址
                for (int i = 0; i < hostEntry.AddressList.Length; i++)
                {
                  //如果存放IP地址中的某一个成员是IPV4地址则返回这个IP地址
                  if (hostEntry.AddressList.AddressFamily==AddressFamily.InterNetwork)
                  {
                        return hostEntry.AddressList.ToString();
                  }
                }
            }
            catch (Exception ex)
            {

                return ex.Message;
            }
            return "";
      }

    }
}



六.客户端代码


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace _002_Client
{
    public partial class FrmClient : Form
    {
      public FrmClient()
      {
            InitializeComponent();
      }

      
      Socket socket;
      private void btnConnServer_Click(object sender, EventArgs e)
      {
            try
            {
                //1.新建负责与服务器端通信的Socket
                socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                #region 三个参数
                //第一个参数是:Ip地址的类型IPV4
                //第二个参数是:Socket 的类型       Socket 类型有两种===> 1. 流式(用于Tcp服务), 2.数据报形式(用于Udp服务)
                //第三个参数式:服务类型
                #endregion
               
                //2.获取ip地址
                IPAddress ip = IPAddress.Parse(txtIp.Text);

                //3.将Ip地址和Port号 组合在一起
                IPEndPoint point = new IPEndPoint(ip, int.Parse(txtPort.Text));

                //4.将ip地址和port号 放入Socket中 并连接服务器端
                socket.Connect(point);

                //5.提示连接服务器端成功!
                ShowReServerMsg("连接服务器成功!!!", socket);


                //6.接收来自服务器端的信息
                //创建线程    ===> 客户端会不停的刷新 是否有来自服务器端的消息这样会导致主窗体的UI线程执行耗时操作
                Thread th = new Thread(ReServerMsg);

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

                //启动线程
                th.Start();
            }
            catch { }

      }

      /// <summary>
      /// 接收来自服务端的信息
      /// </summary>
      private void ReServerMsg()
      {
            try
            {
                while (true)
                {
                  //1.新建缓冲区
                  byte[] buffer = new byte;

                  //2.将来自服务器端的字节类型数组信息放入缓冲区
                  int byteNums = socket.Receive(buffer);
               
                  if (byteNums == 0) return;

                  //3.将字节数组转换为字符串类型
                  string msg = Encoding.UTF8.GetString(buffer, 0, byteNums);
                  #region 三个参数的意思
                  //第一个参数是:缓冲区中的数据
                  //第二个参数是:从字节类型数组缓冲区的0位开始转换数据
                  //第三个参数是:服务器传入的字节类型数组的实际大小
                  #endregion

                  //4.在客户端聊天区域显示服务器端发送来的信息
                  ShowReServerMsg(msg, socket);
                }
            }
            catch { }


      }


      private void FrmClient_Load(object sender, EventArgs e)
      {
            //关闭对文本框的非法线程操作检查
            Control.CheckForIllegalCrossThreadCalls = false;
      }

      private void btnSendMsg_Click(object sender, EventArgs e)
      {

            //1.如果消息编辑区域的信息为空则提示,并终止给服务器发送信息
            if (string.IsNullOrEmpty(txtSendMsg.Text.Trim().ToString()))
            {
                MessageBox.Show("消息内容不能为空!", "提示:", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            //2.将发送给服务器端的信息转换为字节数组类型
            byte[] buffer = Encoding.UTF8.GetBytes(txtSendMsg.Text.Trim());

            //3.给服务器端发送消息
            socket.Send(buffer);

            //4.在客户端消息列表中显示给服务器发送的信息
            ShowSendServerMsg(txtSendMsg.Text.ToString());

            //5.给服务端发送一次信息,清除一次消息编辑区域的信息
            txtSendMsg.Clear();

      }

      #region 显示信息

      /// <summary>
      /// 显示接收来自服务器端的信息
      /// </summary>
      /// <param name="str">提示信息</param>
      /// <param name="socketInfo">服务器端的Socket的信息</param>
      private void ShowReServerMsg(string str, Socket socketInfo)
      {
            //SockInfo.RemoteEndPoint.ToString();   获得服务器端的Ip地址和端口号
            //DateTime.Now.ToString();获取当前时间日期
            txtChatList.AppendText(DateTime.Now.ToString() + "\r\n" + socketInfo.RemoteEndPoint.ToString() + ": " + str + "\r\n");
      }

      /// <summary>
      /// 在客户端信息列表中显示发送给服务器端的信息
      /// </summary>
      /// <param name="str"></param>
      private void ShowSendServerMsg(string str)
      {
            txtChatList.AppendText(DateTime.Now.ToString() + "\r\n" + "我: " + str + "\r\n");
      }
      #endregion
    }
}



七.代码源文件Gitee地址
https://gitee.com/Never_Fours/Socket.git

never_four 发表于 2020-2-28 23:10

注释特别详细,适合学Socket通信的新手哦!!!:Dweeqw:Dweeqw:Dweeqw

游泳的猪 发表于 2020-2-28 23:55

不是内行人看不懂这些是什么内容!

jun57663796 发表于 2020-2-29 00:24

感谢楼主分享!

lyswj 发表于 2020-3-1 10:16

学习了,一般都在用封装好的

never_four 发表于 2020-3-1 22:13

lyswj 发表于 2020-3-1 10:16
学习了,一般都在用封装好的

万丈高楼,平地起嘛

futing87 发表于 2020-3-2 17:07

代码复制下来学习了

iliulin 发表于 2021-1-28 17:20

地址内容404了

never_four 发表于 2021-2-2 09:09

iliulin 发表于 2021-1-28 17:20
地址内容404了

https://gitee.com/Code_Ha/Socket.git
重新补一个地址
页: [1]
查看完整版本: Socket通信