使用Socket发送Base64编码文件时遭遇TooLongFrameException问题求助
大家好,我正在用C#开发一个基于System.Net.Sockets.Socket的MIME格式邮件客户端,测试环境用的是Java编写的Test-Mail-Server。最近遇到一个棘手的问题:当我把文件转成Base64编码后通过Socket发送时,服务器抛出了TooLongFrameException,但直接发送文件原始字节却没问题,想请教下各位该怎么解决?
问题重现过程
我先建立Socket连接,依次发送SMTP命令(HELO、MAIL FROM、RCPT TO、DATA),然后读取本地docx文件转成Base64字符串发送,最后发送.r\n结束DATA阶段,代码如下:
Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); client.Connect(IPAddress.Parse("127.0.0.1"), 2225); client.Send(Encoding.ASCII.GetBytes("HELO test.com\r\n")); client.Send(Encoding.ASCII.GetBytes("MAIL FROM: <abc@gmail.com>\r\n")); client.Send(Encoding.ASCII.GetBytes("RCPT TO: <abc@gmail.com>\r\n")); client.Send(Encoding.ASCII.GetBytes("DATA\r\n")); byte[] data = File.ReadAllBytes(@"D:\test.docx"); string contentBase64 = Convert.ToBase64String(data); client.Send(Encoding.ASCII.GetBytes(contentBase64)); client.Send(Encoding.ASCII.GetBytes(".\r\n"));
报错信息
服务器抛出的异常栈如下:
An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception. io.netty.handler.codec.TooLongFrameException: frame length (over 74240) exceeds the allowed maximum (16384) at io.netty.handler.codec.LineBasedFrameDecoder.fail(LineBasedFrameDecoder.java:150) at io.netty.handler.codec.LineBasedFrameDecoder.decode(LineBasedFrameDecoder.java:122) at io.netty.handler.codec.DelimiterBasedFrameDecoder.decode(DelimiterBasedFrameDecoder.java:232) at io.netty.handler.codec.DelimiterBasedFrameDecoder.decode(DelimiterBasedFrameDecoder.java:216) at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:489) at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:428) at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:265) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:286) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1434) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:965) at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163) at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:647) at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:582) at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:499) at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:461) at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:884) at java.lang.Thread.run(Unknown Source)
已尝试的解决方案
我试过Copilot和ChatGPT给出的分块读取文件转Base64再合并发送的方法,但还是报同样的错,代码如下:
static void SendFile(NetworkStream stream, string filePath) { byte[] buffer = new byte[4096]; int bytesRead; using (FileStream fileStream = new FileStream(filePath, FileMode.Open)) using (MemoryStream memoryStream = new MemoryStream()) { while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) > 0) { // Convert the chunk to Base64 and write it to the memory stream string base64Chunk = Convert.ToBase64String(buffer, 0, bytesRead); byte[] base64Bytes = Encoding.ASCII.GetBytes(base64Chunk); memoryStream.Write(base64Bytes, 0, base64Bytes.Length); } // Send the entire Base64 content in one go stream.Write(memoryStream.ToArray(), 0, (int)memoryStream.Length); } Console.WriteLine("File sent successfully."); }
我的疑问
为什么直接发文件字节没问题,转成Base64就报错?分块转码的思路哪里错了?应该怎么修改才能让服务器正常接收Base64编码的文件?
解决方案(社区解答)
这个问题的核心原因是服务器使用了Netty的LineBasedFrameDecoder按行解析数据,而你生成的Base64字符串是一整段超长内容,没有换行,超过了解码器默认的16384字节帧长度限制。而直接发送原始文件字节时,文件内容里可能存在自然换行,刚好符合解码器的行解析规则,所以没报错。
针对这个问题,有两个关键修复点:
1. 给Base64编码内容添加换行符(符合MIME规范)
根据RFC 2045的规定,Base64编码的内容应该每76个字符插入一个\r\n换行。C#的Convert.ToBase64String有一个重载可以自动处理这个:使用Base64FormattingOptions.InsertLineBreaks参数,生成带换行的Base64字符串,这样每一行的长度就不会超过服务器的帧限制。
修改后的Base64生成代码:
string contentBase64 = Convert.ToBase64String(data, Base64FormattingOptions.InsertLineBreaks);
2. 完善MIME邮件格式(可选但重要)
你当前的DATA阶段只发送了Base64内容,缺少必要的MIME头部信息,服务器可能无法正确识别这是一个附件。建议在发送Base64内容前,先发送MIME头部:
// 发送MIME邮件头部 string mimeHeaders = "Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document; name=\"test.docx\"\r\n" + "Content-Transfer-Encoding: base64\r\n" + "Content-Disposition: attachment; filename=\"test.docx\"\r\n\r\n"; client.Send(Encoding.ASCII.GetBytes(mimeHeaders));
3. 优化发送流程(可选)
另外,发送SMTP命令后最好读取服务器的响应(比如发送HELO后读取返回的250状态码),确保前一个命令执行成功再发送下一个,避免因服务器状态异常导致的隐藏问题。
最终修改后的完整代码示例
Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); client.Connect(IPAddress.Parse("127.0.0.1"), 2225); // 发送SMTP命令并读取响应(示例简化,实际应处理响应) client.Send(Encoding.ASCII.GetBytes("HELO test.com\r\n")); // 此处可添加读取服务器响应的代码 client.Send(Encoding.ASCII.GetBytes("MAIL FROM: <abc@gmail.com>\r\n")); // 此处可添加读取服务器响应的代码 client.Send(Encoding.ASCII.GetBytes("RCPT TO: <abc@gmail.com>\r\n")); // 此处可添加读取服务器响应的代码 client.Send(Encoding.ASCII.GetBytes("DATA\r\n")); // 此处可添加读取服务器响应的代码 // 发送MIME头部 string mimeHeaders = "Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document; name=\"test.docx\"\r\n" + "Content-Transfer-Encoding: base64\r\n" + "Content-Disposition: attachment; filename=\"test.docx\"\r\n\r\n"; client.Send(Encoding.ASCII.GetBytes(mimeHeaders)); // 读取文件并生成带换行的Base64 byte[] data = File.ReadAllBytes(@"D:\test.docx"); string contentBase64 = Convert.ToBase64String(data, Base64FormattingOptions.InsertLineBreaks); client.Send(Encoding.ASCII.GetBytes(contentBase64)); // 结束DATA阶段(注意前面加换行,确保.单独一行) client.Send(Encoding.ASCII.GetBytes("\r\n.\r\n")); // 此处可添加读取服务器响应的代码
备注:内容来源于stack exchange,提问作者Do Tan Ngoc Anh

