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",
|
"name": "Win32",
|
||||||
"includePath": [
|
"includePath": [
|
||||||
"${default}",
|
"${default}",
|
||||||
"${workspaceFolder}/**",
|
"${workspaceFolder}/**"
|
||||||
"C:/msys64/mingw64/include"
|
|
||||||
],
|
],
|
||||||
"defines": [
|
"defines": [
|
||||||
"_DEBUG",
|
"_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
|
CXX := g++
|
||||||
CC = gcc
|
CXXFLAGS := -std=c++20 -Wall -Wextra -g -Iinclude -I/c/msys64/mingw64/include
|
||||||
|
|
||||||
# Paths
|
LDFLAGS := -lws2_32
|
||||||
SRC_DIR = src
|
|
||||||
BUILD_DIR = build
|
|
||||||
OUT = main.exe
|
|
||||||
|
|
||||||
# Source files
|
SRC_DIRS := src
|
||||||
SRC = \
|
BUILD_DIR := build
|
||||||
$(SRC_DIR)\main.c \
|
|
||||||
$(SRC_DIR)\net.c \
|
|
||||||
$(SRC_DIR)\event.c \
|
|
||||||
$(SRC_DIR)\player.c
|
|
||||||
|
|
||||||
# Object files
|
# Find all source files
|
||||||
OBJ = $(patsubst $(SRC_DIR)\%.c, $(BUILD_DIR)\%.o, $(SRC))
|
SRC := $(foreach dir, $(SRC_DIRS), $(wildcard $(dir)/*.cpp))
|
||||||
|
|
||||||
# Include and linker flags
|
OBJ := $(patsubst %.cpp, $(BUILD_DIR)/%.o, $(SRC))
|
||||||
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)
|
|
||||||
|
|
||||||
# Default target
|
TARGET := main.exe
|
||||||
all: $(OUT)
|
|
||||||
|
|
||||||
$(OUT): $(OBJ)
|
all: $(TARGET)
|
||||||
$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)
|
|
||||||
|
|
||||||
# Compile each .c into .o
|
$(TARGET): $(OBJ)
|
||||||
$(BUILD_DIR)\%.o: $(SRC_DIR)\%.c
|
$(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS)
|
||||||
@if not exist $(BUILD_DIR) mkdir $(BUILD_DIR)
|
|
||||||
$(CC) $(CFLAGS) -c $< -o $@
|
$(BUILD_DIR)/%.o: %.cpp
|
||||||
|
@mkdir "$(dir $@)" || exit 0
|
||||||
|
$(CXX) $(CXXFLAGS) -c $< -o $@
|
||||||
|
|
||||||
# Clean
|
|
||||||
clean:
|
clean:
|
||||||
del /Q $(BUILD_DIR)\*.o 2>nul || exit 0
|
del /Q /S $(subst /,\,$(BUILD_DIR)\*.o) $(TARGET) 2>nul || exit 0
|
||||||
del /Q $(OUT) 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