C# Socket 客户端 逻辑思路 【初学者提问】

初学者请教个问题,Winform程序作客户端连接多个服务端进行通讯,C# Socket 也是刚学,没找到合适的书或是资料,看了好多博客写了些,但是一直不能用,想请大伙给看看,提些问题或者建议,感谢的😁

public partial class Form1 : MaterialForm
    {
        #region 已知信息存有以下信息的DataTable表
        //dt表里存的是服务端的IP、端口号、还有发送的内容
        DataTable dt_ServerInfo = new DataTable();
        #endregion

        #region 变量声明
        private Dictionary<string, Socket> socketClients = new Dictionary<string, Socket>() { };    //客户端套接字集合
        byte[] dataSend = new byte[500];
        byte[] ReceiveByte = new byte[1024];
        #endregion

        #region 线程委托等实例
        Thread SendThr = null;
        #endregion

        public Form1()
        {
            InitializeComponent();
            GenerateDataTable();   //生成dt表结构和内容
            CreateSocketConnection();    //创建Socket和建立连接
        }

 #region 自定义函数
        private void CreateSocketConnection()
        {
            int countOfServers = dt_ServerInfo.Rows.Count;
            for (int i = 0; i < countOfServers; i++)
            {
                Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Parse(dt_ServerInfo.Rows[i]["serverIP"].ToString()),int.Parse(dt_ServerInfo.Rows[i]["serverPort"].ToString()));
                serverSocket.BeginConnect(serverEndPoint, new AsyncCallback(ConnectCallBack), serverSocket);
            }
        }

        private void ConnectCallBack(IAsyncResult asyncResult)
        {
            Socket client = (Socket)asyncResult.AsyncState; //客户端套接字
            try
            {
                client.EndConnect(asyncResult);
                socketClients.Add(client.RemoteEndPoint.ToString(), client);
            }
            catch (Exception)       //客户端开启后未连接,自动重连
            {
                IPEndPoint temppoint = (IPEndPoint)client.RemoteEndPoint;
                client.BeginConnect(temppoint, ConnectCallBack, client);
            }

        }

        private void SendThread()
        {
            while (socketClients.Count() > 0)
            {
                foreach (var item in socketClients)
                {
                    string serverInfo = item.Key.ToString();
                    //string tst = $"ServerIp = '{serverInfo.Split(':')[0]}' And ServerPort = '{serverInfo.Split(':')[1]}' ";
                    DataRow[] drs = dt_ServerInfo.Select($"ServerIp = '{serverInfo.Split(':')[0]}' And ServerPort = '{serverInfo.Split(':')[1]}'");
                    byte[] sendByte = hexStringToByteArray(drs[0]["Command"].ToString());
                    item.Value.BeginSend(sendByte, 0, sendByte.Length, SocketFlags.None, new AsyncCallback(SendCallback), item.Value);
                }
            }
        }

        private void SendCallback(IAsyncResult asyncResult)
        {
            Socket client = (Socket)asyncResult.AsyncState; //客户端套接字
            try
            {
                client.EndSend(asyncResult);
                client.BeginReceive(ReceiveByte, 0, ReceiveByte.Length, SocketFlags.None, new AsyncCallback(AsyncReceiveCall), client);
            }
            catch (Exception)       //当客户端无法发送命令时,自动断开连接并进行重连
            {
                IPEndPoint temppoint = (IPEndPoint)client.RemoteEndPoint;
                socketClients[temppoint.ToString()].Close();
                client.BeginConnect(temppoint, ConnectCallBack, client);
                socketClients.Remove(temppoint.ToString());
            }

        }

        private void AsyncReceiveCall(IAsyncResult asyncResult)
        {
            Socket client = (Socket)asyncResult.AsyncState; //客户端套接字
            int bytesRead = client.EndReceive(asyncResult);
            MessageBox.Show(ReceiveByte.ToString());
        }

这段代码中存在一些潜在的问题,可能会导致程序无法正常工作,以下是一些建议:

