QBLive - UDP server/client using ENet - Unseen Machine - 12-25-2025
In options in the ide for compiler bits add these or it wont work!
Compiler settings : -UDEPENDENCY_NO_SOCKETS
Compiler linkning : -lws2_32 -lwinmm -liphlpapi
Extract to your QB64 folder, run the server, start it up and then run the client in another window...
VOIP, 16 player online FPS games, etc! All now at your finger tips!
Unseen
RE: QBLive - UDP server/client using ENet - Petr - 12-25-2025
@Unseen Machine
Hey, I think I’ll give you your comment back. Windows is reporting a virus. Funny, it's like I just read about this happening somewhere else recently...
While you're dealing with TCP/IP: I'm not working on it at the moment, but I have a question. In LocalHost mode, have you considered using it for actual parallelization in QB64PE? The idea is to launch the same EXE multiple times according to the number of logical cores. The OS would handle the load balancing into CPUs core, and you could use TCP/IP for inter-process communication, even if it's somewhat limited. What’s your take on this? No need to code anything, just food for thought.
RE: QBLive - UDP server/client using ENet - Unseen Machine - 12-25-2025
Enet .h file
QBLive_UI.h
Code: (Select All)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <commctrl.h>
#include <process.h>
#include <cstdint>
#include <string>
#pragma comment(lib, "comctl32.lib")
#pragma comment(lib, "user32.lib")
// --- UI CONSTANTS ---
#define IDC_PORT_LABEL 1000
#define IDC_PORT_EDIT 1001
#define IDC_BTN_START 1002
#define IDC_BTN_STOP 1003
#define IDC_LIST_CLIENTS 1004
#define IDC_BTN_KICK 1005
#define IDC_CHK_REUSE 1006
#define IDC_STATUS_BAR 1007
// --- GLOBAL HANDLES ---
HWND hMainDlg = NULL;
HWND hClientList = NULL;
typedef void (__stdcall *UI_EVENT_CALLBACK)(int32_t action, const char* data);
UI_EVENT_CALLBACK g_EventCallback = nullptr;
// --- DYNAMIC DIALOG TEMPLATE HELPER ---
// This builds the window structure in memory (VB6 style layout)
void BuildServerUI(HWND hwnd) {
// Port Label and Input
CreateWindowEx(0, "STATIC", "Server Port:", WS_CHILD | WS_VISIBLE, 10, 15, 80, 20, hwnd, (HMENU)IDC_PORT_LABEL, NULL, NULL);
CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", "12347", WS_CHILD | WS_VISIBLE | ES_NUMBER, 90, 12, 60, 22, hwnd, (HMENU)IDC_PORT_EDIT, NULL, NULL);
// Options
CreateWindowEx(0, "BUTTON", "Auto-Reuse Port", WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX, 160, 15, 120, 20, hwnd, (HMENU)IDC_CHK_REUSE, NULL, NULL);
SendMessage(GetDlgItem(hwnd, IDC_CHK_REUSE), BM_SETCHECK, BST_CHECKED, 0);
// Action Buttons
CreateWindowEx(0, "BUTTON", "START SERVER", WS_CHILD | WS_VISIBLE, 10, 50, 130, 35, hwnd, (HMENU)IDC_BTN_START, NULL, NULL);
CreateWindowEx(0, "BUTTON", "STOP SERVER", WS_CHILD | WS_VISIBLE, 150, 50, 130, 35, hwnd, (HMENU)IDC_BTN_STOP, NULL, NULL);
// Client List
CreateWindowEx(0, "STATIC", "Active Clients:", WS_CHILD | WS_VISIBLE, 10, 100, 100, 20, hwnd, NULL, NULL, NULL);
hClientList = CreateWindowEx(WS_EX_CLIENTEDGE, "LISTBOX", NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL | LBS_NOTIFY, 10, 120, 270, 150, hwnd, (HMENU)IDC_LIST_CLIENTS, NULL, NULL);
// Management
CreateWindowEx(0, "BUTTON", "KICK SELECTED", WS_CHILD | WS_VISIBLE, 10, 280, 270, 30, hwnd, (HMENU)IDC_BTN_KICK, NULL, NULL);
// Status Bar
CreateWindowEx(0, "STATIC", "STATUS: IDLE", WS_CHILD | WS_VISIBLE | SS_SUNKEN, 0, 320, 300, 20, hwnd, (HMENU)IDC_STATUS_BAR, NULL, NULL);
}
// --- WINDOW PROCEDURE ---
LRESULT CALLBACK ServerWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch (msg) {
case WM_CREATE:
BuildServerUI(hwnd);
return 0;
case WM_COMMAND:
if (g_EventCallback) {
int wmId = LOWORD(wParam);
if (wmId == IDC_BTN_START) {
char buf[10];
GetWindowText(GetDlgItem(hwnd, IDC_PORT_EDIT), buf, 10);
SetWindowText(GetDlgItem(hwnd, IDC_STATUS_BAR), "STATUS: RUNNING");
g_EventCallback(1, buf); // Action 1: Start
}
if (wmId == IDC_BTN_STOP) {
SetWindowText(GetDlgItem(hwnd, IDC_STATUS_BAR), "STATUS: STOPPED");
g_EventCallback(0, ""); // Action 0: Stop
}
if (wmId == IDC_BTN_KICK) {
int sel = (int)SendMessage(hClientList, LB_GETCURSEL, 0, 0);
if (sel != LB_ERR) {
char name[32];
SendMessage(hClientList, LB_GETTEXT, sel, (LPARAM)name);
g_EventCallback(2, name); // Action 2: Kick
}
}
}
return 0;
case WM_CLOSE:
hMainDlg = NULL;
DestroyWindow(hwnd);
return 0;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
// --- BACKGROUND UI THREAD ---
void UI_Thread_Proc(void* param) {
WNDCLASS wc = { 0 };
wc.lpfnWndProc = ServerWndProc;
wc.hInstance = GetModuleHandle(NULL);
wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
wc.lpszClassName = "QBLive_Admin_UI";
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
RegisterClass(&wc);
hMainDlg = CreateWindowEx(WS_EX_TOPMOST, wc.lpszClassName, "QBLive S-Tier Admin Panel", WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT, 310, 380, NULL, NULL, wc.hInstance, NULL);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
// --- EXPORTED FUNCTIONS ---
extern "C" {
__declspec(dllexport) void UI_Show(uintptr_t callback) {
if (!hMainDlg) {
g_EventCallback = (UI_EVENT_CALLBACK)callback;
_beginthread(UI_Thread_Proc, 0, NULL);
}
}
__declspec(dllexport) void UI_UpdateList(const char* name, int action) {
if (!hClientList) return;
if (action == 1) SendMessage(hClientList, LB_ADDSTRING, 0, (LPARAM)name); // 1 = Add
else if (action == 0) { // 0 = Remove
int idx = (int)SendMessage(hClientList, LB_FINDSTRINGEXACT, -1, (LPARAM)name);
if (idx != LB_ERR) SendMessage(hClientList, LB_DELETESTRING, idx, 0);
}
}
}
QBLive_Server.h
Code: (Select All)
#ifndef QBLIVE_SERVER_H
#define QBLIVE_SERVER_H
#define ENET_IMPLEMENTATION
#include <winsock2.h>
#include <windows.h>
#include <commctrl.h>
#include <string>
#include <cstdio>
#include "enet.h"
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "winmm.lib")
// IDs for UI Controls
#define ID_BTN_START 101
#define ID_BTN_STOP 102
#define ID_BTN_KICK 104
#define IDM_EXIT 203
#define ID_STATE_TEXT 301
#define ID_EDIT_PORT 401
#define ID_LIST_CLIENTS 501
// Global Handles
static HWND g_hStateLabel = NULL;
static HWND g_hPortEdit = NULL;
static HWND g_hClientList = NULL;
static ENetHost* g_Server = NULL;
static int g_LastStartedPort = 7777;
static char g_LastPacketData[1024] = { 0 };
// Helper: Find the index of a client in the listbox by their ENetPeer pointer
static int FindClientIndex(ENetPeer* peer) {
int count = (int)SendMessage(g_hClientList, LB_GETCOUNT, 0, 0);
for (int i = 0; i < count; i++) {
if ((ENetPeer*)SendMessage(g_hClientList, LB_GETITEMDATA, i, 0) == peer) return i;
}
return -1;
}
// Helper: Get Public IP using Curl
std: tring GetGlobalIP_Clean() {
char buffer[128];
std: tring result = "";
FILE* pipe = _popen("curl -4 -s ifconfig.me", "r");
if (!pipe) return "0.0.0.0";
if (fgets(buffer, sizeof(buffer), pipe) != NULL) result = buffer;
_pclose(pipe);
if (result.length() < 7) return "127.0.0.1";
result.erase(result.find_last_not_of(" \n\r\t") + 1);
return result;
}
// Helper: Update the main status text
void RefreshStatusUI(const char* status, const char* ip, int port) {
if (!g_hStateLabel) return;
std: tring info = "Status: " + std: tring(status) +
"\nPublic IP: " + std: tring(ip) +
"\nPort: " + std::to_string(port) +
"\nClients: " + (g_Server ? std::to_string(g_Server->connectedPeers) : "0");
SetWindowText(g_hStateLabel, info.c_str());
}
// Main Window Logic
static LRESULT CALLBACK MyWinProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_COMMAND:
switch (LOWORD(wParam)) {
case ID_BTN_START:
if (!g_Server) {
char portBuf[10];
GetWindowText(g_hPortEdit, portBuf, 10);
int userPort = atoi(portBuf);
if (userPort <= 0) { userPort = 7777; SetWindowText(g_hPortEdit, "7777"); }
g_LastStartedPort = userPort;
if (enet_initialize() == 0) {
ENetAddress addr;
addr.host = ENET_HOST_ANY;
addr.port = (enet_uint16)userPort;
g_Server = enet_host_create(&addr, 32, 2, 0, 0);
if (g_Server) RefreshStatusUI("Running", GetGlobalIP_Clean().c_str(), userPort);
}
}
break;
case ID_BTN_STOP:
if (g_Server) {
for (size_t i = 0; i < g_Server->peerCount; ++i) {
if (g_Server->peers[i].state == ENET_PEER_STATE_CONNECTED) {
enet_peer_disconnect(&g_Server->peers[i], 500);
}
}
enet_host_flush(g_Server);
Sleep(100);
SendMessage(g_hClientList, LB_RESETCONTENT, 0, 0);
enet_host_destroy(g_Server);
g_Server = NULL;
enet_deinitialize();
RefreshStatusUI("Stopped", "---", 0);
}
break;
case ID_BTN_KICK: {
int sel = (int)SendMessage(g_hClientList, LB_GETCURSEL, 0, 0);
if (sel != LB_ERR) {
ENetPeer* peer = (ENetPeer*)SendMessage(g_hClientList, LB_GETITEMDATA, sel, 0);
if (peer) enet_peer_disconnect(peer, 403);
}
break;
}
case IDM_EXIT:
PostQuitMessage(0);
break;
}
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
extern "C" {
__declspec(dllexport) void InitServerUI() {
HINSTANCE hInst = GetModuleHandle(NULL);
WNDCLASS wc = { 0 };
wc.lpfnWndProc = MyWinProc; wc.hInstance = hInst; wc.lpszClassName = "QBLiveClass";
wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); wc.hCursor = LoadCursor(NULL, IDC_ARROW);
RegisterClass(&wc);
HWND main = CreateWindowEx(0, "QBLiveClass", "QBLive Server 2025",
WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 800, 600,
NULL, NULL, hInst, NULL);
CreateWindow("BUTTON", "Start", WS_VISIBLE | WS_CHILD, 20, 20, 80, 30, main, (HMENU)ID_BTN_START, hInst, NULL);
CreateWindow("BUTTON", "Stop", WS_VISIBLE | WS_CHILD, 110, 20, 80, 30, main, (HMENU)ID_BTN_STOP, hInst, NULL);
CreateWindow("BUTTON", "Kick Selected", WS_VISIBLE | WS_CHILD, 450, 20, 120, 30, main, (HMENU)ID_BTN_KICK, hInst, NULL);
CreateWindow("STATIC", "Port:", WS_VISIBLE | WS_CHILD, 210, 25, 40, 20, main, NULL, hInst, NULL);
g_hPortEdit = CreateWindow("EDIT", "7777", WS_VISIBLE | WS_CHILD | WS_BORDER | ES_NUMBER, 255, 22, 60, 25, main, (HMENU)ID_EDIT_PORT, hInst, NULL);
CreateWindow("STATIC", "Connected Clients:", WS_VISIBLE | WS_CHILD, 450, 60, 150, 20, main, NULL, hInst, NULL);
g_hClientList = CreateWindow("LISTBOX", NULL, WS_VISIBLE | WS_CHILD | WS_BORDER | WS_VSCROLL | LBS_NOTIFY, 450, 80, 300, 400, main, (HMENU)ID_LIST_CLIENTS, hInst, NULL);
g_hStateLabel = CreateWindow("STATIC", "Status: Ready", WS_VISIBLE | WS_CHILD, 20, 80, 400, 150, main, (HMENU)ID_STATE_TEXT, hInst, NULL);
}
__declspec(dllexport) void ProcessWindowEvents() {
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); }
if (g_Server) {
ENetEvent event;
while (enet_host_service(g_Server, &event, 0) > 0) {
char ip[64];
enet_address_get_host_ip(&event.peer->address, ip, 64);
switch (event.type) {
case ENET_EVENT_TYPE_CONNECT: {
std: tring entry = "IP: " + std: tring(ip);
int idx = (int)SendMessage(g_hClientList, LB_ADDSTRING, 0, (LPARAM)entry.c_str());
SendMessage(g_hClientList, LB_SETITEMDATA, idx, (LPARAM)event.peer);
RefreshStatusUI("Running", "Active", g_LastStartedPort);
break;
}
case ENET_EVENT_TYPE_DISCONNECT: {
int idx = FindClientIndex(event.peer);
if (idx != -1) SendMessage(g_hClientList, LB_DELETESTRING, idx, 0);
RefreshStatusUI("Running", "Active", g_LastStartedPort);
break;
}
case ENET_EVENT_TYPE_RECEIVE:
strncpy(g_LastPacketData, (char*)event.packet->data, 1024);
enet_packet_destroy(event.packet);
break;
default: break;
}
}
}
}
__declspec(dllexport) int UDP_Has_Message() { return (g_LastPacketData[0] != '\0'); }
__declspec(dllexport) void UDP_Get_Message(char* buffer) { strcpy(buffer, g_LastPacketData); g_LastPacketData[0] = '\0'; }
}
#endif
QBLive_Client.h
Code: (Select All)
#ifndef QBLIVE_CLIENT_H
#define QBLIVE_CLIENT_H
#define ENET_IMPLEMENTATION
#include <winsock2.h>
#include <windows.h>
#include <string>
#include "enet.h"
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "winmm.lib")
static ENetHost* g_ClientHost = NULL;
static ENetPeer* g_ServerPeer = NULL;
extern "C" {
__declspec(dllexport) int InitClient(const char* ip, int port) {
if (enet_initialize() != 0) return -1;
g_ClientHost = enet_host_create(NULL, 1, 2, 0, 0);
if (!g_ClientHost) return -2;
ENetAddress address;
enet_address_set_host(&address, ip);
address.port = (enet_uint16)port;
g_ServerPeer = enet_host_connect(g_ClientHost, &address, 2, 0);
return (g_ServerPeer) ? 0 : -3;
}
// UPDATED: Now returns 1=Connect, 2=Data, 3=Normal Disconnect, 403=Kicked, 500=Server Off
__declspec(dllexport) int UpdateClient() {
if (!g_ClientHost) return -1;
ENetEvent event;
while (enet_host_service(g_ClientHost, &event, 0) > 0) {
switch (event.type) {
case ENET_EVENT_TYPE_CONNECT: return 1;
case ENET_EVENT_TYPE_RECEIVE: enet_packet_destroy(event.packet); return 2;
case ENET_EVENT_TYPE_DISCONNECT:
// Check the data code the server sent us
if (event.data == 403) return 403;
if (event.data == 500) return 500;
return 3; // Generic disconnect
default: break;
}
}
return 0;
}
__declspec(dllexport) void ClientSendMessage(const char* msg) {
if (!g_ServerPeer) return;
ENetPacket* packet = enet_packet_create(msg, strlen(msg) + 1, ENET_PACKET_FLAG_RELIABLE);
enet_peer_send(g_ServerPeer, 0, packet);
enet_host_flush(g_ClientHost);
}
__declspec(dllexport) void ShutdownClient() {
if (g_ServerPeer) enet_peer_disconnect_now(g_ServerPeer, 0);
if (g_ClientHost) enet_host_destroy(g_ClientHost);
enet_deinitialize();
}
}
#endif
Qb64 Side Server
Code: (Select All)
DECLARE LIBRARY "QBLive_Server"
SUB InitServerUI ()
SUB ProcessWindowEvents ()
FUNCTION UDP_Has_Message& ()
SUB UDP_Get_Message (buffer AS STRING)
END DECLARE
InitServerUI
DO
ProcessWindowEvents
IF UDP_Has_Message THEN
DIM m AS STRING * 1024
UDP_Get_Message m
PRINT "Message: "; m
END IF
_LIMIT 60
LOOP UNTIL _EXIT
QB64 side Client
Code: (Select All)
' QB64 Client Application
DECLARE LIBRARY "QBLive_Client"
FUNCTION InitClient& (ip AS STRING, BYVAL port AS LONG)
FUNCTION UpdateClient& ()
SUB ClientSendMessage (msg AS STRING)
SUB ShutdownClient ()
END DECLARE
PRINT "Connecting to QBLive Server..."
res& = InitClient("127.0.0.1" + CHR$(0), 7777)
IF res& < 0 THEN
PRINT "Failed to initialize client! Error:"; res&
END
END IF
isConnected = 0
DO
status& = UpdateClient
SELECT CASE status&
CASE 1: PRINT "Connected!"
CASE 3: PRINT "Disconnected from server.": END
CASE 403: PRINT "YOU HAVE BEEN KICKED!": END
CASE 500: PRINT "SERVER SHUTDOWN!": END
END SELECT
_LIMIT 60
LOOP UNTIL _EXIT
ShutdownClient
SYSTEM
It'll be work but now hopefully you get no virus reports!
John
RE: QBLive - UDP server/client using ENet - Petr - 12-25-2025
@Unseen Machine
Writing at the speed of light, I see?
Sources contains many typos. Contains external call. _popen("curl ...")
What is this. QBLive_Server.h line 42: std:tring GetGlobalIP_Clean() { (better is std:: string...., no?)
line 44: std:tring result = "";
and then...
Buffering security flaw (actually a bug)
strncpy(..., 1024) does not guarantee a \0 at the end.
strcpy(buffer, g_LastPacketData) can overwrite memory if the caller passes a smaller buffer. This is a classic buffer overflow in an exported function.
But maybe this all is not your fail. C++ code insert to code marks. It works better. [ code ] and [ / code ] without spaces
RE: QBLive - UDP server/client using ENet - vince - 12-25-2025
is this like the industry standard methodology for online multiplayer games? pretty cool
i know UDP is industry standard for this sort of thing but, on the other hand, we have a multiplayer platformer and now a multiplayer top-down in QB64 using, as i understand it, TCP/IP
RE: QBLive - UDP server/client using ENet - Petr - 12-25-2025
OK, I checked the ZIP and the sources are all there. The program only works in a 64-bit environment, though. It seems really well done!
RE: QBLive - UDP server/client using ENet - Unseen Machine - 12-26-2025
Ill fix it over the next week or so and comeback with working iterations! I may have uploaded the wrong code or something though as i went through 5 or 6 iterations before this one.
I got no idea why its only 64 but though? Ideas?
Thanks for the feedback and yes, speed of light data transfers, dedicated channels for VOIP and as for TCP! If we get a solid rendition of this lib then it'll replace its use in your online gaming.
Unseen
|