开源 C# 快速开发(八)通讯--Tcp服务器端

文章的目的为了记录使用C# 开发学习的经历。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。

相关链接:

开源 C# 快速开发(一)基础知识

开源 C# 快速开发(二)基础控件

开源 C# 快速开发(三)复杂控件

开源 C# 快速开发(四)自定义控件--波形图

开源 C# 快速开发(五)自定义控件--仪表盘

开源 C# 快速开发(六)自定义控件--圆环

开源 C# 快速开发(七)通讯--串口

开源 C# 快速开发(八)通讯--Tcp服务器端

开源 C# 快速开发(九)通讯--Tcp客户端

开源 C# 快速开发(十)通讯--http客户端

推荐链接:

开源 C# .net mvc 开发(一)WEB搭建_c#部署web程序-CSDN博客

开源 C# .net mvc 开发(二)网站快速搭建_c#网站开发-CSDN博客

开源 C# .net mvc 开发(三)WEB内外网访问-CSDN博客

开源 C# .net mvc 开发(四)工程结构、页面提交以及显示-CSDN博客

开源 C# .net mvc 开发(五)常用代码快速开发_c# mvc开发-CSDN博客

开源 C# .net mvc 开发(六)发送邮件、定时以及CMD编程-CSDN博客

开源 C# .net mvc 开发(七)动态图片、动态表格和json数据生成-CSDN博客

开源 C# .net mvc 开发(八)IIS Express轻量化Web服务器的配置和使用-CSDN博客

开源 C# .net mvc 开发(九)websocket--服务器与客户端的实时通信-CSDN博客

本章节主要内容是:Tcp服务器端程序,基于Windows Forms和串口控件实现。

目录:

1.源码分析

2.所有源码

3.效果演示

一、源码分析

  1. 构造函数 TcpServerForm()

功能分析:

初始化窗体组件(通过InitializeComponent())

创建空的客户端连接列表,用于存储所有连接的TCP客户端

初始化服务器状态为停止状态(isServerRunning = false)

复制代码
public partial class TcpServerForm : Form
{
    private TcpListener tcpListener;
    private Thread listenerThread;
    private List<TcpClient> connectedClients;
    private bool isServerRunning = false;
    
    public TcpServerForm()
    {
        InitializeComponent();
        connectedClients = new List<TcpClient>(); // 初始化客户端列表
    }
}
  1. 启动服务器 btnStart_Click(object sender, EventArgs e)

详细流程:

输入验证:检查IP地址和端口号格式是否正确

启动监听:创建TcpListener实例并调用Start()方法

线程创建:创建后台线程专门处理客户端连接请求

UI更新:禁用启动按钮,启用停止和发送按钮,更新状态标签

日志记录:记录服务器启动成功信息

复制代码
private void btnStart_Click(object sender, EventArgs e)
{
    try
    {
        // 验证IP地址输入
        if (!IPAddress.TryParse(txtIP.Text, out IPAddress ipAddress))
        {
            MessageBox.Show("请输入有效的IP地址");
            return;
        }

        // 验证端口号输入(1-65535)
        if (!int.TryParse(txtPort.Text, out int port) || port < 1 || port > 65535)
        {
            MessageBox.Show("请输入有效的端口号(1-65535)");
            return;
        }

        // 创建并启动TCP监听器
        tcpListener = new TcpListener(ipAddress, port);
        tcpListener.Start();
        isServerRunning = true;

        // 创建并启动监听线程
        listenerThread = new Thread(new ThreadStart(ListenForClients));
        listenerThread.IsBackground = true; // 设置为后台线程(主程序退出时自动终止)
        listenerThread.Start();

        // 更新UI控件状态
        btnStart.Enabled = false;
        btnStop.Enabled = true;
        btnSend.Enabled = true;
        lblStatus.Text = $"服务器已启动 - {ipAddress}:{port}";

        AddLog($"服务器启动成功,监听 {ipAddress}:{port}");
    }
    catch (Exception ex)
    {
        MessageBox.Show($"启动服务器失败: {ex.Message}");
    }
}
  1. 停止服务器 StopServer()

关键技术点:

lock关键字:防止多线程同时访问connectedClients集合

