如何修复 Ansi C Tcp 客户端的分段错误?

How to fix a segmentation fault for Ansi C Tcp client?

我正在尝试扩展一个使用 Ansi C 开发的 Tcp 客户端的示例,遵循本书 "TCP/IP Sockets in C"。客户端连接到提供不同长度字符串的 Tcp 服务器,具体取决于客户端提供的请求(我开发了自己的简单协议)。当返回的字符串长度较短时,一切正常。当它们超过一定长度时(例如 4KB),客户端会因分段错误而崩溃。

使用包装器处理套接字以流式传输 i/o:

FILE *str = fdopen(sock, "r+"); // Wrap for stream I/O

并且使用 fwrite() 和 fread() 处理传输和接收。

这是在我的项目中产生错误的调用(调用者):

uint8_t inbuf[MAX_WIRE_SIZE];
size_t respSize = GetNextMsg(str, inbuf, MAX_WIRE_SIZE); // Get the message

这是 GetNextMsg() 函数的实现,用于接收数据并解帧:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <netinet/in.h>
#include "Practical.h"

/* Read 4-byte length and place in big-endian order.
 * Then read the indicated number of bytes.
 * If the input buffer is too small for the data, truncate to fit and
 * return the negation of the *indicated* length.  Thus a negative return
 * other than -1 indicates that the message was truncated.
 * (Ambiguity is possible only if the caller passes an empty buffer.)
 * Input stream is always left empty.
 */
uint32_t GetNextMsg(FILE *in, uint8_t *buf, size_t bufSize)
{
    uint32_t mSize = 0;
    uint32_t extra = 0;

    if (fread(&mSize, sizeof(uint32_t), 1, in) != 1)
        return -1;
    mSize = ntohl(mSize);
    if (mSize > bufSize)
    {
        extra = mSize - bufSize;
        mSize = bufSize; // Truncate
    }
    if (fread(buf, sizeof(uint8_t), mSize, in) != mSize)
    {
        fprintf(stderr, "Framing error: expected %d, read less\n", mSize);
        return -1;
    }
    if (extra > 0)
    { // Message was truncated
        uint32_t waste[BUFSIZE];
        fread(waste, sizeof(uint8_t), extra, in); // Try to flush the channel
        return -(mSize + extra);                  // Negation of indicated size
    }
    else
        return mSize;
}

我怀疑这可能与以下事实有关:使用 Tcp,发送方和接收方正在处理具有流行为的数据,因此接收方不被允许 一次获取所有数据,就像我开始时假设的简单示例一样。事实上,使用短字符串一切正常。对于更长的字符串,它不会。

我已经完成了一个简化的调试,将 printf 作为函数内部的第一件事插入,但是当我发生崩溃时,它甚至没有被打印出来。

作为参数传递给函数的 FILE *str 似乎有问题,当 通过套接字收到比平时更长的消息。

缓冲区的大小远远大于导致问题的消息长度(1MB 对 4KB)。 我什至尝试通过 setsockopt:

增加套接字缓冲区的大小
int rcvBufferSize;
    // Retrieve and print the default buffer size
    int sockOptSize = sizeof(rcvBufferSize);
    if (getsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rcvBufferSize, (socklen_t*)&sockOptSize) < 0)
        DieWithSystemMessage("getsockopt() failed");
    printf("Initial Receive Buffer Size: %d\n", rcvBufferSize);

    // Double the buffer size
    rcvBufferSize *= 10;
    if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rcvBufferSize, 
sizeof(rcvBufferSize)) < 0)
        DieWithSystemMessage("setsockopt() failed");

但这没有帮助。

关于原因的任何想法以及我该如何解决它?

此代码:

 { // Message was truncated
    uint32_t waste[BUFSIZE];
    fread(waste, sizeof(uint8_t), extra, in); // Try to flush the channel

extra 字节读入大小为 4*BUFSIZE 的缓冲区(4 因为您打算创建缓冲区 unit8_t,但不小心将其设置为 uint32_t)。

如果 extra 大于 4*BUFSIZE,那么您将遇到本地缓冲区溢出和堆栈损坏,可能导致崩溃。

要正确执行此操作,需要这样的东西:

  int remaining = extra;
  while (remaining > 0) {
    char waste[BUFSIZE];
    int to_read = min(BUFSIZE, remaining);
    int got = fread(waste, 1, to_read, in);
    if (got <= 0) break;
    remaining -= got;    
  }