emscripten-core / emscripten-core/emscripten

`recv()` not working even for `tests/sockets/tcp_socket_echo_client.c`

Open
#15,750 4 comments 0 reactions 0 assignees View on GitHub
Dominant language
C++
Stars
27.6k
Forks
3.6k
Avg merge
1d 1h
Merged PRs (30d)
105

Description

I have a small library written in C that uses POSIX TCP sockets which I compile to WASM via emscripten. I then call the exported WASM functions from JS in node to establish a connection to the TCP server proxied by websockify. However, this does not fully work. Even though it can connect and send data, the JS client never receives data from the server.

For this reason, I tried to play around with the test cases found in `tests/sockets/tcp_sockets_echo_client.c/` and I came up with this very minimal example (slightly simplified the boilerplate from `tcp_sockets_echo_client.c`).

```
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef __EMSCRIPTEN__
#include
#endif

static const char HANDSHAKE[] = "\x00\x01";

typedef struct {
char *buffer;
int length;
} msg_t;

// message to send to the server
#ifndef MESSAGE
#define MESSAGE "pingtothepong"
#endif

typedef enum { MSG_READ, MSG_WRITE } msg_state_t;

typedef struct {
int fd;
msg_t msg;
msg_state_t state;
} server_t;

server_t server;
msg_t echo_msg;
int echo_read;
int echo_wrote;

void finish(int result) {
if (server.fd) {
close(server.fd);
server.fd = 0;
}
#ifdef __EMSCRIPTEN__
#ifdef REPORT_RESULT
REPORT_RESULT(result);
#endif
emscripten_force_exit(result);
#else
exit(result);
#endif
}

int transport_send(int sockfd, const char *buf, size_t len) {
size_t total_sent = 0;
while (total_sent < len) {
ssize_t sent_now =
send(sockfd, buf + total_sent, len - total_sent, MSG_NOSIGNAL);
if (sent_now == -1) {
printf("failed to send\n");
return -1;
}
total_sent += (size_t)sent_now;
}
printf("total send: %u\n", total_sent);
return 0;
}

int transport_recv(int sockfd, const char *buf, size_t len) {
size_t total_received = 0;
while (total_received < len) {
ssize_t received_now =
recv(sockfd, buf + total_received, len - total_received, 0);
printf("transport_recv received: %d\n", received_now);
if (received_now == 0 && errno != EINPROGRESS) {
return -1;
}
if (received_now == -1) {
printf("coulnd't receive\n");
return -1;
}
total_received += (size_t)received_now;
}
return 0;
}

void main_loop() {
static char out[1024 * 2];
static int pos = 0;
fd_set fdr;
fd_set fdw;
int res = 0;
FD_ZERO(&fdr);
FD_ZERO(&fdw);
FD_SET(server.fd, &fdr);
FD_SET(server.fd, &fdw);
int sockfd = server.fd;
res = select(server.fd + 1, &fdr, &fdw, NULL, NULL);
static size_t state = 0;
if (FD_ISSET(server.fd, &fdw) && state == 0) {
if (res == -1) {
perror("select failed");
finish(EXIT_FAILURE);
}
printf("Trying to handshake\n");
if (transport_send(sockfd, HANDSHAKE, strlen(HANDSHAKE)) != 0) {
printf("errror sending\n");
finish(EXIT_FAILURE);
}
++state;
return;
}
if (FD_ISSET(server.fd, &fdr) && state == 1) {
int available;
res = ioctl(server.fd, FIONREAD, &available);
printf("available for reading %d\n", res);
uint32_t server_version;
if (transport_recv(sockfd, (char *)&server_version, 4) != 0) {
printf("Unexpected\n");
}
return;
}
printf("Shouldn't reach here, socket not readable\n");
}

int main() {
struct sockaddr_in addr;
int res;

memset(&server, 0, sizeof(server_t));
server.state = MSG_WRITE;

// setup the message we're going to echo
memset(&echo_msg, 0, sizeof(msg_t));
echo_msg.length = strlen(MESSAGE) + 1;
echo_msg.buffer = malloc(echo_msg.length);
strncpy(echo_msg.buffer, MESSAGE, echo_msg.length);

echo_read = 0;
echo_wrote = 0;

// create the socket and set to non-blocking
server.fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (server.fd == -1) {
perror("cannot create socket");
finish(EXIT_FAILURE);
}

// connect the socket
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(8001);
printf("This is inet \n");
if (inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr) != 1) {
perror("inet_pton failed");
finish(EXIT_FAILURE);
}

res = connect(server.fd, (struct sockaddr *)&addr, sizeof(addr));
if (res == -1 && errno != EINPROGRESS) {
perror("connect failed");
finish(EXIT_FAILURE);
}

{
int z;
struct sockaddr_in adr_inet;
socklen_t len_inet = sizeof adr_inet;
z = getsockname(server.fd, (struct sockaddr *)&adr_inet, &len_inet);
if (z != 0) {
perror("getsockname");
finish(EXIT_FAILURE);
}
char buffer[1000];
sprintf(buffer, "%s:%u", inet_ntoa(adr_inet.sin_addr),
(unsigned)ntohs(adr_inet.sin_port));
// TODO: This is not the correct result: We should have a auto-bound address
char *correct = "0.0.0.0:0";
printf("got (expected) socket: %s (%s), size %lu (%lu)\n", buffer, correct,
strlen(buffer), strlen(correct));
assert(strlen(buffer) == strlen(correct));
assert(strcmp(buffer, correct) == 0);
}

{
int z;
struct sockaddr_in adr_inet;
socklen_t len_inet = sizeof adr_inet;
z = getpeername(server.fd, (struct sockaddr *)&adr_inet, &len_inet);
if (z != 0) {
perror("getpeername");
finish(EXIT_FAILURE);
}
char buffer[1000];
sprintf(buffer, "%s:%u", inet_ntoa(adr_inet.sin_addr),
(unsigned)ntohs(adr_inet.sin_port));
char correct[1000];
sprintf(correct, "127.0.0.1:%u", 8001);
printf("got (expected) socket: %s (%s), size %lu (%lu)\n", buffer, correct,
strlen(buffer), strlen(correct));
assert(strlen(buffer) == strlen(correct));
assert(strcmp(buffer, correct) == 0);
}

emscripten_set_main_loop(main_loop, 1, 0);

return EXIT_SUCCESS;
}
```
Which I compile via:
```
emcc client.c -o out.js -s SOCKET_DEBUG -s LLD_REPORT_UNDEFINED -s EXPORTED_FUNCTIONS='_main'
```
and run:
```
node out.js
```

