Yeaaaa
This commit is contained in:
parent
bf07969ee9
commit
e4117b60ef
3
.vscode/c_cpp_properties.json
vendored
3
.vscode/c_cpp_properties.json
vendored
@ -4,8 +4,7 @@
|
||||
"name": "Win32",
|
||||
"includePath": [
|
||||
"${default}",
|
||||
"${workspaceFolder}/**",
|
||||
"C:/msys64/mingw64/include"
|
||||
"${workspaceFolder}/**"
|
||||
],
|
||||
"defines": [
|
||||
"_DEBUG",
|
||||
|
72
.vscode/settings.json
vendored
Normal file
72
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"*.pyx": "python",
|
||||
"*.js": "javascript",
|
||||
"*.c": "c",
|
||||
"array": "cpp",
|
||||
"atomic": "cpp",
|
||||
"bit": "cpp",
|
||||
"*.tcc": "cpp",
|
||||
"bitset": "cpp",
|
||||
"cctype": "cpp",
|
||||
"charconv": "cpp",
|
||||
"chrono": "cpp",
|
||||
"clocale": "cpp",
|
||||
"cmath": "cpp",
|
||||
"compare": "cpp",
|
||||
"concepts": "cpp",
|
||||
"condition_variable": "cpp",
|
||||
"cstdarg": "cpp",
|
||||
"cstddef": "cpp",
|
||||
"cstdint": "cpp",
|
||||
"cstdio": "cpp",
|
||||
"cstdlib": "cpp",
|
||||
"cstring": "cpp",
|
||||
"ctime": "cpp",
|
||||
"cwchar": "cpp",
|
||||
"cwctype": "cpp",
|
||||
"deque": "cpp",
|
||||
"forward_list": "cpp",
|
||||
"set": "cpp",
|
||||
"string": "cpp",
|
||||
"unordered_map": "cpp",
|
||||
"vector": "cpp",
|
||||
"exception": "cpp",
|
||||
"algorithm": "cpp",
|
||||
"functional": "cpp",
|
||||
"iterator": "cpp",
|
||||
"memory": "cpp",
|
||||
"memory_resource": "cpp",
|
||||
"netfwd": "cpp",
|
||||
"numeric": "cpp",
|
||||
"optional": "cpp",
|
||||
"random": "cpp",
|
||||
"ratio": "cpp",
|
||||
"string_view": "cpp",
|
||||
"system_error": "cpp",
|
||||
"tuple": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"utility": "cpp",
|
||||
"format": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"iomanip": "cpp",
|
||||
"iosfwd": "cpp",
|
||||
"iostream": "cpp",
|
||||
"istream": "cpp",
|
||||
"limits": "cpp",
|
||||
"mutex": "cpp",
|
||||
"new": "cpp",
|
||||
"numbers": "cpp",
|
||||
"ostream": "cpp",
|
||||
"semaphore": "cpp",
|
||||
"span": "cpp",
|
||||
"sstream": "cpp",
|
||||
"stdexcept": "cpp",
|
||||
"stop_token": "cpp",
|
||||
"streambuf": "cpp",
|
||||
"thread": "cpp",
|
||||
"cinttypes": "cpp",
|
||||
"typeinfo": "cpp",
|
||||
"variant": "cpp"
|
||||
}
|
||||
}
|
50
Makefile
50
Makefile
@ -1,39 +1,29 @@
|
||||
# Compiler
|
||||
CC = gcc
|
||||
CXX := g++
|
||||
CXXFLAGS := -std=c++20 -Wall -Wextra -g -Iinclude -I/c/msys64/mingw64/include
|
||||
|
||||
# Paths
|
||||
SRC_DIR = src
|
||||
BUILD_DIR = build
|
||||
OUT = main.exe
|
||||
LDFLAGS := -lws2_32
|
||||
|
||||
# Source files
|
||||
SRC = \
|
||||
$(SRC_DIR)\main.c \
|
||||
$(SRC_DIR)\net.c \
|
||||
$(SRC_DIR)\event.c \
|
||||
$(SRC_DIR)\player.c
|
||||
SRC_DIRS := src
|
||||
BUILD_DIR := build
|
||||
|
||||
# Object files
|
||||
OBJ = $(patsubst $(SRC_DIR)\%.c, $(BUILD_DIR)\%.o, $(SRC))
|
||||
# Find all source files
|
||||
SRC := $(foreach dir, $(SRC_DIRS), $(wildcard $(dir)/*.cpp))
|
||||
|
||||
# Include and linker flags
|
||||
INCLUDE_FLAGS = -IC:/msys64/mingw64/include
|
||||
LIBS = -LC:/msys64/mingw64/lib -static -lws2_32 -lSDL2main -lSDL2 -lwinmm -limm32 -lole32 -loleaut32 -lversion -lsetupapi -lgdi32 -lshell32 -luser32 -mconsole
|
||||
CFLAGS = -Wall -Wextra -g $(INCLUDE_FLAGS)
|
||||
LDFLAGS = $(LIBS)
|
||||
OBJ := $(patsubst %.cpp, $(BUILD_DIR)/%.o, $(SRC))
|
||||
|
||||
# Default target
|
||||
all: $(OUT)
|
||||
TARGET := main.exe
|
||||
|
||||
$(OUT): $(OBJ)
|
||||
$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)
|
||||
all: $(TARGET)
|
||||
|
||||
# Compile each .c into .o
|
||||
$(BUILD_DIR)\%.o: $(SRC_DIR)\%.c
|
||||
@if not exist $(BUILD_DIR) mkdir $(BUILD_DIR)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
$(TARGET): $(OBJ)
|
||||
$(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS)
|
||||
|
||||
$(BUILD_DIR)/%.o: %.cpp
|
||||
@mkdir "$(dir $@)" || exit 0
|
||||
$(CXX) $(CXXFLAGS) -c $< -o $@
|
||||
|
||||
# Clean
|
||||
clean:
|
||||
del /Q $(BUILD_DIR)\*.o 2>nul || exit 0
|
||||
del /Q $(OUT) 2>nul || exit 0
|
||||
del /Q /S $(subst /,\,$(BUILD_DIR)\*.o) $(TARGET) 2>nul || exit 0
|
||||
rmdir /S /Q $(BUILD_DIR) 2>nul || exit 0
|
||||
|
||||
.PHONY: all clean
|
BIN
build/src/Client.o
Normal file
BIN
build/src/Client.o
Normal file
Binary file not shown.
BIN
build/src/Packet.o
Normal file
BIN
build/src/Packet.o
Normal file
Binary file not shown.
BIN
build/src/Server.o
Normal file
BIN
build/src/Server.o
Normal file
Binary file not shown.
BIN
build/src/main.o
Normal file
BIN
build/src/main.o
Normal file
Binary file not shown.
9
include/Client.h
Normal file
9
include/Client.h
Normal file
@ -0,0 +1,9 @@
|
||||
#ifndef CLIENT_H
|
||||
#define CLIENT_H
|
||||
|
||||
#include <string>
|
||||
|
||||
// Runs the client (with OpenGL rendering and network handling).
|
||||
void runClient(const std::string& host, const std::string& portStr);
|
||||
|
||||
#endif // CLIENT_H
|
40
include/Packet.h
Normal file
40
include/Packet.h
Normal file
@ -0,0 +1,40 @@
|
||||
#ifndef PACKET_H
|
||||
#define PACKET_H
|
||||
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
|
||||
enum PacketType : uint8_t {
|
||||
HEARTBEAT = 1,
|
||||
JOIN,
|
||||
DISCONNECT,
|
||||
MOVE,
|
||||
CHAT,
|
||||
PLAYER_UPDATE,
|
||||
SCORE_UPDATE,
|
||||
GAME_START,
|
||||
GAME_END,
|
||||
PING,
|
||||
PONG,
|
||||
AUTH_REQUEST,
|
||||
AUTH_RESPONSE,
|
||||
PACKET_MAX
|
||||
};
|
||||
|
||||
struct Packet
|
||||
{
|
||||
PacketType type;
|
||||
std::string data;
|
||||
};
|
||||
|
||||
uint32_t to_network_uint32(uint32_t host);
|
||||
uint32_t from_network_uint32(uint32_t net);
|
||||
bool send_all(SOCKET sock, const char *data, size_t len);
|
||||
bool recv_all(SOCKET sock, char *buffer, size_t len);
|
||||
bool sendPacket(SOCKET sock, const Packet &packet);
|
||||
bool receivePacket(SOCKET sock, Packet &packet);
|
||||
|
||||
#endif // PACKET_H
|
23
include/Server.h
Normal file
23
include/Server.h
Normal file
@ -0,0 +1,23 @@
|
||||
#ifndef SERVER_H
|
||||
#define SERVER_H
|
||||
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include "Packet.h"
|
||||
|
||||
class Server {
|
||||
public:
|
||||
Server(unsigned short port);
|
||||
~Server();
|
||||
void start();
|
||||
void broadcast(const Packet &packet);
|
||||
private:
|
||||
void handleClient(SOCKET clientSock);
|
||||
SOCKET listenSock;
|
||||
std::vector<SOCKET> clients;
|
||||
std::mutex mutex_;
|
||||
};
|
||||
|
||||
#endif // SERVER_H
|
18
include/Utils.h
Normal file
18
include/Utils.h
Normal file
@ -0,0 +1,18 @@
|
||||
#ifndef UTILS_H
|
||||
#define UTILS_H
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
|
||||
// Returns the current timestamp in milliseconds since the epoch.
|
||||
inline std::string getCurrentTimestamp() {
|
||||
using namespace std::chrono;
|
||||
auto now = system_clock::now();
|
||||
auto ms = duration_cast<milliseconds>(now.time_since_epoch()).count();
|
||||
std::ostringstream oss;
|
||||
oss << ms;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
#endif // UTILS_H
|
98
src/Client.cpp
Normal file
98
src/Client.cpp
Normal file
@ -0,0 +1,98 @@
|
||||
// Client.cpp
|
||||
#include "Client.h"
|
||||
#include "Packet.h"
|
||||
#include "Utils.h"
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <string>
|
||||
|
||||
void sendAuthPacket(SOCKET sock, const std::string &username, const std::string &password) {
|
||||
Packet authPacket;
|
||||
authPacket.type = AUTH_REQUEST; // Ensure AUTH_REQUEST is defined in your PacketType enum.
|
||||
authPacket.data = username + ":" + password;
|
||||
if(!sendPacket(sock, authPacket)) {
|
||||
std::cerr << "Failed to send auth packet." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void runClient(const std::string& host, const std::string& portStr) {
|
||||
int port = std::stoi(portStr);
|
||||
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if(sock == INVALID_SOCKET) {
|
||||
std::cerr << "Failed to create socket." << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
sockaddr_in serverAddr;
|
||||
serverAddr.sin_family = AF_INET;
|
||||
inet_pton(AF_INET, host.c_str(), &serverAddr.sin_addr);
|
||||
serverAddr.sin_port = htons(port);
|
||||
|
||||
if(connect(sock, reinterpret_cast<sockaddr*>(&serverAddr), sizeof(serverAddr)) == SOCKET_ERROR) {
|
||||
std::cerr << "Connect failed." << std::endl;
|
||||
closesocket(sock);
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "Connected to server." << std::endl;
|
||||
|
||||
// Prompt for authentication details.
|
||||
std::string username, password;
|
||||
std::cout << "Enter username: ";
|
||||
std::getline(std::cin, username);
|
||||
std::cout << "Enter password: ";
|
||||
std::getline(std::cin, password);
|
||||
|
||||
// Send authentication packet.
|
||||
sendAuthPacket(sock, username, password);
|
||||
|
||||
Packet joinPacket;
|
||||
joinPacket.type = JOIN;
|
||||
joinPacket.data = "I have joined.";
|
||||
if(!sendPacket(sock, joinPacket)) {
|
||||
std::cerr << "Failed to send join packet." << std::endl;
|
||||
closesocket(sock);
|
||||
return;
|
||||
}
|
||||
|
||||
// Main loop to receive packets from the server.
|
||||
Packet packet;
|
||||
while(receivePacket(sock, packet)) {
|
||||
switch(packet.type) {
|
||||
case HEARTBEAT: {
|
||||
try {
|
||||
long long serverTimestamp = std::stoll(packet.data);
|
||||
using namespace std::chrono;
|
||||
auto now = system_clock::now();
|
||||
auto msNow = duration_cast<milliseconds>(now.time_since_epoch()).count();
|
||||
long long ping = msNow - serverTimestamp;
|
||||
std::cout << "Heartbeat: " << ping << " ms" << std::endl;
|
||||
} catch(...) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case JOIN:
|
||||
std::cout << "Connect: " << packet.data << std::endl;
|
||||
break;
|
||||
case DISCONNECT:
|
||||
std::cout << "Disconnect: " << packet.data << std::endl;
|
||||
break;
|
||||
case AUTH_RESPONSE:
|
||||
std::cout << "Auth Response: " << packet.data << std::endl;
|
||||
break;
|
||||
default:
|
||||
std::cout << "Unknown packet type: " << static_cast<int>(packet.type)
|
||||
<< " | Data: " << packet.data << std::endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "Disconnected from server." << std::endl;
|
||||
closesocket(sock);
|
||||
}
|
||||
|
||||
|
||||
|
72
src/Packet.cpp
Normal file
72
src/Packet.cpp
Normal file
@ -0,0 +1,72 @@
|
||||
#include "Packet.h"
|
||||
#include <cstdlib>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
|
||||
uint32_t to_network_uint32(uint32_t host) {
|
||||
return htonl(host);
|
||||
}
|
||||
|
||||
uint32_t from_network_uint32(uint32_t net) {
|
||||
return ntohl(net);
|
||||
}
|
||||
|
||||
bool send_all(SOCKET sock, const char* data, size_t len) {
|
||||
size_t totalSent = 0;
|
||||
while(totalSent < len) {
|
||||
int sent = send(sock, data + totalSent, static_cast<int>(len - totalSent), 0);
|
||||
if(sent <= 0)
|
||||
return false;
|
||||
totalSent += sent;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool recv_all(SOCKET sock, char* buffer, size_t len) {
|
||||
size_t totalRecv = 0;
|
||||
while(totalRecv < len) {
|
||||
int recvd = recv(sock, buffer + totalRecv, static_cast<int>(len - totalRecv), 0);
|
||||
if(recvd <= 0)
|
||||
return false;
|
||||
totalRecv += recvd;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool sendPacket(SOCKET sock, const Packet& packet) {
|
||||
// Payload length = 1 byte for type + data length.
|
||||
uint32_t payloadLen = 1 + static_cast<uint32_t>(packet.data.size());
|
||||
uint32_t netLen = to_network_uint32(payloadLen);
|
||||
if(!send_all(sock, reinterpret_cast<const char*>(&netLen), sizeof(netLen)))
|
||||
return false;
|
||||
if(!send_all(sock, reinterpret_cast<const char*>(&packet.type), sizeof(packet.type)))
|
||||
return false;
|
||||
if(!packet.data.empty()) {
|
||||
if(!send_all(sock, packet.data.data(), packet.data.size()))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool receivePacket(SOCKET sock, Packet &packet) {
|
||||
uint32_t netLen = 0;
|
||||
if(!recv_all(sock, reinterpret_cast<char*>(&netLen), sizeof(netLen)))
|
||||
return false;
|
||||
uint32_t len = from_network_uint32(netLen);
|
||||
if(len < 1)
|
||||
return false;
|
||||
char typeChar;
|
||||
if(!recv_all(sock, &typeChar, 1))
|
||||
return false;
|
||||
packet.type = static_cast<PacketType>(typeChar);
|
||||
size_t dataLen = len - 1;
|
||||
if(dataLen > 0) {
|
||||
std::vector<char> buf(dataLen);
|
||||
if(!recv_all(sock, buf.data(), dataLen))
|
||||
return false;
|
||||
packet.data = std::string(buf.begin(), buf.end());
|
||||
} else {
|
||||
packet.data.clear();
|
||||
}
|
||||
return true;
|
||||
}
|
151
src/Server.cpp
Normal file
151
src/Server.cpp
Normal file
@ -0,0 +1,151 @@
|
||||
#include "Server.h"
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
|
||||
Server::Server(unsigned short port) {
|
||||
listenSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if(listenSock == INVALID_SOCKET) {
|
||||
std::cerr << "Failed to create socket." << std::endl;
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
sockaddr_in serverAddr;
|
||||
serverAddr.sin_family = AF_INET;
|
||||
serverAddr.sin_addr.s_addr = INADDR_ANY;
|
||||
serverAddr.sin_port = htons(port);
|
||||
if(bind(listenSock, reinterpret_cast<sockaddr*>(&serverAddr), sizeof(serverAddr)) == SOCKET_ERROR) {
|
||||
std::cerr << "Bind failed." << std::endl;
|
||||
closesocket(listenSock);
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
if(listen(listenSock, SOMAXCONN) == SOCKET_ERROR) {
|
||||
std::cerr << "Listen failed." << std::endl;
|
||||
closesocket(listenSock);
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
Server::~Server() {
|
||||
closesocket(listenSock);
|
||||
}
|
||||
|
||||
void Server::start() {
|
||||
std::thread([this](){
|
||||
while(true) {
|
||||
SOCKET clientSock = accept(listenSock, nullptr, nullptr);
|
||||
if(clientSock == INVALID_SOCKET) {
|
||||
std::cerr << "Accept failed." << std::endl;
|
||||
continue;
|
||||
}
|
||||
std::cout << "Client connected." << std::endl;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
clients.push_back(clientSock);
|
||||
}
|
||||
// Broadcast join packet.
|
||||
Packet joinPacket;
|
||||
joinPacket.type = JOIN;
|
||||
joinPacket.data = "Client Connected";
|
||||
broadcast(joinPacket);
|
||||
// Spawn a thread to handle the new client.
|
||||
std::thread(&Server::handleClient, this, clientSock).detach();
|
||||
}
|
||||
}).detach();
|
||||
}
|
||||
|
||||
void Server::broadcast(const Packet &packet) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
for(auto it = clients.begin(); it != clients.end();) {
|
||||
if(!sendPacket(*it, packet)) {
|
||||
// Remove client if send fails.
|
||||
closesocket(*it);
|
||||
it = clients.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Server::handleClient(SOCKET clientSock) {
|
||||
bool authenticated = false;
|
||||
std::string sessionToken;
|
||||
Packet packet;
|
||||
|
||||
while(receivePacket(clientSock, packet)) {
|
||||
if(packet.type == AUTH_REQUEST) {
|
||||
std::string creds = packet.data;
|
||||
size_t delim = creds.find(":");
|
||||
Packet authResp;
|
||||
authResp.type = AUTH_RESPONSE;
|
||||
if(delim != std::string::npos) {
|
||||
std::string username = creds.substr(0, delim);
|
||||
std::string password = creds.substr(delim + 1);
|
||||
|
||||
|
||||
if(username == "testuser" && password == "pass123") {
|
||||
|
||||
authenticated = true;
|
||||
sessionToken = "SESSION_TOKEN_ABC123";
|
||||
authResp.data = sessionToken;
|
||||
std::cout << "Client authenticated: " << username << std::endl;
|
||||
|
||||
} else {
|
||||
authResp.data = "AUTH_FAILED";
|
||||
std::cout << "Client failed to authenticate: " << username << std::endl;
|
||||
}
|
||||
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
authResp.data = "AUTH_FAILED";
|
||||
std::cerr << "Invalid authentication packet format." << std::endl;
|
||||
|
||||
}
|
||||
sendPacket(clientSock, authResp);
|
||||
}
|
||||
else if(!authenticated) {
|
||||
|
||||
|
||||
Packet notAuth;
|
||||
notAuth.type = AUTH_RESPONSE;
|
||||
notAuth.data = "NOT_AUTHENTICATED";
|
||||
sendPacket(clientSock, notAuth);
|
||||
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
switch(packet.type) {
|
||||
case MOVE:
|
||||
broadcast(packet);
|
||||
break;
|
||||
case HEARTBEAT:
|
||||
break;
|
||||
case JOIN:
|
||||
std::cout << "Join packet received: " << packet.data << std::endl;
|
||||
break;
|
||||
case DISCONNECT:
|
||||
std::cout << "Disconnect packet received: " << packet.data << std::endl;
|
||||
break;
|
||||
default:
|
||||
std::cout << "Unknown packet type: " << static_cast<int>(packet.type)
|
||||
<< " | Data: " << packet.data << std::endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::cout << "Client disconnected." << std::endl;
|
||||
Packet disconnectPacket;
|
||||
disconnectPacket.type = DISCONNECT;
|
||||
disconnectPacket.data = "A client disconnected.";
|
||||
broadcast(disconnectPacket);
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
auto it = std::find(clients.begin(), clients.end(), clientSock);
|
||||
if(it != clients.end()) {
|
||||
closesocket(*it);
|
||||
clients.erase(it);
|
||||
}
|
||||
}
|
14
src/event.c
14
src/event.c
@ -1,14 +0,0 @@
|
||||
#include "event.h"
|
||||
|
||||
#define MAX_HANDLERS 256
|
||||
static PacketHandler g_handlers[MAX_HANDLERS] = {0};
|
||||
|
||||
void register_handler(uint8_t type, PacketHandler handler) {
|
||||
g_handlers[type] = handler;
|
||||
}
|
||||
|
||||
void handle_packet(Packet* pkt, SOCKET client) {
|
||||
if (g_handlers[pkt->type]) {
|
||||
g_handlers[pkt->type](pkt, client);
|
||||
}
|
||||
}
|
15
src/event.h
15
src/event.h
@ -1,15 +0,0 @@
|
||||
#ifndef EVENT_H
|
||||
#define EVENT_H
|
||||
|
||||
#include "net.h"
|
||||
|
||||
// Each handler receives Packet* and the client socket
|
||||
typedef void (*PacketHandler)(Packet*, SOCKET client);
|
||||
|
||||
// Register a handler for a certain PacketType
|
||||
void register_handler(uint8_t type, PacketHandler handler);
|
||||
|
||||
// Dispatch a packet
|
||||
void handle_packet(Packet* pkt, SOCKET client);
|
||||
|
||||
#endif
|
336
src/main.c
336
src/main.c
@ -1,336 +0,0 @@
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define SDL_MAIN_HANDLED
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <windows.h> // Sleep()
|
||||
#include "net.h"
|
||||
#include "event.h"
|
||||
#include "player.h"
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
CRITICAL_SECTION g_playerLock;
|
||||
static Player g_players[MAX_PLAYERS];
|
||||
static int g_numPlayers = 0;
|
||||
static SOCKET g_clients[MAX_CLIENTS];
|
||||
static int g_clientCount = 0;
|
||||
|
||||
static uint32_t g_nextID = 1; // to assign unique IDs
|
||||
|
||||
// Forward declarations
|
||||
DWORD WINAPI client_thread(LPVOID param);
|
||||
void broadcast_packet(Packet* pkt, SOCKET except);
|
||||
|
||||
|
||||
void on_player_move(Packet* pkt, SOCKET client) {
|
||||
PlayerMove pm;
|
||||
memcpy(&pm, pkt->data, sizeof(PlayerMove));
|
||||
|
||||
EnterCriticalSection(&g_playerLock);
|
||||
for(int i = 0; i < g_numPlayers; i++) {
|
||||
if(g_players[i].id == pm.id) {
|
||||
g_players[i].x += pm.dx;
|
||||
g_players[i].y += pm.dy;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// broadcast updated positions
|
||||
int numStates = g_numPlayers;
|
||||
int dataSize = sizeof(PlayerState) * numStates;
|
||||
PlayerState* states = malloc(dataSize);
|
||||
for(int i = 0; i < g_numPlayers; i++) {
|
||||
states[i].id = g_players[i].id;
|
||||
states[i].x = g_players[i].x;
|
||||
states[i].y = g_players[i].y;
|
||||
}
|
||||
Packet* statePkt = create_packet(PACKET_PLAYER_STATE, states, dataSize);
|
||||
free(states);
|
||||
broadcast_packet(statePkt, INVALID_SOCKET);
|
||||
destroy_packet(statePkt);
|
||||
|
||||
LeaveCriticalSection(&g_playerLock);
|
||||
}
|
||||
|
||||
|
||||
void broadcast_packet(Packet* pkt, SOCKET except) {
|
||||
// serialize
|
||||
char buffer[BUFFER_SIZE];
|
||||
int len = serialize_packet(pkt, buffer);
|
||||
|
||||
EnterCriticalSection(&g_playerLock);
|
||||
for(int i = 0; i < g_clientCount; i++) {
|
||||
if(g_clients[i] == except) continue;
|
||||
send(g_clients[i], buffer, len, 0);
|
||||
}
|
||||
LeaveCriticalSection(&g_playerLock);
|
||||
}
|
||||
|
||||
DWORD WINAPI client_thread(LPVOID param) {
|
||||
SOCKET client = *(SOCKET*)param;
|
||||
free(param);
|
||||
|
||||
// Non-blocking for easier single-thread dispatch or we do blocking in a thread
|
||||
set_socket_nonblocking(client, 0); // blocking in this thread
|
||||
|
||||
// Assign a new player
|
||||
Player newPlayer;
|
||||
newPlayer.id = g_nextID++;
|
||||
newPlayer.x = 100; // start at 100,100
|
||||
newPlayer.y = 100;
|
||||
|
||||
EnterCriticalSection(&g_playerLock);
|
||||
g_players[g_numPlayers++] = newPlayer;
|
||||
g_clients[g_clientCount++] = client;
|
||||
LeaveCriticalSection(&g_playerLock);
|
||||
|
||||
printf("[SERVER] Client %u connected.\n", newPlayer.id);
|
||||
|
||||
// Now run receive loop
|
||||
char buffer[BUFFER_SIZE];
|
||||
while(1) {
|
||||
int received = recv(client, buffer, BUFFER_SIZE, 0);
|
||||
if (received <= 0) {
|
||||
break; // client disconnected
|
||||
}
|
||||
Packet* pkt = deserialize_packet(buffer, received);
|
||||
if (pkt) {
|
||||
// Dispatch
|
||||
handle_packet(pkt, client);
|
||||
destroy_packet(pkt);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from global state
|
||||
EnterCriticalSection(&g_playerLock);
|
||||
// remove from g_clients
|
||||
for(int i = 0; i < g_clientCount; i++) {
|
||||
if(g_clients[i] == client) {
|
||||
g_clients[i] = g_clients[--g_clientCount];
|
||||
break;
|
||||
}
|
||||
}
|
||||
// remove from g_players
|
||||
for(int i = 0; i < g_numPlayers; i++) {
|
||||
if(g_players[i].id == newPlayer.id) {
|
||||
g_players[i] = g_players[--g_numPlayers];
|
||||
break;
|
||||
}
|
||||
}
|
||||
LeaveCriticalSection(&g_playerLock);
|
||||
|
||||
closesocket(client);
|
||||
printf("[SERVER] Client %u disconnected.\n", newPlayer.id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void run_server(int port) {
|
||||
net_init();
|
||||
InitializeCriticalSection(&g_playerLock);
|
||||
|
||||
// Register server handlers
|
||||
register_handler(PACKET_PLAYER_MOVE, on_player_move);
|
||||
|
||||
SOCKET server = net_bind_and_listen(port);
|
||||
if (server == INVALID_SOCKET) {
|
||||
printf("Failed to bind on port %d\n", port);
|
||||
return;
|
||||
}
|
||||
printf("[SERVER] Listening on port %d...\n", port);
|
||||
|
||||
while(1) {
|
||||
SOCKET s = net_accept(server);
|
||||
if (s == INVALID_SOCKET) {
|
||||
Sleep(10);
|
||||
continue;
|
||||
}
|
||||
// Start a client thread
|
||||
SOCKET* ptr = malloc(sizeof(SOCKET));
|
||||
*ptr = s;
|
||||
CreateThread(NULL, 0, client_thread, ptr, 0, NULL);
|
||||
}
|
||||
|
||||
DeleteCriticalSection(&g_playerLock);
|
||||
closesocket(server);
|
||||
net_cleanup();
|
||||
}
|
||||
|
||||
|
||||
static Player g_localPlayer = {0};
|
||||
static PlayerState g_otherPlayers[MAX_PLAYERS];
|
||||
static int g_otherCount = 0;
|
||||
|
||||
// For now, store the socket globally
|
||||
static SOCKET g_sock = INVALID_SOCKET;
|
||||
|
||||
// Handler: PACKET_PLAYER_STATE
|
||||
void on_player_state(Packet* pkt, SOCKET _) {
|
||||
// We'll parse an array of PlayerStates
|
||||
int num = pkt->size / sizeof(PlayerState);
|
||||
if(num > MAX_PLAYERS) num = MAX_PLAYERS;
|
||||
|
||||
memcpy(g_otherPlayers, pkt->data, pkt->size);
|
||||
g_otherCount = num;
|
||||
}
|
||||
|
||||
void run_client(const char* ip, int port) {
|
||||
net_init();
|
||||
|
||||
// Register client handlers
|
||||
register_handler(PACKET_PLAYER_STATE, on_player_state);
|
||||
|
||||
// Connect
|
||||
g_sock = net_connect(ip, port);
|
||||
if (g_sock == INVALID_SOCKET) {
|
||||
printf("[CLIENT] Could not connect to server.\n");
|
||||
return;
|
||||
}
|
||||
printf("[CLIENT] Connected to server %s:%d\n", ip, port);
|
||||
|
||||
// We'll assign an ID after reading from server in a real scenario,
|
||||
// but for simplicity, let's guess the server will do so.
|
||||
// We'll just store local ID = 999, or we wait for some handshake.
|
||||
// For now, let's store a random number:
|
||||
g_localPlayer.id = 9999;
|
||||
g_localPlayer.x = 200;
|
||||
g_localPlayer.y = 200;
|
||||
|
||||
// Setup SDL
|
||||
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
|
||||
printf("SDL_Init failed: %s\n", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_Window* window = SDL_CreateWindow(
|
||||
"MPGC Client",
|
||||
SDL_WINDOWPOS_CENTERED,
|
||||
SDL_WINDOWPOS_CENTERED,
|
||||
800, 600,
|
||||
0
|
||||
);
|
||||
if(!window) {
|
||||
printf("SDL_CreateWindow failed: %s\n", SDL_GetError());
|
||||
SDL_Quit();
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0);
|
||||
if(!renderer) {
|
||||
printf("SDL_CreateRenderer failed: %s\n", SDL_GetError());
|
||||
SDL_DestroyWindow(window);
|
||||
SDL_Quit();
|
||||
return;
|
||||
}
|
||||
|
||||
// Non-blocking so we can poll in main loop
|
||||
set_socket_nonblocking(g_sock, 1);
|
||||
|
||||
int running = 1;
|
||||
while (running) {
|
||||
// ---- Handle SDL events
|
||||
SDL_Event e;
|
||||
while(SDL_PollEvent(&e)) {
|
||||
if(e.type == SDL_QUIT) {
|
||||
running = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Keyboard input (WASD)
|
||||
const Uint8* state = SDL_GetKeyboardState(NULL);
|
||||
float dx = 0.f;
|
||||
float dy = 0.f;
|
||||
if(state[SDL_SCANCODE_W]) { dy -= 2.f; }
|
||||
if(state[SDL_SCANCODE_S]) { dy += 2.f; }
|
||||
if(state[SDL_SCANCODE_A]) { dx -= 2.f; }
|
||||
if(state[SDL_SCANCODE_D]) { dx += 2.f; }
|
||||
|
||||
if(dx != 0 || dy != 0) {
|
||||
// Move local player
|
||||
g_localPlayer.x += dx;
|
||||
g_localPlayer.y += dy;
|
||||
|
||||
// Send PACKET_PLAYER_MOVE
|
||||
PlayerMove pm;
|
||||
pm.id = g_localPlayer.id;
|
||||
pm.dx = dx;
|
||||
pm.dy = dy;
|
||||
|
||||
Packet* movePkt = create_packet(PACKET_PLAYER_MOVE, &pm, sizeof(pm));
|
||||
char buffer[BUFFER_SIZE];
|
||||
int len = serialize_packet(movePkt, buffer);
|
||||
send(g_sock, buffer, len, 0);
|
||||
destroy_packet(movePkt);
|
||||
}
|
||||
|
||||
// ---- Check for incoming data
|
||||
while (1) {
|
||||
char netbuf[BUFFER_SIZE];
|
||||
int r = recv(g_sock, netbuf, BUFFER_SIZE, 0);
|
||||
if (r <= 0) break; // no data or error
|
||||
Packet* pkt = deserialize_packet(netbuf, r);
|
||||
if(pkt) {
|
||||
handle_packet(pkt, g_sock);
|
||||
destroy_packet(pkt);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Render scene
|
||||
SDL_SetRenderDrawColor(renderer, 20, 20, 20, 255);
|
||||
SDL_RenderClear(renderer);
|
||||
|
||||
// Draw local player
|
||||
SDL_Rect rLoc = {
|
||||
(int)g_localPlayer.x,
|
||||
(int)g_localPlayer.y,
|
||||
30, 30
|
||||
};
|
||||
SDL_SetRenderDrawColor(renderer, 200, 200, 50, 255);
|
||||
SDL_RenderFillRect(renderer, &rLoc);
|
||||
|
||||
// Draw other players
|
||||
SDL_SetRenderDrawColor(renderer, 100, 200, 200, 255);
|
||||
for(int i = 0; i < g_otherCount; i++) {
|
||||
SDL_Rect rOth = {
|
||||
(int)g_otherPlayers[i].x,
|
||||
(int)g_otherPlayers[i].y,
|
||||
30, 30
|
||||
};
|
||||
SDL_RenderFillRect(renderer, &rOth);
|
||||
}
|
||||
SDL_RenderPresent(renderer);
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
SDL_DestroyRenderer(renderer);
|
||||
SDL_DestroyWindow(window);
|
||||
SDL_Quit();
|
||||
|
||||
closesocket(g_sock);
|
||||
net_cleanup();
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
printf("[INFO] Starting...\n");
|
||||
SDL_SetMainReady();
|
||||
|
||||
if (argc < 3) {
|
||||
printf("Usage:\n %s server <port>\n %s client <ip> <port>\n", argv[0], argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
if(strcmp(argv[1], "server") == 0) {
|
||||
int port = atoi(argv[2]);
|
||||
run_server(port);
|
||||
printf("[SERVER] Listening on port %d\n", port);
|
||||
} else if(strcmp(argv[1], "client") == 0 && argc >= 4) {
|
||||
const char* ip = argv[2];
|
||||
int port = atoi(argv[3]);
|
||||
run_client(ip, port);
|
||||
} else {
|
||||
printf("Invalid arguments.\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
72
src/main.cpp
Normal file
72
src/main.cpp
Normal file
@ -0,0 +1,72 @@
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include "Server.h"
|
||||
#include "Client.h"
|
||||
#include "Utils.h"
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
std::cout << "[Server] Starting Server on port:" << argv[2] << "" << std::endl;
|
||||
|
||||
WSADATA wsaData;
|
||||
if(WSAStartup(MAKEWORD(2,2), &wsaData) != 0) {
|
||||
std::cerr << "WSAStartup failed." << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if(argc < 3) {
|
||||
std::cout << "Usage:\n Server: " << argv[0] << " server <port>\n Client: " << argv[0] << " client <IP>:<port>\n";
|
||||
WSACleanup();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
std::string mode = argv[1];
|
||||
if(mode == "server") {
|
||||
unsigned short port = static_cast<unsigned short>(std::stoi(argv[2]));
|
||||
Server server(port);
|
||||
server.start();
|
||||
// Heartbeat thread: send a heartbeat with current timestamp every second.
|
||||
std::thread heartbeat([&server](){
|
||||
while(true) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
Packet heartbeat;
|
||||
heartbeat.type = HEARTBEAT;
|
||||
heartbeat.data = getCurrentTimestamp();
|
||||
server.broadcast(heartbeat);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
while(true) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
}
|
||||
heartbeat.join();
|
||||
} else if(mode == "client") {
|
||||
|
||||
std::string ip_port = argv[2];
|
||||
size_t colonPos = ip_port.find(':');
|
||||
if(colonPos == std::string::npos) {
|
||||
std::cerr << "Invalid client format. Use IP:port" << std::endl;
|
||||
WSACleanup();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
std::string ip = ip_port.substr(0, colonPos);
|
||||
std::string port = ip_port.substr(colonPos + 1);
|
||||
runClient(ip, port);
|
||||
|
||||
} else {
|
||||
|
||||
std::cerr << "Unknown mode: " << mode << std::endl;
|
||||
WSACleanup();
|
||||
return EXIT_FAILURE;
|
||||
|
||||
}
|
||||
|
||||
WSACleanup();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
}
|
78
src/net.c
78
src/net.c
@ -1,78 +0,0 @@
|
||||
#include "net.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
|
||||
|
||||
Packet* create_packet(PacketType type, const void* data, uint16_t size) {
|
||||
Packet* pkt = malloc(sizeof(Packet) + size);
|
||||
pkt->type = type;
|
||||
pkt->size = size;
|
||||
memcpy(pkt->data, data, size);
|
||||
return pkt;
|
||||
}
|
||||
|
||||
int serialize_packet(Packet* pkt, char* buffer) {
|
||||
buffer[0] = pkt->type;
|
||||
buffer[1] = (pkt->size >> 8) & 0xFF;
|
||||
buffer[2] = pkt->size & 0xFF;
|
||||
memcpy(buffer + 3, pkt->data, pkt->size);
|
||||
return 3 + pkt->size;
|
||||
}
|
||||
|
||||
Packet* deserialize_packet(const char* buffer, int len) {
|
||||
if (len < 3) return NULL;
|
||||
uint8_t type = buffer[0];
|
||||
uint16_t size = ((uint8_t)buffer[1] << 8) | (uint8_t)buffer[2];
|
||||
if (size > len - 3) return NULL;
|
||||
return create_packet(type, buffer + 3, size);
|
||||
}
|
||||
|
||||
void destroy_packet(Packet* pkt) {
|
||||
free(pkt);
|
||||
}
|
||||
|
||||
void net_init() {
|
||||
WSADATA wsa;
|
||||
WSAStartup(MAKEWORD(2, 2), &wsa);
|
||||
}
|
||||
|
||||
void net_cleanup() {
|
||||
WSACleanup();
|
||||
}
|
||||
|
||||
SOCKET net_connect(const char* ip, int port) {
|
||||
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
struct sockaddr_in addr = {
|
||||
.sin_family = AF_INET,
|
||||
.sin_port = htons(port)
|
||||
};
|
||||
inet_pton(AF_INET, ip, &addr.sin_addr);
|
||||
connect(sock, (struct sockaddr*)&addr, sizeof(addr));
|
||||
return sock;
|
||||
}
|
||||
|
||||
SOCKET net_bind_and_listen(int port) {
|
||||
SOCKET server = socket(AF_INET, SOCK_STREAM, 0);
|
||||
struct sockaddr_in addr = {
|
||||
.sin_family = AF_INET,
|
||||
.sin_port = htons(port),
|
||||
.sin_addr.s_addr = INADDR_ANY
|
||||
};
|
||||
bind(server, (struct sockaddr*)&addr, sizeof(addr));
|
||||
listen(server, SOMAXCONN);
|
||||
return server;
|
||||
}
|
||||
|
||||
SOCKET net_accept(SOCKET server) {
|
||||
return accept(server, NULL, NULL);
|
||||
}
|
||||
|
||||
|
||||
void set_socket_nonblocking(SOCKET sock, int enable)
|
||||
{
|
||||
u_long mode = (enable) ? 1 : 0;
|
||||
ioctlsocket(sock, FIONBIO, &mode);
|
||||
}
|
40
src/net.h
40
src/net.h
@ -1,40 +0,0 @@
|
||||
#ifndef NET_H
|
||||
#define NET_H
|
||||
|
||||
#include <winsock2.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define MAX_CLIENTS 64
|
||||
#define BUFFER_SIZE 1024
|
||||
|
||||
typedef enum {
|
||||
PACKET_PING = 1,
|
||||
PACKET_TEXT,
|
||||
PACKET_CLIENT_JOINED,
|
||||
PACKET_CLIENT_LEFT,
|
||||
PACKET_PLAYER_MOVE,
|
||||
PACKET_PLAYER_STATE,
|
||||
PACKET_MAX
|
||||
} PacketType;
|
||||
|
||||
typedef struct {
|
||||
uint8_t type;
|
||||
uint16_t size;
|
||||
char data[];
|
||||
} Packet;
|
||||
|
||||
Packet* create_packet(PacketType type, const void* data, uint16_t size);
|
||||
int serialize_packet(Packet* pkt, char* buffer);
|
||||
Packet* deserialize_packet(const char* buffer, int len);
|
||||
void destroy_packet(Packet* pkt);
|
||||
|
||||
// Networking setup
|
||||
void net_init();
|
||||
void net_cleanup();
|
||||
SOCKET net_connect(const char* ip, int port);
|
||||
SOCKET net_bind_and_listen(int port);
|
||||
SOCKET net_accept(SOCKET server);
|
||||
void set_socket_nonblocking(SOCKET sock, int enable);
|
||||
|
||||
|
||||
#endif
|
@ -1,2 +0,0 @@
|
||||
#include "player.h"
|
||||
|
25
src/player.h
25
src/player.h
@ -1,25 +0,0 @@
|
||||
#ifndef PLAYER_H
|
||||
#define PLAYER_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct {
|
||||
uint32_t id;
|
||||
float x, y;
|
||||
} Player;
|
||||
|
||||
typedef struct {
|
||||
uint32_t id;
|
||||
float dx;
|
||||
float dy;
|
||||
} PlayerMove;
|
||||
|
||||
typedef struct {
|
||||
uint32_t id;
|
||||
float x;
|
||||
float y;
|
||||
} PlayerState;
|
||||
|
||||
#define MAX_PLAYERS 64
|
||||
|
||||
#endif
|
173
test.py
Normal file
173
test.py
Normal file
@ -0,0 +1,173 @@
|
||||
#!/usr/bin/env python3
|
||||
import socket
|
||||
import struct
|
||||
import threading
|
||||
import tkinter as tk
|
||||
from tkinter import scrolledtext, messagebox
|
||||
import queue
|
||||
import sys
|
||||
|
||||
PACKET_NAMES = {
|
||||
1: "HEARTBEAT",
|
||||
2: "JOIN",
|
||||
3: "DISCONNECT",
|
||||
4: "MOVE",
|
||||
5: "CHAT",
|
||||
6: "PLAYER_UPDATE",
|
||||
7: "SCORE_UPDATE",
|
||||
8: "GAME_START",
|
||||
9: "GAME_END",
|
||||
10: "PING",
|
||||
11: "PONG",
|
||||
12: "AUTH_REQUEST",
|
||||
13: "AUTH_RESPONSE",
|
||||
14: "PACKET_MAX"
|
||||
}
|
||||
|
||||
|
||||
class PacketClient:
|
||||
def __init__(self, server_ip, server_port):
|
||||
self.server_ip = server_ip
|
||||
self.server_port = server_port
|
||||
self.sock = None
|
||||
self.running = False
|
||||
self.recv_queue = queue.Queue() # For thread-safe packet passing
|
||||
|
||||
def connect(self):
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
try:
|
||||
self.sock.connect((self.server_ip, self.server_port))
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"Failed to connect to {self.server_ip}:{self.server_port}: {e}")
|
||||
self.running = True
|
||||
threading.Thread(target=self.receive_loop, daemon=True).start()
|
||||
|
||||
def receive_loop(self):
|
||||
while self.running:
|
||||
try:
|
||||
header = self._recv_all(4)
|
||||
if header is None:
|
||||
break
|
||||
(payload_len,) = struct.unpack("!I", header)
|
||||
payload = self._recv_all(payload_len)
|
||||
if payload is None:
|
||||
break
|
||||
packet_type = payload[0]
|
||||
data = payload[1:].decode("utf-8", errors="replace")
|
||||
# Enqueue the packet for the GUI.
|
||||
self.recv_queue.put((packet_type, data))
|
||||
except Exception as e:
|
||||
self.recv_queue.put(("Error", str(e)))
|
||||
break
|
||||
self.running = False
|
||||
|
||||
def _recv_all(self, n):
|
||||
data = b""
|
||||
while len(data) < n:
|
||||
try:
|
||||
chunk = self.sock.recv(n - len(data))
|
||||
except Exception:
|
||||
return None
|
||||
if not chunk:
|
||||
return None
|
||||
data += chunk
|
||||
return data
|
||||
|
||||
def send_packet(self, packet_type, data):
|
||||
data_bytes = data.encode("utf-8")
|
||||
payload = struct.pack("!B", packet_type) + data_bytes
|
||||
packet = struct.pack("!I", len(payload)) + payload
|
||||
try:
|
||||
self.sock.sendall(packet)
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"Failed to send packet: {e}")
|
||||
|
||||
def close(self):
|
||||
self.running = False
|
||||
if self.sock:
|
||||
self.sock.close()
|
||||
|
||||
# The GUI application.
|
||||
class PacketClientGUI:
|
||||
def __init__(self, master, client: PacketClient):
|
||||
self.master = master
|
||||
self.client = client
|
||||
|
||||
master.title("Packet Client GUI")
|
||||
|
||||
# Create a scrolling text widget for received packets.
|
||||
self.received_text = scrolledtext.ScrolledText(master, width=80, height=20, state="disabled")
|
||||
self.received_text.grid(row=0, column=0, columnspan=4, padx=10, pady=10)
|
||||
|
||||
# Packet type label and entry.
|
||||
tk.Label(master, text="Packet Type:").grid(row=1, column=0, sticky="e", padx=(10,2))
|
||||
self.type_entry = tk.Entry(master, width=5)
|
||||
self.type_entry.grid(row=1, column=1, sticky="w")
|
||||
self.type_entry.insert(0, "4") # Default to MOVE packet
|
||||
|
||||
# Data label and entry.
|
||||
tk.Label(master, text="Data:").grid(row=1, column=2, sticky="e", padx=(10,2))
|
||||
self.data_entry = tk.Entry(master, width=40)
|
||||
self.data_entry.grid(row=1, column=3, sticky="w", padx=(0,10))
|
||||
|
||||
# Send button.
|
||||
self.send_button = tk.Button(master, text="Send Packet", command=self.send_packet)
|
||||
self.send_button.grid(row=2, column=0, columnspan=4, pady=(5,10))
|
||||
|
||||
# Start periodic GUI update.
|
||||
self.master.after(100, self.process_recv_queue)
|
||||
|
||||
def process_recv_queue(self):
|
||||
while not self.client.recv_queue.empty():
|
||||
pkt_type, data = self.client.recv_queue.get()
|
||||
if isinstance(pkt_type, int):
|
||||
name = PACKET_NAMES.get(pkt_type, f"Unknown ({pkt_type})")
|
||||
else:
|
||||
name = pkt_type # For error messages
|
||||
self.append_text(f"{name} > {data}\n")
|
||||
self.master.after(100, self.process_recv_queue)
|
||||
|
||||
def append_text(self, text):
|
||||
self.received_text.config(state="normal")
|
||||
self.received_text.insert(tk.END, text)
|
||||
self.received_text.see(tk.END)
|
||||
self.received_text.config(state="disabled")
|
||||
|
||||
def send_packet(self):
|
||||
try:
|
||||
pkt_type = int(self.type_entry.get())
|
||||
except ValueError:
|
||||
tk.messagebox.showerror("Error", "Packet type must be an integer")
|
||||
return
|
||||
data = self.data_entry.get()
|
||||
try:
|
||||
self.client.send_packet(pkt_type, data)
|
||||
# Show the packet sent, using the name if available.
|
||||
pkt_name = PACKET_NAMES.get(pkt_type, f"Unknown ({pkt_type})")
|
||||
self.append_text(f"Sent Packet | Type: {pkt_name} | Data: {data}\n")
|
||||
except Exception as e:
|
||||
tk.messagebox.showerror("Error", str(e))
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: python packet_client_gui.py <server_ip> <server_port>")
|
||||
sys.exit(1)
|
||||
server_ip = sys.argv[1]
|
||||
server_port = int(sys.argv[2])
|
||||
|
||||
client = PacketClient(server_ip, server_port)
|
||||
try:
|
||||
client.connect()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
sys.exit(1)
|
||||
|
||||
root = tk.Tk()
|
||||
app = PacketClientGUI(root, client)
|
||||
try:
|
||||
root.mainloop()
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Reference in New Issue
Block a user