如何在C中存根套接字

How to stub a socket in C?

本文关键字:存根 套接字      更新时间:2023-10-16

我已经编写了客户端代码,它应该通过套接字发送一些数据并从远程服务器读取答案。

我想对代码进行单元测试。函数的签名类似如下:

double call_remote(double[] args, int fd);

其中fd为远程服务器的套接字的文件描述符。

现在,call_remote函数在发送数据后,将阻止从服务器读取答案。如何存根这样一个远程服务器来对代码进行单元测试?

理想情况下,我希望是这样的:

int main() {
  int stub = /* initialize stub */
  double expected = 42.0;
  assert(expected == call_remote(/* args */, stub);
  return 0;
}
double stub_behavior(double[] args) {
  return 42.0;
}

我希望调用stub_behavior并将42.0值发送到存根文件描述符。

有什么简单的方法吗?

如果这是POSIX系统,您可以使用fork()socketpair():

#define N_DOUBLES_EXPECTED 10
double stub_behaviour(double []);
int initialize_stub(void)
{
    int sock[2];
    double data[N_DOUBLES_EXPECTED];
    socketpair(AF_UNIX, SOCK_STREAM, 0, sock);
    if (fork()) {
        /* Parent process */
        close(sock[0]);
        return sock[1];
    }
    /* Child process */
    close(sock[1]);
    /* read N_DOUBLES_EXPECTED in */
    read(sock[0], data, sizeof data);
    /* execute stub */
    data[0] = stub_behaviour(data);
    /* write one double back */
    write(sock[0], data, sizeof data[0]);
    close(sock[0]);
    _exit(0);
}

int main()
{
  int stub = initialize_stub();
  double expected = 42.0;
  assert(expected == call_remote(/* args */, stub);
  return 0;
}
double stub_behavior(double args[])
{
  return 42.0;
}

…当然,您可能需要添加一些错误检查,并更改读取请求的逻辑。

socketpair()创建的文件描述符是一个普通的套接字,因此像send()recv()这样的套接字调用在它上面可以很好地工作。

您可以使用任何可以通过文件描述符访问的内容。一个文件或者,如果你想模拟阻塞行为,一个管道。

注意:显然套接字特定的调用(setsockopt, fcntl, ioctl,…)不会工作

我遇到了同样的情况,我将分享我的方法。我创建了网络转储,准确地记录了客户机应该发送的内容和服务器应该发送的响应内容。然后,我对客户机请求进行逐字节的比较,以确保它是匹配的。如果请求是有效的,则从响应文件中读取并将其发送回客户机。

我很高兴提供更多的细节(当我在一个机器访问这个代码)

这是一个c++实现(我知道,最初的问题是针对C的,但如果需要的话,很容易转换回C)。它可能不适用于非常大的字符串,因为如果字符串不能被缓冲,套接字可能会阻塞。但它适用于小单元测试。

/// Class creates a simple socket for testing out functions that write to a socket.
/// Usage:
///  1. Call GetSocket() to get a file description socket ID
///  2. write to that socket FD
///  3. Call ReadAll() read back all the data that was written to that socket.
///  The sockets are all closed by ReadAll(), so this is a one-use object.
///
/// example
///  MockSocket ms;
///  int socket = ms.GetSocket();
///  send(socket,"foo bar",7);
///  ...
///  std::string s = ms.ReadAll();
///  EXPECT_EQ("foo bar",s);
class MockSocket
{
public:
    ~MockSocket()
    {
    }

    int GetSocket()
    {
        socketpair(AF_UNIX, SOCK_STREAM, 0, sockets_);
        return sockets_[0];
    }
    std::string ReadAll()
    {
        close(sockets_[0]);
        std::string s;
        char buffer[256];
        while (true)
        {
            int n = read(sockets_[1], buffer, sizeof(buffer));
            if (n > 0) s.append(buffer,n);
            if (n <= 0) break;
        }
        close(sockets_[1]);
        return s;
    }
private:
    int sockets_[2];
};