Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
QBLive - UDP server/client using ENet
#1
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


Attached Files
.7z   Enet.7z (Size: 38.78 KB / Downloads: 17)
Reply
#2
@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...  Big Grin

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.


Reply
#3
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:Confusedtring GetGlobalIP_Clean() {
    char buffer[128];
    std:Confusedtring 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:Confusedtring info = "Status: " + std:Confusedtring(status) +
                      "\nPublic IP: " + std:Confusedtring(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:Confusedtring entry = "IP: " + std:Confusedtring(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
Reply
#4
@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


Reply
#5
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
Reply
#6
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!


Reply
#7
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
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)