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 %
相关推荐
嘉琪0012 小时前
Vue3+JS 高级前端面试题
开发语言·前端·javascript
xunyan62342 小时前
面向对象(下)-接口的理解
java·开发语言
遥不可及~~斌2 小时前
Java 面试题集 -- 001
java·开发语言
2501_921649493 小时前
如何获取美股实时行情:Python 量化交易指南
开发语言·后端·python·websocket·金融
hkhkhkhkh1233 小时前
Linux设备节点基础知识
linux·服务器·驱动开发
集智飞行3 小时前
c++函数传参的几种推荐方式
开发语言·c++
鼾声鼾语4 小时前
matlab的ros2发布的消息,局域网内其他设备收不到情况吗?但是matlab可以订阅其他局域网的ros2发布的消息(问题总结)
开发语言·人工智能·深度学习·算法·matlab·isaaclab
麦麦鸡腿堡4 小时前
Java_类的加载
java·开发语言
我命由我123454 小时前
VSCode - Prettier 配置格式化的单行长度
开发语言·前端·ide·vscode·前端框架·编辑器·学习方法