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 %
相关推荐
wjs20242 小时前
jEasyUI 启用行内编辑
开发语言
夕除2 小时前
js--6
java·开发语言
ytttr8732 小时前
C#实现海康威视智能车牌识别
开发语言·c#
梵刹古音2 小时前
【C语言】 关键字与用户标识符
c语言·开发语言
悟能不能悟2 小时前
grpc协议
开发语言
四维碎片2 小时前
【Qt】代理(Delegate)的使用
开发语言·qt
鹓于3 小时前
Excel一键生成炫彩二维码
开发语言·前端·javascript
froginwe113 小时前
MongoDB 固定集合详解
开发语言
迷路爸爸1803 小时前
无sudo权限远程连接Ubuntu服务器安装TeX Live实操记录(适配VS Code+LaTeX Workshop,含路径选择与卸载方案)
java·服务器·ubuntu·latex
我送炭你添花3 小时前
树莓派 3B+ 部署 TR-069 ACS(自动配置服务器)GenieACS 实录
运维·服务器·网络协议