1.Dictionary<string, Socket> 可能会导致线程不安全的问题。由于你的应用程序需要在多个线程中操作套接字,因此如果在操作中修改集合,可能会导致线程竞争条件。因此,建议使用 ConcurrentDictionary<string, Socket> 代替 Dictionary<string, Socket> 来确保线程安全。
2.ConnectCallBack() 方法中的异常处理可以更加细致。当前的处理是在发生任何异常时进行自动重连,但这可能会导致无限次的重连尝试。建议对特定的异常类型进行处理,并在重试次数达到一定限制后停止重连。
3.SendThread() 中的 foreach 循环可能会导致性能问题。在此方法中,每个循环迭代都会向套接字发送命令,并在返回时等待异步回调,这可能会导致很多线程被阻塞。可以考虑将发送命令的过程与异步回调分开,并将异步回调委托给另一个线程处理。
4.AsyncReceiveCall() 方法中 MessageBox.Show(ReceiveByte.ToString()) 可能不会正确显示接收到的数据。当前的代码将字节数组转换为字符串并将其显示在消息框中。但这种转换方法可能会导致不可预测的结果,因为字节数组中的每个字节都不一定是有效的 ASCII 字符。建议使用 System.Text.Encoding 类来将字节数组转换为字符串,例如 Encoding.UTF8.GetString(ReceiveByte, 0, bytesRead)。
希望这些建议可以帮助你改进你的程序并解决你遇到的问题。

这个把主要是你找错了方向,某个园子的文章脱离程序届很久了。都是些耍眼球的人自娱自乐的产物

要看代码请去nuget,git。找到程序员们在实践中最新的代码去看,看不懂这些代码在去查。最起码这些代码是新的,而且敢把代码直接show在git的上的也都是有那个自信经的起全世界的coder去coderview的

这个你可以看Rsocket,虽然这个是半成品,而且实现的是Rsocket自己的协议,不过单单就如何处理socket就比大多数园子的博文代码要好太多
项目地址:


对于你处理client来说,主要看他的Transports---物理传输层的实现

你的核心参考代码
https://github.com/rsocket/rsocket-net/blob/master/RSocket.Core/Transports/SocketTransport.cs

我提取一下核心代码

//开始连接
public async Task StartAsync(CancellationToken cancel = default)
        {
          //检查dns联通性
            var dns = await Dns.GetHostEntryAsync(Url.Host);
            if (dns.AddressList.Length == 0) { throw new InvalidOperationException($"Unable to resolve address."); }
            Endpoint = new IPEndPoint(dns.AddressList[0], Url.Port);

           //新建socket
            Socket = new Socket(Endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            //连接服务器
            Socket.Connect(dns.AddressList, Url.Port);  //TODO Would like this to be async... Why so serious???
           //连接成功处理,发送和接受
            Running = ProcessSocketAsync(Socket);
        }

        public Task StopAsync() => Task.CompletedTask;        //TODO More graceful shutdown

        private async Task ProcessSocketAsync(Socket socket)
        {
            // Begin sending and receiving. Receiving must be started first because ExecuteAsync enables SendAsync.
         //启动接收任务
            var receiving = StartReceiving(socket);
         //启动发送任务
            var sending = StartSending(socket);
         //等待这两个任务任意一个结束
            var trigger = await Task.WhenAny(receiving, sending);

        
        }


这样的代码是不是比那些园子的文章要清楚明白的多。
当然他内部应用了System.io.pipelines做“公共缓存”,用IDuplexPipe做了一个抽象,这个到需要你自己理解

img


这图能也很清楚表达的意思,当你按这个IDuplexPipe实现代码后,你后续代码将不必在管socket的本身了
你想发送就往上面的Write里写,他自己reader后发出去
同样你后面接收只管从reader读,他从socket里获取的数据自动写到另个write里

这样你学习完这个代码,就无惧“Tcp,udp,串口,蓝牙”了,因为你最后使用的只是IDuplexPipe,而不是tcp,udp,串口,蓝牙他们的差异在Transports层被统一了