Invoke方法:确保跨线程UI更新的安全性

异常处理:使用空catch块忽略关闭连接时的异常

复制代码
private void StopServer()
{
    try
    {
        isServerRunning = false; // 设置停止标志

        // 停止TCP监听器
        tcpListener?.Stop();

        // 线程安全地关闭所有客户端连接
        lock (connectedClients) // 使用lock确保线程安全
        {
            foreach (var client in connectedClients)
            {
                try
                {
                    client.Close(); // 关闭客户端连接
                }
                catch { } // 忽略关闭过程中的异常
            }
            connectedClients.Clear(); // 清空客户端列表
        }

        // 使用Invoke确保UI操作在UI线程上执行
        Invoke(new Action(() =>
        {
            btnStart.Enabled = true;
            btnStop.Enabled = false;
            btnSend.Enabled = false;
            lblStatus.Text = "服务器已停止";
            lstClients.Items.Clear(); // 清空客户端列表显示
            AddLog("服务器已停止");
        }));
    }
    catch (Exception ex)
    {
        Invoke(new Action(() => AddLog($"停止服务器时出错: {ex.Message}")));
    }
}
  1. 客户端监听循环 ListenForClients()

重要特性:

阻塞调用:AcceptTcpClient()会阻塞线程直到有客户端连接

一客户一线程:每个客户端连接都有独立的处理线程

实时UI更新:新连接立即显示在界面上

复制代码
private void ListenForClients()
{
    while (isServerRunning) // 主循环,检查服务器运行状态
    {
        try
        {
            // 阻塞等待客户端连接(同步方法)
            TcpClient client = tcpListener.AcceptTcpClient();

            // 为每个客户端创建独立的处理线程
            Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientComm));
            clientThread.IsBackground = true;
            clientThread.Start(client); // 将客户端对象传递给线程

            // 线程安全地添加客户端到列表
            lock (connectedClients)
            {
                connectedClients.Add(client);
            }

            // 更新UI显示新连接的客户端
            Invoke(new Action(() =>
            {
                string clientInfo = $"{((IPEndPoint)client.Client.RemoteEndPoint).Address}:{((IPEndPoint)client.Client.RemoteEndPoint).Port}";
                lstClients.Items.Add(clientInfo);
                AddLog($"客户端连接: {clientInfo}");
            }));
        }
        catch (Exception ex)
        {
            if (isServerRunning) // 只有在服务器运行时才记录错误
            {
                Invoke(new Action(() => AddLog($"接受客户端连接时出错: {ex.Message}")));
            }
            break; // 发生异常时退出循环
        }
    }
}
  1. 客户端通信处理 HandleClientComm(object client)

数据处理流程:

获取网络流:通过GetStream()获取用于读写的数据流

循环读取:持续读取客户端发送的数据

编码转换:将字节数组转换为UTF-8字符串

异常处理:处理网络中断等异常情况

资源清理:在finally块中确保连接正确关闭

复制代码
private void HandleClientComm(object client)
{
    TcpClient tcpClient = (TcpClient)client;
    NetworkStream clientStream = tcpClient.GetStream(); // 获取网络流
    byte[] buffer = new byte[4096]; // 4KB接收缓冲区
    int bytesRead;

    try
    {
        while (isServerRunning && tcpClient.Connected)
        {
            // 阻塞读取客户端数据
            bytesRead = clientStream.Read(buffer, 0, buffer.Length);
            
            if (bytesRead == 0) // 连接已关闭
                break;

            // 将字节数据转换为字符串(UTF-8编码)
            string receivedData = Encoding.UTF8.GetString(buffer, 0, bytesRead);

            // 在UI线程上更新接收到的消息
            Invoke(new Action(() =>
            {
                string clientInfo = $"{((IPEndPoint)tcpClient.Client.RemoteEndPoint).Address}:{((IPEndPoint)tcpClient.Client.RemoteEndPoint).Port}";
                AddLog($"来自 {clientInfo}: {receivedData}");
            }));
        }
    }
    catch (Exception ex)
    {
        if (isServerRunning)
        {
            Invoke(new Action(() => AddLog($"处理客户端通信时出错: {ex.Message}")));
        }
    }
    finally // 确保资源清理
    {
        // 从客户端列表中移除
        lock (connectedClients)
        {
            connectedClients.Remove(tcpClient);
        }

        // 更新UI显示客户端断开
        Invoke(new Action(() =>
        {
            string clientInfo = $"{((IPEndPoint)tcpClient.Client.RemoteEndPoint).Address}:{((IPEndPoint)tcpClient.Client.RemoteEndPoint).Port}";

            // 从列表框中移除客户端
            for (int i = 0; i < lstClients.Items.Count; i++)
            {
                if (lstClients.Items[i].ToString() == clientInfo)
                {
                    lstClients.Items.RemoveAt(i);
                    break;
                }
            }

            AddLog($"客户端断开连接: {clientInfo}");
        }));

        tcpClient.Close(); // 关闭客户端连接
    }
}
  1. 消息发送 btnSend_Click(object sender, EventArgs e)

