【问题标题】:TCP server in Webassembly (C++) fails with accept()Webassembly (C++) 中的 TCP 服务器因 accept() 而失败
【发布时间】:2020-08-02 23:21:03
【问题描述】:

我用 C 语言编写了一个简单的 TCP 服务器-客户端本机应用程序。它按预期工作(Linux Fedora 以及适用于 Windows 10 的 Cygwin)。当我使用 EM++(或 EMCC)编译它时,它可以很好地构建 JS 和 WASM。

$ em++ TcpService.cpp -o TcpService.js -s ASYNCIFY=1

但是,在运行服务器时,我看到以下错误;虽然它一直在等待客户端数据的到来,但它从未真正连接:

$节点TcpService.js

等待消息到达...

TypeError:无法读取未定义的属性“流”

TypeError:无法读取未定义的属性“流”

...

我将问题缩小到了 accept(3) 调用。请注意,以前,除了上面的 TypeError 之外,我还会看到一个堆栈跟踪(不是很有用)。现在我正在用 "-s ASYNCIFY=1" 编译代码,堆栈跟踪消失了。但是,客户端程序仍然没有连接(无论是原生构建还是它的 WASM)。

服务器程序使用简单的 BSD 套接字:

void server(void)
{
  // port to start the server on
  int SERVER_PORT = 8877;

  // socket address used for the server
  struct sockaddr_in _ServerAddr;
  memset(&_ServerAddr, 0, sizeof(_ServerAddr));
  _ServerAddr.sin_family = AF_INET;

  // htons: host to network short: transforms a value in host byte
  // ordering format to a short value in network byte ordering format
  _ServerAddr.sin_port = htons(SERVER_PORT);

  // htonl: host to network long: same as htons but to long
  _ServerAddr.sin_addr.s_addr = htonl(INADDR_ANY);

  // create a TCP socket, creation returns -1 on failure
  int listen_sock;
  if ((listen_sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
    throw std::runtime_error("could not create listen socket\n");
  }

  // bind it to listen to the incoming connections on the created server
  // address, will return -1 on error
  if ((bind(listen_sock, (struct sockaddr *)&_ServerAddr,
            sizeof(_ServerAddr))) < 0) {
    throw std::runtime_error("could not bind socket\n");
  }

  int wait_size = 16;  // maximum number of waiting clients, after which
                       // dropping begins
  if (listen(listen_sock, wait_size) < 0) {
    throw std::runtime_error("could not open socket for listening\n");
  }

  // socket address used to store client address
  struct sockaddr_in client_address;
  int client_address_len = 0;

  // run indefinitely
  while (true) {

    // open a new socket to transmit data per connection
    int sock;
    printf("Waiting for arrival of messages ...\n");
    if ((sock =
             accept(listen_sock, (struct sockaddr *)&client_address,
                    (socklen_t *)&client_address_len)) < 0) {
      throw std::runtime_error("could not open a socket to accept data\n");
    }
    int n = 0;
    int len = 0, maxlen = 100;
    char buffer[maxlen];
    char *pbuffer = buffer;

    printf("client connected with ip address: %s\n",
           inet_ntoa(client_address.sin_addr));

    // keep running as long as the client keeps the connection open
    while ((n = recv(sock, pbuffer, maxlen, 0)) > 0) {
      pbuffer += n;
      maxlen -= n;
      len += n;

      printf("received: '%s'\n", buffer);

      // echo received content back
      send(sock, buffer, len, 0);
    }

    close(sock);
  }

  close(listen_sock);
}

客户端同样简单:

void sendMessage(void)
{
  memset(&server_address, 0, sizeof(server_address));
  server_address.sin_family = AF_INET;

  inet_pton(AF_INET, server_name, &server_address.sin_addr);

  server_address.sin_port = htons(server_port);
  if ((_Sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
    printf("could not create socket\n");
  }

  if (connect(_Sock, (struct sockaddr*)&server_address,
             sizeof(server_address)) < 0) {
    printf("could not connect to server\n");
    return;
  }
  const char* data_to_send = "test message";
  send(_Sock, data_to_send, strlen(data_to_send), 0);
}

谷歌搜索错误“TypeError: Cannot read property 'stream' of undefined”并没有证明非常有用,尤其是 WRT 流属性 - 大多数页面只与 AWS 相关,而我的是一个简单的本地测试程序。

在让节点运行应用程序之前,我还安装了所需的 websockets NPM。

【问题讨论】:

  • 这是一个非常令人兴奋的项目!你成功了吗?

标签: javascript c++ sockets tcp webassembly


【解决方案1】:

如果您有现有的使用 Posix Sockets API 的 C/C++ 编写的 TCP 网络代码,默认情况下 Emscripten 会尝试模拟此类连接以通过 WebSocket 协议进行。为此,您需要在服务器端使用 WebSockify 之类的东西,以使 TCP 服务器堆栈能够接收传入的 WebSocket 连接。目前此仿真还不是很完整,您可能会遇到开箱即用的问题,需要调整代码以在此仿真提供的限制范围内工作。

这是 POSIX 套接字的默认构建模式,无需链接器标志或选项设置即可启用它。

基于 WebSocket 代理服务器的完整 POSIX 套接字

Emscripten 提供了一个本地 POSIX Sockets 代理服务器程序,位于目录 tools/websocket_to_posix_proxy/,它允许从 Web 浏览器访问完整的 POSIX Sockets API。这种支持通过将浏览器中的所有 POSIX Sockets API 调用代理到 ​​Emscripten POSIX Sockets 代理服务器(通过透明使用 WebSockets API)来实现,然后代理服务器代表页面执行本机 TCP/UDP 调用。这允许 Web 浏览器页面运行完整的 TCP 和 UDP 连接,充当服务器以接受传入连接,并执行主机名查找和反向查找。因为所有 API 调用都是单独代理的,所以这种支持可能很慢。这种支持对于开发测试基础设施和调试非常有用。

以下 POSIX 套接字函数以这种方式代理: socket()、socketpair()、shutdown()、bind()、connect()、listen()、accept()、getsockname()、getpeername()、send()、recv()、sendto()、recvfrom( )、sendmsg()、recvmsg()、getsockopt()、setsockopt()、getaddrinfo()、`getnameinfo()。 以下 POSIX 套接字函数当前未代理(并且将不起作用): poll(), close() (使用 shutdown() 代替), select() 要使用 POSIX 套接字代理,请使用标志“-lwebsocket.js -s PROXY_POSIX_SOCKETS=1 -s USE_PTHREADS=1 -s PROXY_TO_PTHREAD=1”链接应用程序。也就是说,POSIX 套接字代理构建在 Emscripten WebSockets 库之上,并且需要多线程并将应用程序 main() 代理到 pthread。

有关 POSIX Sockets 代理服务器如何在 Emscripten 客户端程序中工作的示例,请参阅文件 tests/websocket/tcp_echo_client.cpp。

https://emscripten.org/docs/porting/networking.html

【讨论】:

  • 这没有意义? - accept() 应该不会失败,因为 WebSocket 是基于 TCP 的,因此应该接受 TCP 连接?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-07-15
  • 2012-10-12
  • 2012-05-23
  • 2017-01-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多