and a very simple TCP server written in python and proxied with websockify:

```
HOST = '0.0.0.0'
PORT = 8002

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind((HOST, PORT))
s.listen()
conn, addr = s.accept()
with conn:
print('Connected by', addr)
while True:
data = conn.recv(1024)
if not data:
print("oh no")
break
conn.send(str(12).encode('utf-8'))
print("data sent")
```

Now the issue is that after the client connects to the server and sends the data, the condition `if (FD_ISSET(server.fd, &fdr) && state == 1)` in `main_loop` is never `true` and as a consequence, we get `Shouldn't reach here, socket not readable` in `stdout`. Furthermore, on the server-side, the output has `data sent` printed.

I have also tried to run `python3 tests/runner.py sockets` to trigger the test cases for sockets that all pass. However, the printed output only refers to the servers reading/sending data to the clients, therefore making me question if the clients actually receive any data (there are `asserts` in the test cases, but the output of the clients is not displayed by the runner.).

Therefore, I wanted to test and see what the actual output of the client is when the tests are run. Instead of using the test runner, I compiled the `tcp_sockets_echo_client` with:

```
emcc test_sockets_echo_client.c -o out.js -s SOCKET_DEBUG -s LLD_REPORT_UNDEFINED -s EXPORTED_FUNCTIONS='_main'
```
and the `tcp_sockets_echo_server` with:
```
gcc server.c -o server
```
Then I used `websockify` to proxy to the server and run the client with node: `node out.js` and the output:

Client:
```
connect: ws://127.0.0.1:8001/, binary
websocket adding peer: 127.0.0.1:8001
__syscall_getsockname 3
got (expected) socket: 0.0.0.0:0 (0.0.0.0:0), size 9 (9)
got (expected) socket: 127.0.0.1:8001 (127.0.0.1:8001), size 14 (14)
websocket handle open
Socket open fd = 3
websocket send (4 bytes): 14,0,0,0
do_msg_write: sending message header for 14 bytes
websocket send (14 bytes): 112,105,110,103,116,111,116,104,101,112,111,110,103,0
do_msg_write: wrote 14 bytes 14
```

Server:
```
do_msg_read: allocating 14 bytes for message
do_msg_read: read 14 bytes
do_msg_write: sending message header for 14 bytes
do_msg_write: wrote 14 bytes 14
do_msg_read: read 0 bytes
```
Confirming my suspicion that the clients never receive any data (that is `if(!FD_ISSET(server.fd, &fdr))`) is true. Is this a bug with `recv` implementation or is there something else that I am missing?

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.