广播机制:

遍历所有客户端:向列表中的每个客户端发送相同消息

连接状态检查:发送前验证客户端是否仍然连接

自动清理:移除已断开连接的客户端

复制代码
private void btnSend_Click(object sender, EventArgs e)
{
    if (string.IsNullOrWhiteSpace(txtSend.Text))
    {
        MessageBox.Show("请输入要发送的数据");
        return;
    }

    try
    {
        byte[] data = Encoding.UTF8.GetBytes(txtSend.Text); // 字符串转字节数组

        lock (connectedClients)
        {
            List<TcpClient> disconnectedClients = new List<TcpClient>();

            foreach (var client in connectedClients)
            {
                try
                {
                    if (client.Connected)
                    {
                        NetworkStream stream = client.GetStream();
                        stream.Write(data, 0, data.Length); // 发送数据
                        stream.Flush(); // 立即发送缓冲区数据
                    }
                    else
                    {
                        disconnectedClients.Add(client); // 记录已断开的客户端
                    }
                }
                catch
                {
                    disconnectedClients.Add(client); // 记录发送失败的客户端
                }
            }

            // 清理已断开的客户端连接
            foreach (var disconnectedClient in disconnectedClients)
            {
                connectedClients.Remove(disconnectedClient);
                disconnectedClient.Close();
            }
        }

        AddLog($"发送到所有客户端: {txtSend.Text}");
        txtSend.Clear(); // 清空发送文本框
    }
    catch (Exception ex)
    {
        MessageBox.Show($"发送数据失败: {ex.Message}");
    }
}
  1. 日志记录 AddLog(string message)

线程安全机制:

InvokeRequired:检测当前是否在非UI线程

Invoke:将调用封送到UI线程执行

递归调用确保最终在UI线程上执行实际操作

复制代码
private void AddLog(string message)
{
    if (txtReceive.InvokeRequired) // 检查是否需要在UI线程上执行
    {
        txtReceive.Invoke(new Action<string>(AddLog), message); // 递归调用
    }
    else
    {
        txtReceive.AppendText($"[{DateTime.Now:HH:mm:ss}] {message}\r\n"); // 添加时间戳
        txtReceive.ScrollToCaret(); // 自动滚动到最新内容
    }
}
  1. 窗体关闭事件 OnFormClosing(FormClosingEventArgs e)

    protected override void OnFormClosing(FormClosingEventArgs e)
    {
    StopServer(); // 确保服务器正确停止
    base.OnFormClosing(e); // 调用基类方法
    }

二、所有源码

TcpServerForm .Designer.cs界面代码

复制代码
using System;
using System.Drawing;
using System.Windows.Forms;

namespace TcpServerApp
{
    partial class TcpServerForm
    {
        /*
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(800, 450);
            this.Text = "TcpServerForm";
        }

        #endregion
        */


