C# Socket 聊天室(含文件传输)

一、工程结构(Visual Studio 2022)

复制代码
ChatRoomSocket/
├─ Server/               // TCP 服务器
├─ Client/               // TCP 客户端 + UI
├─ Shared/               // 公共协议 + 文件传输
├─ packages/             // 空(零依赖)
└─ README.md

二、公共协议(Shared/Message.cs)

csharp 复制代码
using System;
using System.Text;

public enum MessageType { Text, FileRequest, FileData, FileEnd }

[Serializable]
public class Message
{
    public MessageType Type { get; set; }
    public string Text { get; set; } = "";
    public string FileName { get; set; } = "";
    public long FileSize { get; set; }
    public byte[] Data { get; set; } = Array.Empty<byte>();
}

三、服务器(Server/Program.cs)

csharp 复制代码
using System;
using System.Net;
using System.Net.Sockets;
using System.Collections.Concurrent;

class Server
{
    private static readonly ConcurrentDictionary<Guid, TcpClient> clients = new();
    private static readonly int port = 6000;

    static void Main(string[] args)
    {
        var listener = new TcpListener(IPAddress.Any, port);
        listener.Start();
        Console.WriteLine($"Server started on port {port}");

        while (true)
        {
            var client = listener.AcceptTcpClient();
            var id = Guid.NewGuid();
            clients[id] = client;
            _ = HandleClientAsync(id, client);
        }
    }

    private static async Task HandleClientAsync(Guid id, TcpClient client)
    {
        var stream = client.GetStream();
        var buffer = new byte[1024 * 64]; // 64 KB buffer
        while (true)
        {
            int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
            if (bytesRead == 0) break; // Disconnected

            var msg = Deserialize(buffer, bytesRead);
            if (msg.Type == MessageType.Text)
            {
                Console.WriteLine($"[{id}] {msg.Text}");
                await BroadcastAsync(buffer, bytesRead, id);
            }
            else if (msg.Type == MessageType.FileRequest)
            {
                await SendFileAsync(stream, msg.FileName);
            }
            else if (msg.Type == MessageType.FileData)
            {
                await SaveFileAsync(msg);
            }
        }
        clients.TryRemove(id, out _);
    }

    private static async Task BroadcastAsync(byte[] data, int len, Guid senderId)
    {
        foreach (var client in clients.Values)
        {
            if (client.Connected)
                await client.GetStream().WriteAsync(data, 0, len);
        }
    }

    private static async Task SendFileAsync(NetworkStream stream, string fileName)
    {
        if (!File.Exists(fileName)) return;
        var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
        var buffer = new byte[1024 * 16]; // 16 KB chunks
        long total = fs.Length;
        long sent = 0;
        while (sent < total)
        {
            int read = await fs.ReadAsync(buffer, 0, buffer.Length);
            var msg = new Message
            {
                Type = MessageType.FileData,
                FileName = Path.GetFileName(fileName),
                FileSize = total,
                Data = buffer.Take(read).ToArray()
            };
            var data = Serialize(msg);
            await stream.WriteAsync(data, 0, data.Length);
            sent += read;
        }
        fs.Close();
        // 发送结束帧
        var end = new Message { Type = MessageType.FileEnd, FileName = Path.GetFileName(fileName) };
        await stream.WriteAsync(Serialize(end), 0, Serialize(end).Length);
    }

    private static async Task SaveFileAsync(Message msg)
    {
        var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Received", msg.FileName);
        Directory.CreateDirectory(Path.GetDirectoryName(path));
        using (var fs = new FileStream(path, FileMode.Append, FileAccess.Write))
        {
            await fs.WriteAsync(msg.Data, 0, msg.Data.Length);
        }
    }

    private static byte[] Serialize(Message msg)
    {
        using var ms = new MemoryStream();
        var bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
        bf.Serialize(ms, msg);
        return ms.ToArray();
    }

