static void testDBufferNetHandling (void) { dbuffer* buf = new_dbuffer(); NET_WriteByte(buf, 'b'); CU_ASSERT_EQUAL(1, dbuffer_len(buf)); NET_WriteShort(buf, 128); CU_ASSERT_EQUAL(3, dbuffer_len(buf)); NET_WriteLong(buf, 128); CU_ASSERT_EQUAL(7, dbuffer_len(buf)); free_dbuffer(buf); }
static void NET_ShowStreams_f (void) { char buf[256]; int cnt = 0; for (int i = 0; i < MAX_STREAMS; i++) { if (streams[i] == nullptr) continue; Com_Printf("Steam %i is opened: %s on socket %i (closed: %i, finished: %i, outbound: " UFO_SIZE_T ", inbound: " UFO_SIZE_T ")\n", i, NET_StreamPeerToName(streams[i], buf, sizeof(buf), true), streams[i]->socket, streams[i]->closed, streams[i]->finished, dbuffer_len(streams[i]->outbound), dbuffer_len(streams[i]->inbound)); cnt++; } Com_Printf("%i/%i streams opened\n", cnt, MAX_STREAMS); }
/** * @sa NET_StreamFinished * @sa NET_StreamNew */ static void NET_StreamClose (struct net_stream* s) { if (!s || s->closed) return; if (s->socket != INVALID_SOCKET) { if (dbuffer_len(s->outbound)) Com_Printf("The outbound buffer for this socket (%d) is not empty\n", s->socket); else if (dbuffer_len(s->inbound)) Com_Printf("The inbound buffer for this socket (%d) is not empty\n", s->socket); FD_CLR(s->socket, &read_fds); FD_CLR(s->socket, &write_fds); netCloseSocket(s->socket); s->socket = INVALID_SOCKET; } if (s->index >= 0) streams[s->index] = nullptr; if (s->loopback_peer) { /* Detach the peer, so that it won't send us anything more */ s->loopback_peer->outbound = dbufferptr(); s->loopback_peer->loopback_peer = nullptr; } s->closed = true; Com_DPrintf(DEBUG_SERVER, "Close stream at index: %i\n", s->index); s->outbound = dbufferptr(); s->socket = INVALID_SOCKET; /* Note that this is potentially invalid after the callback returns */ if (s->finished) { s->inbound = dbufferptr(); if (s->onclose != nullptr) s->onclose(); Mem_Free(s); s = nullptr; } else if (s->func) { s->func(s); } if (s != nullptr && s->onclose != nullptr) s->onclose(); }
static void testDBufferBigData (void) { int i; int count = 100; byte *data; /* this entity string may not contain any inline models, we don't have the bsp tree loaded here */ const int size = FS_LoadFile("game/entity.txt", &data); dbuffer* buf = new_dbuffer(); for (i = 0; i < count; i++) { dbuffer_add(buf, (char *)data, size); } CU_ASSERT_EQUAL(size * count, dbuffer_len(buf)); free_dbuffer(buf); FS_FreeFile(data); }
/** * @brief Call NET_StreamFinished to mark the stream as uninteresting, but to * finish sending any data in the buffer. The stream will appear * closed after this call, and at some unspecified point in the future * s will become an invalid pointer, so it should not be further * referenced. */ void NET_StreamFinished (struct net_stream* s) { if (!s) return; s->finished = true; if (s->socket != INVALID_SOCKET) FD_CLR(s->socket, &read_fds); /* Stop the loopback peer from queueing stuff up in here */ if (s->loopback_peer) s->loopback_peer->outbound = dbufferptr(); const ScopedMutex scopedMutex(netMutex); s->inbound = dbufferptr(); /* If there's nothing in the outbound buffer, any finished stream is * ready to be closed */ if (dbuffer_len(s->outbound) == 0) NET_StreamClose(s); }
static struct net_stream* NET_DoConnect (const char* node, const char* service, const struct addrinfo* addr, int i, stream_onclose_func* onclose) { struct net_stream* s; SOCKET sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); if (sock == INVALID_SOCKET) { Com_Printf("Failed to create socket: %s\n", netStringError(netError)); return nullptr; } if (!NET_SocketSetNonBlocking(sock)) { netCloseSocket(sock); return nullptr; } if (connect(sock, addr->ai_addr, addr->ai_addrlen) != 0) { const int err = netError; #ifdef _WIN32 if (err != WSAEWOULDBLOCK) { #else if (err != EINPROGRESS) { #endif Com_Printf("Failed to start connection to %s:%s: %s\n", node, service, netStringError(err)); netCloseSocket(sock); return nullptr; } } s = NET_StreamNew(i); s->socket = sock; s->inbound = dbufferptr(new dbuffer(4096)); s->outbound = dbufferptr(new dbuffer(4096)); s->family = addr->ai_family; s->addrlen = addr->ai_addrlen; s->onclose = onclose; maxfd = std::max(sock + 1, maxfd); FD_SET(sock, &read_fds); return s; } /** * @brief Try to connect to a given host on a given port * @param[in] node The host to connect to * @param[in] service The port to connect to * @param[in] onclose The callback that is called on closing the returned stream. This is useful if * you hold the pointer for the returned stream anywhere else and would like to get notified once * this pointer is invalid. * @sa NET_DoConnect * @sa NET_ConnectToLoopBack * @todo What about a timeout */ struct net_stream* NET_Connect (const char* node, const char* service, stream_onclose_func* onclose) { struct addrinfo* res; struct addrinfo hints; int rc; struct net_stream* s = nullptr; int index; OBJZERO(hints); hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV; hints.ai_socktype = SOCK_STREAM; /* force ipv4 */ if (net_ipv4->integer) hints.ai_family = AF_INET; rc = getaddrinfo(node, service, &hints, &res); if (rc != 0) { Com_Printf("Failed to resolve host %s:%s: %s\n", node, service, gai_strerror(rc)); return nullptr; } index = NET_StreamGetFree(); if (index == -1) { Com_Printf("Failed to connect to host %s:%s, too many streams open\n", node, service); freeaddrinfo(res); return nullptr; } s = NET_DoConnect(node, service, res, index, onclose); freeaddrinfo(res); return s; } /** * @param[in] onclose The callback that is called on closing the returned stream. This is useful if * you hold the pointer for the returned stream anywhere else and would like to get notified once * this pointer is invalid. * @sa NET_Connect */ struct net_stream* NET_ConnectToLoopBack (stream_onclose_func* onclose) { struct net_stream* client, *server; int server_index, client_index; if (!server_running) return nullptr; server_index = NET_StreamGetFree(); client_index = NET_StreamGetFree(); if (server_index == -1 || client_index == -1 || server_index == client_index) { Com_Printf("Failed to connect to loopback server, too many streams open\n"); return nullptr; } client = NET_StreamNew(client_index); client->loopback = true; client->inbound = dbufferptr(new dbuffer(4096)); client->outbound = dbufferptr(new dbuffer(4096)); client->onclose = onclose; server = NET_StreamNew(server_index); server->loopback = true; server->inbound = client->outbound; server->outbound = client->inbound; server->func = server_func; server->onclose = nullptr; client->loopback_peer = server; server->loopback_peer = client; server_func(server); return client; } /** * @brief Enqueue a network message into a stream * @sa NET_StreamDequeue */ void NET_StreamEnqueue (struct net_stream* s, const char* data, int len) { if (len <= 0 || !s || s->closed || s->finished) return; if (s->outbound) { const ScopedMutex scopedMutex(netMutex); s->outbound->add(data, len); } /* on linux, socket is int, and INVALID_SOCKET -1 * on windows it is unsigned and INVALID_SOCKET (~0) * Let's hope that checking for INVALID_SOCKET is good enough for linux. */ //if (s->socket >= 0) if (s->socket != INVALID_SOCKET) FD_SET(s->socket, &write_fds); if (s->loopback_peer) { loopback_ready = true; s->loopback_peer->ready = true; } } /** * @brief Returns the length of the waiting inbound buffer */ static int NET_StreamPeek (struct net_stream* s, char* data, int len) { if (len <= 0 || !s) return 0; dbufferptr& dbuf = s->inbound; if ((s->closed || s->finished) && dbuffer_len(dbuf) == 0) return 0; return dbuf->get(data, len); } /** * @sa NET_StreamEnqueue */ int NET_StreamDequeue (struct net_stream* s, char* data, int len) { if (len <= 0 || !s || s->finished) return 0; return s->inbound->extract(data, len); } /** * @brief Reads messages from the network channel and adds them to the dbuffer * where you can use the NET_Read* functions to get the values in the correct * order * @sa NET_StreamDequeue */ dbuffer* NET_ReadMsg (struct net_stream* s) { unsigned int v; const ScopedMutex scopedMutex(netMutex); if (NET_StreamPeek(s, (char*)&v, 4) < 4) return nullptr; int len = LittleLong(v); if (NET_StreamGetLength(s) < (4 + len)) return nullptr; char tmp[256]; const int size = sizeof(tmp); NET_StreamDequeue(s, tmp, 4); dbuffer* buf = new dbuffer(); while (len > 0) { const int x = NET_StreamDequeue(s, tmp, std::min(len, size)); buf->add(tmp, x); len -= x; } return buf; }
/** * @sa Qcommon_Frame */ void NET_Wait (int timeout) { struct timeval tv; int ready; int i; fd_set read_fds_out; fd_set write_fds_out; memcpy(&read_fds_out, &read_fds, sizeof(read_fds_out)); memcpy(&write_fds_out, &write_fds, sizeof(write_fds_out)); /* select() won't notice that loopback streams are ready, so we'll * eliminate the delay directly */ if (loopback_ready) timeout = 0; tv.tv_sec = timeout / 1000; tv.tv_usec = 1000 * (timeout % 1000); #ifdef _WIN32 if (read_fds_out.fd_count == 0) { Sys_Sleep(timeout); ready = 0; } else #endif ready = select(maxfd, &read_fds_out, &write_fds_out, nullptr, &tv); if (ready == -1) { Com_Printf("select failed: %s\n", netStringError(netError)); return; } if (ready == 0 && !loopback_ready) return; if (server_socket != INVALID_SOCKET && FD_ISSET(server_socket, &read_fds_out)) { const SOCKET client_socket = accept(server_socket, nullptr, 0); if (client_socket == INVALID_SOCKET) { if (errno != EAGAIN) Com_Printf("accept on socket %d failed: %s\n", server_socket, netStringError(netError)); } else do_accept(client_socket); } for (i = 0; i < MAX_STREAMS; i++) { struct net_stream* s = streams[i]; if (!s) continue; if (s->loopback) { /* If the peer is gone and the buffer is empty, close the stream */ if (!s->loopback_peer && NET_StreamGetLength(s) == 0) { NET_StreamClose(s); } /* Note that s is potentially invalid after the callback returns - we'll close dead streams on the next pass */ else if (s->ready && s->func) { s->func(s); } continue; } if (s->socket == INVALID_SOCKET) continue; if (FD_ISSET(s->socket, &write_fds_out)) { char buf[4096]; int len; if (dbuffer_len(s->outbound) == 0) { FD_CLR(s->socket, &write_fds); /* Finished streams are closed when their outbound queues empty */ if (s->finished) NET_StreamClose(s); continue; } { const ScopedMutex scopedMutex(netMutex); len = s->outbound->get(buf, sizeof(buf)); len = send(s->socket, buf, len, 0); s->outbound->remove(len); } if (len < 0) { Com_Printf("write on socket %d failed: %s\n", s->socket, netStringError(netError)); NET_StreamClose(s); continue; } Com_DPrintf(DEBUG_SERVER, "wrote %d bytes to stream %d (%s)\n", len, i, NET_StreamPeerToName(s, buf, sizeof(buf), true)); } if (FD_ISSET(s->socket, &read_fds_out)) { char buf[4096]; const int len = recv(s->socket, buf, sizeof(buf), 0); if (len <= 0) { if (len == -1) Com_Printf("read on socket %d failed: %s\n", s->socket, netStringError(netError)); NET_StreamClose(s); continue; } else { if (s->inbound) { SDL_LockMutex(netMutex); s->inbound->add(buf, len); SDL_UnlockMutex(netMutex); Com_DPrintf(DEBUG_SERVER, "read %d bytes from stream %d (%s)\n", len, i, NET_StreamPeerToName(s, buf, sizeof(buf), true)); /* Note that s is potentially invalid after the callback returns */ if (s->func) s->func(s); continue; } } } } for (i = 0; i < MAX_DATAGRAM_SOCKETS; i++) { struct datagram_socket* s = datagram_sockets[i]; if (!s) continue; if (FD_ISSET(s->socket, &write_fds_out)) { if (s->queue) { struct datagram* dgram = s->queue; const int len = sendto(s->socket, dgram->msg, dgram->len, 0, (struct sockaddr* )dgram->addr, s->addrlen); if (len == -1) Com_Printf("sendto on socket %d failed: %s\n", s->socket, netStringError(netError)); /* Regardless of whether it worked, we don't retry datagrams */ s->queue = dgram->next; Mem_Free(dgram->msg); Mem_Free(dgram->addr); Mem_Free(dgram); if (!s->queue) s->queue_tail = &s->queue; } else { FD_CLR(s->socket, &write_fds); } } if (FD_ISSET(s->socket, &read_fds_out)) { char buf[256]; char addrbuf[256]; socklen_t addrlen = sizeof(addrbuf); const int len = recvfrom(s->socket, buf, sizeof(buf), 0, (struct sockaddr* )addrbuf, &addrlen); if (len == -1) Com_Printf("recvfrom on socket %d failed: %s\n", s->socket, netStringError(netError)); else s->func(s, buf, len, (struct sockaddr* )addrbuf); } } loopback_ready = false; }
static inline int NET_StreamGetLength (struct net_stream* s) { return s ? dbuffer_len(s->inbound) : 0; }
static void testDBuffer (void) { int i; const int size = 10000000; dbuffer* buf = new_dbuffer(); char data[128]; size_t dataSize = sizeof(data); for (i = 0; i < size; i++) dbuffer_add(buf, "b", 1); CU_ASSERT_EQUAL(size, dbuffer_len(buf)); CU_ASSERT_EQUAL(dataSize, dbuffer_get(buf, data, dataSize)); CU_ASSERT_EQUAL(size, dbuffer_len(buf)); CU_ASSERT_EQUAL(dataSize, dbuffer_extract(buf, data, dataSize)); CU_ASSERT_EQUAL(size - dataSize, dbuffer_len(buf)); free_dbuffer(buf); buf = new_dbuffer(); dbuffer_add(buf, "b", 1); CU_ASSERT_EQUAL(1, dbuffer_len(buf)); CU_ASSERT_EQUAL(1, dbuffer_get(buf, data, dataSize)); CU_ASSERT_EQUAL(1, dbuffer_len(buf)); CU_ASSERT_EQUAL(1, dbuffer_extract(buf, data, dataSize)); CU_ASSERT_EQUAL(0, dbuffer_len(buf)); buf = dbuffer_dup(buf); CU_ASSERT_EQUAL(0, dbuffer_len(buf)); for (i = 0; i <= dataSize; i++) dbuffer_add(buf, "b", 1); CU_ASSERT_EQUAL(dataSize + 1, dbuffer_len(buf)); CU_ASSERT_EQUAL(dataSize, dbuffer_extract(buf, data, dataSize)); CU_ASSERT_EQUAL(1, dbuffer_len(buf)); CU_ASSERT_EQUAL(1, dbuffer_remove(buf, 1)); CU_ASSERT_EQUAL(0, dbuffer_len(buf)); CU_ASSERT_EQUAL(0, dbuffer_remove(buf, 1)); CU_ASSERT_EQUAL(0, dbuffer_get_at(buf, 1, data, dataSize)); for (i = 0; i <= dataSize; i++) dbuffer_add(buf, "b", 1); CU_ASSERT_EQUAL(dataSize + 1, dbuffer_len(buf)); CU_ASSERT_EQUAL(dataSize, dbuffer_get_at(buf, 1, data, dataSize)); CU_ASSERT_EQUAL(dataSize + 1, dbuffer_len(buf)); }