        private System.ComponentModel.IContainer components = null;
        private TextBox txtIP;
        private TextBox txtPort;
        private Button btnStart;
        private Button btnStop;
        private TextBox txtSend;
        private Button btnSend;
        private TextBox txtReceive;
        private ListBox lstClients;
        private Label lblStatus;
        private Label label1;
        private Label label2;
        private Label label3;
        private Label label4;
        private Label label5;

        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();
            this.txtIP = new TextBox();
            this.txtPort = new TextBox();
            this.btnStart = new Button();
            this.btnStop = new Button();
            this.txtSend = new TextBox();
            this.btnSend = new Button();
            this.txtReceive = new TextBox();
            this.lstClients = new ListBox();
            this.lblStatus = new Label();
            this.label1 = new Label();
            this.label2 = new Label();
            this.label3 = new Label();
            this.label4 = new Label();
            this.label5 = new Label();

            // 窗体设置
            this.Text = "TCP服务器";
            this.Size = new Size(800, 600);
            this.StartPosition = FormStartPosition.CenterScreen;

            // IP标签和文本框
            this.label1.Location = new Point(20, 20);
            this.label1.Size = new Size(80, 20);
            this.label1.Text = "IP地址:";
            this.txtIP.Location = new Point(100, 20);
            this.txtIP.Size = new Size(150, 20);
            this.txtIP.Text = "127.0.0.1";

            // 端口标签和文本框
            this.label2.Location = new Point(270, 20);
            this.label2.Size = new Size(80, 20);
            this.label2.Text = "端口:";
            this.txtPort.Location = new Point(320, 20);
            this.txtPort.Size = new Size(80, 20);
            this.txtPort.Text = "8080";

            // 启动按钮
            this.btnStart.Location = new Point(420, 18);
            this.btnStart.Size = new Size(80, 25);
            this.btnStart.Text = "启动服务器";
            this.btnStart.Click += new EventHandler(this.btnStart_Click);

            // 停止按钮
            this.btnStop.Location = new Point(510, 18);
            this.btnStop.Size = new Size(80, 25);
            this.btnStop.Text = "停止服务器";
            this.btnStop.Enabled = false;
            this.btnStop.Click += new EventHandler(this.btnStop_Click);

            // 状态标签
            this.lblStatus.Location = new Point(20, 60);
            this.lblStatus.Size = new Size(400, 20);
            this.lblStatus.Text = "服务器未启动";

            // 客户端列表标签和列表框
            this.label3.Location = new Point(20, 100);
            this.label3.Size = new Size(100, 20);
            this.label3.Text = "已连接客户端:";
            this.lstClients.Location = new Point(20, 120);
            this.lstClients.Size = new Size(250, 200);

            // 接收数据标签和文本框
            this.label4.Location = new Point(20, 340);
            this.label4.Size = new Size(100, 20);
            this.label4.Text = "接收数据:";
            this.txtReceive.Location = new Point(20, 360);
            this.txtReceive.Multiline = true;
            this.txtReceive.ScrollBars = ScrollBars.Vertical;
            this.txtReceive.Size = new Size(750, 100);
            this.txtReceive.ReadOnly = true;

            // 发送数据标签和文本框
            this.label5.Location = new Point(20, 480);
            this.label5.Size = new Size(100, 20);
            this.label5.Text = "发送数据:";
            this.txtSend.Location = new Point(20, 500);
            this.txtSend.Multiline = true;
            this.txtSend.ScrollBars = ScrollBars.Vertical;
            this.txtSend.Size = new Size(650, 50);

            // 发送按钮
            this.btnSend.Location = new Point(680, 500);
            this.btnSend.Size = new Size(90, 50);
            this.btnSend.Text = "发送";
            this.btnSend.Enabled = false;
            this.btnSend.Click += new EventHandler(this.btnSend_Click);

            // 添加控件到窗体
            this.Controls.AddRange(new Control[] {
            this.txtIP, this.txtPort, this.btnStart, this.btnStop,
            this.txtSend, this.btnSend, this.txtReceive, this.lstClients,
            this.lblStatus, this.label1, this.label2, this.label3,
            this.label4, this.label5
        });
        }

    }
}

TcpServerForm.cs源码

复制代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;


using System.Net;
using System.Net.Sockets;

using System.Threading;




namespace TcpServerApp
{
    public partial class TcpServerForm : Form
    {

        private TcpListener tcpListener;
        private Thread listenerThread;
        private List<TcpClient> connectedClients;
        private bool isServerRunning = false;
        public TcpServerForm()
        {
            InitializeComponent();
            connectedClients = new List<TcpClient>();
        }