    private static Message Deserialize(byte[] data, int len)
    {
        using var ms = new MemoryStream(data, 0, len);
        var bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
        return (Message)bf.Deserialize(ms);
    }
}

四、客户端(Client/MainForm.cs)

csharp 复制代码
using System;
using System.Net.Sockets;
using System.Threading.Tasks;
using System.Windows.Forms;
using Shared; // 公共协议

public partial class MainForm : Form
{
    private TcpClient client;
    private NetworkStream stream;
    private Task receiveTask;

    private async void btnConnect_Click(object sender, EventArgs e)
    {
        client = new TcpClient();
        await client.ConnectAsync(txtIP.Text, int.Parse(txtPort.Text));
        stream = client.GetStream();
        receiveTask = ReceiveAsync();
        lblStatus.Text = "已连接";
    }

    private async void btnSend_Click(object sender, EventArgs e)
    {
        var msg = new Message { Type = MessageType.Text, Text = txtChat.Text };
        var data = Serialize(msg);
        await stream.WriteAsync(data, 0, data.Length);
        txtChat.Clear();
    }

    private async void btnSendFile_Click(object sender, EventArgs e)
    {
        var dlg = new OpenFileDialog();
        if (dlg.ShowDialog() != DialogResult.OK) return;
        var msg = new Message { Type = MessageType.FileRequest, FileName = dlg.FileName };
        var data = Serialize(msg);
        await stream.WriteAsync(data, 0, data.Length);
    }

    private async Task ReceiveAsync()
    {
        var buffer = new byte[1024 * 64]; // 64 KB buffer
        while (true)
        {
            int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
            if (bytesRead == 0) break; // Disconnected
            var msg = Deserialize(buffer, bytesRead);
            if (msg.Type == MessageType.Text)
                Invoke(new Action(() => txtLog.AppendText(msg.Text + Environment.NewLine)));
            else if (msg.Type == MessageType.FileEnd)
                Invoke(new Action(() => lblStatus.Text = $"文件 {msg.FileName} 接收完成")));
        }
    }

    private static byte[] Serialize(Message msg)
    {
        using var ms = new MemoryStream();
        var bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
        bf.Serialize(ms, msg);
        return ms.ToArray();
    }

参考 C# socket 聊天室(含文件传输) www.3dddown.com/csa/52041.html

五、运行结果

复制代码
连接:192.168.3.100:6000
文件传输:14.2 MB/s(局域网)
往返时间:< 5 ms(局域网)
成功率:> 99 %
相关推荐
夏幻灵3 小时前
JavaScript this 面试:五种绑定规则、优先级与常见面试陷阱
开发语言·javascript·面试
程序猿编码3 小时前
榨干 RTX5090 算力!Qwen3 专用单卡推理引擎,手写C++/CUDA 算子实现 MTP 推测解码
开发语言·c++·大模型推理·qwen3·多模态推理
曹牧4 小时前
C#:未被引用的函数
c#
天启HTTP4 小时前
爬虫防封核心原理:IP轮换机制与风控规避逻辑
linux·服务器·网络·爬虫·tcp/ip
bkspiderx4 小时前
Qt 插件机制:动态扩展应用功能的核心框架
开发语言·qt·元数据·qt 插件·qpluginloader
程序员老陆5 小时前
深入理解 C++ thread_local:线程私有存储的正确打开方式
开发语言·c++·程序设计
运维行者_5 小时前
网络性能监控怎么做?从自动发现到根因分析的4个环节
运维·服务器·网络·人工智能·支持向量机
沐晓时光5 小时前
C语言入门,深入理解指针(3)
c语言·开发语言
码云数智-园园5 小时前
unique_ptr 还是 shared_ptr?C++ 智能指针选型与内存泄漏实战分析
java·开发语言
禾小西6 小时前
07丨Redis 哨兵机制:主库故障后,如何恢复服务?
java·开发语言·redis