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 %
相关推荐
小CC吃豆子3 分钟前
Java数据结构与算法
java·开发语言
晨旭缘4 分钟前
后端日常启动及常用命令(Java)
java·开发语言
山峰哥5 分钟前
3000字深度解析:SQL调优如何让数据库查询效率提升10倍
java·服务器·数据库·sql·性能优化·编辑器
星辰_mya12 分钟前
RockerMQ之commitlog与consumequeue
java·开发语言
꧁Q༒ོγ꧂13 分钟前
C++ 入门完全指南(六)--指针与动态内存
开发语言·c++
不想上班的小吕13 分钟前
采购申请创建(BAPI_PR_CREATE/BAPI_REQUISITION_CREATE)
java·服务器·数据库
IT=>小脑虎16 分钟前
2026版 Go语言零基础衔接进阶知识点【详解版】
开发语言·后端·golang
ChangYan.16 分钟前
ffi-napi运行失败,报错:No native build was found,解决办法
开发语言
专注VB编程开发20年16 分钟前
压栈顺序是反向(从右往左)的,但正因为是反向压栈,所以第一个参数反而离栈顶(ESP)最近。
java·开发语言·算法
Xの哲學17 分钟前
Linux Select 工作原理深度剖析: 从设计思想到实现细节
linux·服务器·网络·算法·边缘计算