        private void btnStart_Click(object sender, EventArgs e)
        {
            try
            {
                // 验证输入
                if (!IPAddress.TryParse(txtIP.Text, out IPAddress ipAddress))
                {
                    MessageBox.Show("请输入有效的IP地址");
                    return;
                }

                if (!int.TryParse(txtPort.Text, out int port) || port < 1 || port > 65535)
                {
                    MessageBox.Show("请输入有效的端口号(1-65535)");
                    return;
                }

                // 启动服务器
                tcpListener = new TcpListener(ipAddress, port);
                tcpListener.Start();
                isServerRunning = true;

                // 启动监听线程
                listenerThread = new Thread(new ThreadStart(ListenForClients));
                listenerThread.IsBackground = true;
                listenerThread.Start();

                // 更新UI
                btnStart.Enabled = false;
                btnStop.Enabled = true;
                btnSend.Enabled = true;
                lblStatus.Text = $"服务器已启动 - {ipAddress}:{port}";

                AddLog($"服务器启动成功,监听 {ipAddress}:{port}");
            }
            catch (Exception ex)
            {
                MessageBox.Show($"启动服务器失败: {ex.Message}");
            }
        }

        private void btnStop_Click(object sender, EventArgs e)
        {
            StopServer();
        }

        private void StopServer()
        {
            try
            {
                isServerRunning = false;

                // 停止监听
                tcpListener?.Stop();

                // 断开所有客户端连接
                lock (connectedClients)
                {
                    foreach (var client in connectedClients)
                    {
                        try
                        {
                            client.Close();
                        }
                        catch { }
                    }
                    connectedClients.Clear();
                }

                // 更新UI
                Invoke(new Action(() =>
                {
                    btnStart.Enabled = true;
                    btnStop.Enabled = false;
                    btnSend.Enabled = false;
                    lblStatus.Text = "服务器已停止";
                    lstClients.Items.Clear();
                    AddLog("服务器已停止");
                }));
            }
            catch (Exception ex)
            {
                Invoke(new Action(() => AddLog($"停止服务器时出错: {ex.Message}")));
            }
        }

        private void ListenForClients()
        {
            while (isServerRunning)
            {
                try
                {
                    // 接受客户端连接
                    TcpClient client = tcpListener.AcceptTcpClient();

                    // 为新客户端创建处理线程
                    Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientComm));
                    clientThread.IsBackground = true;
                    clientThread.Start(client);

                    // 添加到客户端列表
                    lock (connectedClients)
                    {
                        connectedClients.Add(client);
                    }

                    // 更新UI
                    Invoke(new Action(() =>
                    {
                        string clientInfo = $"{((IPEndPoint)client.Client.RemoteEndPoint).Address}:{((IPEndPoint)client.Client.RemoteEndPoint).Port}";
                        lstClients.Items.Add(clientInfo);
                        AddLog($"客户端连接: {clientInfo}");
                    }));
                }
                catch (Exception ex)
                {
                    if (isServerRunning)
                    {
                        Invoke(new Action(() => AddLog($"接受客户端连接时出错: {ex.Message}")));
                    }
                    break;
                }
            }
        }

        private void HandleClientComm(object client)
        {
            TcpClient tcpClient = (TcpClient)client;
            NetworkStream clientStream = tcpClient.GetStream();
            byte[] buffer = new byte[4096];
            int bytesRead;

            try
            {
                while (isServerRunning && tcpClient.Connected)
                {
                    bytesRead = clientStream.Read(buffer, 0, buffer.Length);
                    if (bytesRead == 0)
                        break;

                    string receivedData = Encoding.UTF8.GetString(buffer, 0, bytesRead);

                    // 更新UI
                    Invoke(new Action(() =>
                    {
                        string clientInfo = $"{((IPEndPoint)tcpClient.Client.RemoteEndPoint).Address}:{((IPEndPoint)tcpClient.Client.RemoteEndPoint).Port}";
                        AddLog($"来自 {clientInfo}: {receivedData}");
                    }));
                }
            }
            catch (Exception ex)
            {
                if (isServerRunning)
                {
                    Invoke(new Action(() => AddLog($"处理客户端通信时出错: {ex.Message}")));
                }
            }
            finally
            {
                // 客户端断开连接
                lock (connectedClients)
                {
                    connectedClients.Remove(tcpClient);
                }

                Invoke(new Action(() =>
                {
                    string clientInfo = $"{((IPEndPoint)tcpClient.Client.RemoteEndPoint).Address}:{((IPEndPoint)tcpClient.Client.RemoteEndPoint).Port}";

                    // 从列表中移除
                    for (int i = 0; i < lstClients.Items.Count; i++)
                    {
                        if (lstClients.Items[i].ToString() == clientInfo)
                        {
                            lstClients.Items.RemoveAt(i);
                            break;
                        }
                    }

                    AddLog($"客户端断开连接: {clientInfo}");
                }));

                tcpClient.Close();
            }
        }

        private void btnSend_Click(object sender, EventArgs e)
        {
            if (string.IsNullOrWhiteSpace(txtSend.Text))
            {
                MessageBox.Show("请输入要发送的数据");
                return;
            }

            try
            {
                byte[] data = Encoding.UTF8.GetBytes(txtSend.Text);

                lock (connectedClients)
                {
                    List<TcpClient> disconnectedClients = new List<TcpClient>();

                    foreach (var client in connectedClients)
                    {
                        try
                        {
                            if (client.Connected)
                            {
                                NetworkStream stream = client.GetStream();
                                stream.Write(data, 0, data.Length);
                                stream.Flush();
                            }
                            else
                            {
                                disconnectedClients.Add(client);
                            }
                        }
                        catch
                        {
                            disconnectedClients.Add(client);
                        }
                    }

                    // 移除已断开的客户端
                    foreach (var disconnectedClient in disconnectedClients)
                    {
                        connectedClients.Remove(disconnectedClient);
                        disconnectedClient.Close();
                    }
                }

                AddLog($"发送到所有客户端: {txtSend.Text}");
                txtSend.Clear();
            }
            catch (Exception ex)
            {
                MessageBox.Show($"发送数据失败: {ex.Message}");
            }
        }

        private void AddLog(string message)
        {
            if (txtReceive.InvokeRequired)
            {
                txtReceive.Invoke(new Action<string>(AddLog), message);
            }
            else
            {
                txtReceive.AppendText($"[{DateTime.Now:HH:mm:ss}] {message}\r\n");
                txtReceive.ScrollToCaret();
            }
        }

        protected override void OnFormClosing(FormClosingEventArgs e)
        {
            StopServer();
            base.OnFormClosing(e);
        }


    }
}

三、效果演示

使用网络调试助手进行调试,设置好ip和端口,验证接收数据和发送数据

相关推荐
毕设源码-钟学长2 小时前
【开题答辩全过程】以 基于Java的戏曲网站设计与实现为例,包含答辩的问题和答案
java·开发语言
2501_916007472 小时前
Java界面开发工具有哪些?常用Java GUI开发工具推荐、实战经验与对比分享
android·java·开发语言·ios·小程序·uni-app·iphone
_码力全开_3 小时前
Python从入门到实战 (14):工具落地:用 PyInstaller 打包 Python 脚本为可执行文件
开发语言·数据结构·python·个人开发
tpoog3 小时前
[C++项目框架库]redis的简单介绍和使用
开发语言·c++·redis
yi碗汤园3 小时前
【一文了解】C#的StringComparison枚举
开发语言·前端·c#
Larry_Yanan3 小时前
QML学习笔记(十九)QML的附加信号处理器
开发语言·笔记·qt·学习·ui
某不知名網友3 小时前
I/O 多路转接之 epoll:高并发服务器的性能利器
开发语言·php
郝学胜-神的一滴4 小时前
深入理解 C++ 中的 `std::bind`:功能、用法与实践
开发语言·c++·算法·软件工程
zhangfeng11334 小时前
wgcna 相关性热图中4个颜色 4个共表达模块 的模块基因是否都要做GO/KEGG分析”,核心取决于你的**研究目标和模块的生物学意义*
开发语言·r语言·生物信息