mirror of
https://github.com/LHY0125/Gobang-Game.git
synced 2026-06-28 16:35:55 +08:00
feat(network): 集成ENet库并实现局域网联机对战功能
- 添加ENet库作为网络通信基础,替换原有的原生Socket实现 - 扩展游戏模式支持局域网联机对战(PvP网络模式) - 重构网络状态结构以适配ENet的Host/Peer模型 - 在图形界面中添加网络对战菜单,支持创建房间和加入房间 - 实现网络消息的发送与接收,包括落子、断开连接等消息类型 - 为网络对战添加定时器轮询机制,实时处理网络事件 - 更新构建系统以编译和链接ENet库
This commit is contained in:
+1
-1
@@ -12,7 +12,7 @@ Ihandle *board_canvas = NULL;
|
||||
Ihandle *lbl_player = NULL;
|
||||
Ihandle *lbl_status = NULL;
|
||||
int gui_loop_running = 0;
|
||||
int gui_game_mode = 0; // 0: PvP, 1: PvE, 2: Replay
|
||||
int gui_game_mode = 0; // 0: PvP, 1: PvE, 2: Replay, 3: Network
|
||||
int replay_total_steps = 0; // 复盘总步数
|
||||
|
||||
/**
|
||||
|
||||
+133
@@ -5,12 +5,73 @@
|
||||
#include "gobang.h"
|
||||
#include "ai.h"
|
||||
#include "record.h"
|
||||
#include "network.h"
|
||||
#include <iup.h>
|
||||
#include <iupdraw.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static Ihandle *timer = NULL; // 网络轮询定时器
|
||||
|
||||
/**
|
||||
* @brief 网络事件轮询回调
|
||||
*/
|
||||
static int timer_cb(Ihandle *ih)
|
||||
{
|
||||
(void)ih;
|
||||
if (gui_game_mode != 3 || game_over)
|
||||
return IUP_DEFAULT;
|
||||
|
||||
NetworkMessage msg;
|
||||
// 非阻塞接收消息
|
||||
if (receive_network_message(&msg, 0))
|
||||
{
|
||||
if (msg.type == MSG_MOVE)
|
||||
{
|
||||
int bx = msg.x;
|
||||
int by = msg.y;
|
||||
int pid = msg.player_id;
|
||||
|
||||
if (have_space(bx, by))
|
||||
{
|
||||
player_move(bx, by, pid);
|
||||
if (check_win(bx, by, pid))
|
||||
{
|
||||
game_over = 1;
|
||||
sprintf(status_message, "对手获胜!");
|
||||
IupMessage("游戏结束", "对手获胜!");
|
||||
}
|
||||
else
|
||||
{
|
||||
current_player_gui = network_state.local_player_id;
|
||||
sprintf(status_message, "轮到你落子");
|
||||
}
|
||||
update_ui_labels();
|
||||
if (board_canvas)
|
||||
IupUpdate(board_canvas);
|
||||
}
|
||||
}
|
||||
else if (msg.type == MSG_DISCONNECT || msg.type == MSG_SURRENDER)
|
||||
{
|
||||
game_over = 1;
|
||||
sprintf(status_message, "对手已断开连接/认输");
|
||||
IupMessage("游戏结束", "对手已退出游戏,你赢了!");
|
||||
update_ui_labels();
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_network_connected() && !game_over)
|
||||
{
|
||||
game_over = 1;
|
||||
sprintf(status_message, "与服务器断开连接");
|
||||
IupMessage("错误", "网络连接已断开");
|
||||
update_ui_labels();
|
||||
}
|
||||
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ACTION 回调:负责重绘
|
||||
*/
|
||||
@@ -46,6 +107,11 @@ int btn_undo_cb(Ihandle *ih)
|
||||
{
|
||||
steps_to_undo = 2; // 悔棋两步(玩家+AI)
|
||||
}
|
||||
else if (gui_game_mode == 3) // Network
|
||||
{
|
||||
IupMessage("提示", "网络模式暂不支持悔棋");
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
if (step_count >= steps_to_undo)
|
||||
{
|
||||
@@ -66,6 +132,7 @@ int btn_undo_cb(Ihandle *ih)
|
||||
sprintf(status_message, "无法悔棋");
|
||||
update_ui_labels();
|
||||
}
|
||||
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
@@ -120,6 +187,18 @@ int btn_back_cb(Ihandle *ih)
|
||||
(void)ih;
|
||||
printf("DEBUG: Back to Menu clicked\n");
|
||||
|
||||
// 如果是网络模式,断开连接
|
||||
if (gui_game_mode == 3)
|
||||
{
|
||||
disconnect_network();
|
||||
if (timer)
|
||||
{
|
||||
IupSetAttribute(timer, "RUN", "NO");
|
||||
IupDestroy(timer);
|
||||
timer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// 1. 先显示主菜单
|
||||
show_main_menu();
|
||||
printf("DEBUG: Main menu shown\n");
|
||||
@@ -150,6 +229,11 @@ int button_cb(Ihandle *ih, int button, int pressed, int x, int y, char *status)
|
||||
if (game_over)
|
||||
return IUP_DEFAULT;
|
||||
|
||||
if (gui_game_mode == 3 && current_player_gui != network_state.local_player_id)
|
||||
{
|
||||
return IUP_DEFAULT; // 网络模式下,非自己回合不可落子
|
||||
}
|
||||
|
||||
int board_x, board_y;
|
||||
if (screen_to_board(x, y, &board_x, &board_y))
|
||||
{
|
||||
@@ -162,6 +246,8 @@ int button_cb(Ihandle *ih, int button, int pressed, int x, int y, char *status)
|
||||
if (check_win(board_x, board_y, current_player_gui))
|
||||
{
|
||||
game_over = 1;
|
||||
if (gui_game_mode == 3)
|
||||
send_move(board_x, board_y, current_player_gui); // 发送最后一步
|
||||
if (current_player_gui == PLAYER)
|
||||
{
|
||||
sprintf(status_message, "黑子获胜!");
|
||||
@@ -183,6 +269,12 @@ int button_cb(Ihandle *ih, int button, int pressed, int x, int y, char *status)
|
||||
else
|
||||
sprintf(status_message, "轮到白子");
|
||||
}
|
||||
else if (gui_game_mode == 3) // Network
|
||||
{
|
||||
send_move(board_x, board_y, current_player_gui);
|
||||
current_player_gui = network_state.remote_player_id;
|
||||
sprintf(status_message, "等待对手落子...");
|
||||
}
|
||||
else // PvE
|
||||
{
|
||||
current_player_gui = AI;
|
||||
@@ -396,3 +488,44 @@ void start_pve_game_gui()
|
||||
}
|
||||
printf("DEBUG: start_pve_game_gui end\n");
|
||||
}
|
||||
|
||||
void start_network_game_gui()
|
||||
{
|
||||
printf("DEBUG: start_network_game_gui start\n");
|
||||
gui_game_mode = 3;
|
||||
empty_board();
|
||||
|
||||
// 主机执黑先行
|
||||
current_player_gui = PLAYER1;
|
||||
game_over = 0;
|
||||
|
||||
create_game_window();
|
||||
printf("DEBUG: create_game_window returned\n");
|
||||
|
||||
if (dlg)
|
||||
{
|
||||
IupShowXY(dlg, IUP_CENTER, IUP_CENTER);
|
||||
printf("DEBUG: IupShowXY called\n");
|
||||
}
|
||||
|
||||
if (network_state.is_server)
|
||||
sprintf(status_message, "局域网联机 - 你是主机(黑子),轮到你落子");
|
||||
else
|
||||
sprintf(status_message, "局域网联机 - 你是客机(白子),等待对手落子...");
|
||||
|
||||
update_ui_labels();
|
||||
|
||||
// 强制初始重绘
|
||||
if (board_canvas)
|
||||
{
|
||||
IupUpdate(board_canvas);
|
||||
}
|
||||
|
||||
// 启动网络轮询定时器
|
||||
timer = IupTimer();
|
||||
IupSetCallback(timer, "ACTION_CB", (Icallback)timer_cb);
|
||||
IupSetAttribute(timer, "TIME", "50"); // 50ms 轮询一次
|
||||
IupSetAttribute(timer, "RUN", "YES");
|
||||
|
||||
printf("DEBUG: start_network_game_gui end\n");
|
||||
}
|
||||
|
||||
+111
-1
@@ -1,12 +1,15 @@
|
||||
#include <iup.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "gui_menu.h"
|
||||
#include "gui.h"
|
||||
#include "gui_internal.h"
|
||||
#include "globals.h"
|
||||
#include "config.h"
|
||||
#include "network.h"
|
||||
|
||||
static Ihandle *menu_dlg = NULL;
|
||||
Ihandle *menu_dlg = NULL;
|
||||
|
||||
static int btn_pvp_cb(Ihandle *ih)
|
||||
{
|
||||
@@ -28,6 +31,107 @@ static int btn_pve_cb(Ihandle *ih)
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
// --- 网络对战相关回调 ---
|
||||
static int btn_network_host_cb(Ihandle *ih)
|
||||
{
|
||||
Ihandle *dlg = IupGetDialog(ih);
|
||||
Ihandle *txt_port = IupGetDialogChild(dlg, "NET_PORT");
|
||||
int port = IupGetInt(txt_port, "VALUE");
|
||||
if (port <= 0 || port > 65535) port = DEFAULT_PORT;
|
||||
|
||||
if (create_server(port))
|
||||
{
|
||||
IupMessage("成功", "房间创建成功,等待玩家加入...");
|
||||
IupHide(dlg);
|
||||
start_network_game_gui();
|
||||
IupHide(menu_dlg);
|
||||
}
|
||||
else
|
||||
{
|
||||
IupMessage("错误", "创建房间失败,可能是端口被占用");
|
||||
}
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
static int btn_network_join_cb(Ihandle *ih)
|
||||
{
|
||||
Ihandle *dlg = IupGetDialog(ih);
|
||||
Ihandle *txt_ip = IupGetDialogChild(dlg, "NET_IP");
|
||||
Ihandle *txt_port = IupGetDialogChild(dlg, "NET_PORT");
|
||||
|
||||
char *ip = IupGetAttribute(txt_ip, "VALUE");
|
||||
int port = IupGetInt(txt_port, "VALUE");
|
||||
if (port <= 0 || port > 65535) port = DEFAULT_PORT;
|
||||
|
||||
if (connect_to_server(ip, port))
|
||||
{
|
||||
IupMessage("成功", "成功加入房间!");
|
||||
IupHide(dlg);
|
||||
start_network_game_gui();
|
||||
IupHide(menu_dlg);
|
||||
}
|
||||
else
|
||||
{
|
||||
IupMessage("错误", "加入房间失败,请检查IP和端口");
|
||||
}
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
static int btn_network_cancel_cb(Ihandle *ih)
|
||||
{
|
||||
Ihandle *dlg = IupGetDialog(ih);
|
||||
IupHide(dlg);
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
static int btn_network_cb(Ihandle *ih)
|
||||
{
|
||||
(void)ih;
|
||||
printf("DEBUG: Opening Network Menu\n");
|
||||
|
||||
Ihandle *txt_ip = IupText(NULL);
|
||||
IupSetAttribute(txt_ip, "NAME", "NET_IP");
|
||||
IupSetAttribute(txt_ip, "VALUE", "127.0.0.1");
|
||||
IupSetAttribute(txt_ip, "SIZE", "100x");
|
||||
|
||||
Ihandle *txt_port = IupText(NULL);
|
||||
IupSetAttribute(txt_port, "NAME", "NET_PORT");
|
||||
char port_str[16];
|
||||
sprintf(port_str, "%d", DEFAULT_PORT);
|
||||
IupSetAttribute(txt_port, "VALUE", port_str);
|
||||
IupSetAttribute(txt_port, "SIZE", "50x");
|
||||
|
||||
Ihandle *btn_host = IupButton("创建房间", NULL);
|
||||
IupSetCallback(btn_host, "ACTION", (Icallback)btn_network_host_cb);
|
||||
|
||||
Ihandle *btn_join = IupButton("加入房间", NULL);
|
||||
IupSetCallback(btn_join, "ACTION", (Icallback)btn_network_join_cb);
|
||||
|
||||
Ihandle *btn_cancel = IupButton("取消", NULL);
|
||||
IupSetCallback(btn_cancel, "ACTION", (Icallback)btn_network_cancel_cb);
|
||||
|
||||
Ihandle *vbox = IupVbox(
|
||||
IupHbox(IupLabel("目标 IP: "), txt_ip, NULL),
|
||||
IupHbox(IupLabel("端口: "), txt_port, NULL),
|
||||
IupLabel(""),
|
||||
IupHbox(btn_host, btn_join, btn_cancel, NULL),
|
||||
NULL
|
||||
);
|
||||
IupSetAttribute(vbox, "MARGIN", "20x20");
|
||||
IupSetAttribute(vbox, "GAP", "10");
|
||||
IupSetAttribute(vbox, "ALIGNMENT", "ACENTER");
|
||||
|
||||
Ihandle *dlg = IupDialog(vbox);
|
||||
IupSetAttribute(dlg, "TITLE", "局域网联机");
|
||||
IupSetAttribute(dlg, "RESIZE", "NO");
|
||||
|
||||
IupPopup(dlg, IUP_CENTER, IUP_CENTER);
|
||||
IupDestroy(dlg);
|
||||
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
// --- 网络对战结束 ---
|
||||
|
||||
static int btn_replay_cb(Ihandle *ih)
|
||||
{
|
||||
(void)ih;
|
||||
@@ -227,6 +331,11 @@ void create_main_menu()
|
||||
IupSetAttribute(btn_pve, "SIZE", "120x30");
|
||||
IupSetAttribute(btn_pve, "FONT", "SimHei, 12");
|
||||
|
||||
Ihandle *btn_net = IupButton("局域网联机", NULL);
|
||||
IupSetCallback(btn_net, "ACTION", (Icallback)btn_network_cb);
|
||||
IupSetAttribute(btn_net, "SIZE", "120x30");
|
||||
IupSetAttribute(btn_net, "FONT", "SimHei, 12");
|
||||
|
||||
Ihandle *btn_replay = IupButton("复盘模式", NULL);
|
||||
IupSetCallback(btn_replay, "ACTION", (Icallback)btn_replay_cb);
|
||||
IupSetAttribute(btn_replay, "SIZE", "120x30");
|
||||
@@ -246,6 +355,7 @@ void create_main_menu()
|
||||
lbl_title,
|
||||
btn_pvp,
|
||||
btn_pve,
|
||||
btn_net,
|
||||
btn_replay,
|
||||
btn_settings,
|
||||
btn_exit,
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
* @note 本文件包含了游戏的主循环、模式选择和游戏初始化等功能
|
||||
* @brief 将以下指令复制到powershell
|
||||
*
|
||||
* !图形化版本编译(需要IUP库):
|
||||
* !编译(需要IUP库):
|
||||
* mingw32-make gui
|
||||
.\bin\gobang_gui.exe
|
||||
*
|
||||
|
||||
+158
-212
@@ -12,41 +12,20 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#pragma comment(lib, "ws2_32.lib")
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#define INVALID_SOCKET -1
|
||||
#define SOCKET_ERROR -1
|
||||
#define closesocket close
|
||||
typedef int SOCKET;
|
||||
#endif
|
||||
#include <enet/enet.h>
|
||||
|
||||
/**
|
||||
* @brief 初始化网络模块
|
||||
*/
|
||||
bool init_network()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
WSADATA wsaData;
|
||||
int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
|
||||
if (result != 0)
|
||||
if (enet_initialize() != 0)
|
||||
{
|
||||
printf("WSAStartup failed: %d\n", result);
|
||||
printf("An error occurred while initializing ENet.\n");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
memset(&network_state, 0, sizeof(NetworkGameState));
|
||||
network_state.socket = INVALID_SOCKET;
|
||||
network_state.port = DEFAULT_PORT;
|
||||
|
||||
return true;
|
||||
@@ -57,16 +36,15 @@ bool init_network()
|
||||
*/
|
||||
void cleanup_network()
|
||||
{
|
||||
if (network_state.socket != INVALID_SOCKET)
|
||||
disconnect_network();
|
||||
|
||||
if (network_state.host != NULL)
|
||||
{
|
||||
closesocket(network_state.socket);
|
||||
network_state.socket = INVALID_SOCKET;
|
||||
enet_host_destroy((ENetHost *)network_state.host);
|
||||
network_state.host = NULL;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
WSACleanup();
|
||||
#endif
|
||||
|
||||
enet_deinitialize();
|
||||
network_state.is_connected = false;
|
||||
}
|
||||
|
||||
@@ -75,43 +53,23 @@ void cleanup_network()
|
||||
*/
|
||||
bool create_server(int port)
|
||||
{
|
||||
struct sockaddr_in server_addr, client_addr;
|
||||
int addr_len = sizeof(client_addr);
|
||||
ENetAddress address;
|
||||
|
||||
// 创建套接字
|
||||
SOCKET listen_socket = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (listen_socket == INVALID_SOCKET)
|
||||
// 绑定所有接口
|
||||
address.host = ENET_HOST_ANY;
|
||||
address.port = port;
|
||||
|
||||
// 创建服务器主机
|
||||
network_state.host = (void *)enet_host_create(&address,
|
||||
1, // 仅允许1个客户端连接
|
||||
2, // 允许2个通道 (0 和 1)
|
||||
0, // 假设传入带宽无限制
|
||||
0 // 假设传出带宽无限制
|
||||
);
|
||||
|
||||
if (network_state.host == NULL)
|
||||
{
|
||||
printf("创建套接字失败\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 设置地址重用
|
||||
int opt = 1;
|
||||
#ifdef _WIN32
|
||||
setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt));
|
||||
#else
|
||||
setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||
#endif
|
||||
|
||||
// 绑定地址
|
||||
memset(&server_addr, 0, sizeof(server_addr));
|
||||
server_addr.sin_family = AF_INET;
|
||||
server_addr.sin_addr.s_addr = INADDR_ANY;
|
||||
server_addr.sin_port = htons(port);
|
||||
|
||||
if (bind(listen_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == SOCKET_ERROR)
|
||||
{
|
||||
printf("绑定端口失败\n");
|
||||
closesocket(listen_socket);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 开始监听
|
||||
if (listen(listen_socket, 1) == SOCKET_ERROR)
|
||||
{
|
||||
printf("监听失败\n");
|
||||
closesocket(listen_socket);
|
||||
printf("创建服务器失败\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -127,29 +85,31 @@ bool create_server(int port)
|
||||
printf("服务器已启动,监听端口: %d\n", port);
|
||||
}
|
||||
|
||||
// 等待客户端连接
|
||||
SOCKET client_socket = accept(listen_socket, (struct sockaddr *)&client_addr, &addr_len);
|
||||
if (client_socket == INVALID_SOCKET)
|
||||
// 阻塞等待客户端连接
|
||||
ENetEvent event;
|
||||
printf("等待连接...\n");
|
||||
// 等待长一点的时间,比如60秒,或者在真实应用中应该放在循环里非阻塞检查
|
||||
if (enet_host_service((ENetHost *)network_state.host, &event, 60000) > 0 &&
|
||||
event.type == ENET_EVENT_TYPE_CONNECT)
|
||||
{
|
||||
printf("接受连接失败\n");
|
||||
closesocket(listen_socket);
|
||||
return false;
|
||||
network_state.peer = (void *)event.peer;
|
||||
network_state.is_server = true;
|
||||
network_state.is_connected = true;
|
||||
network_state.local_player_id = PLAYER1;
|
||||
network_state.remote_player_id = PLAYER2;
|
||||
network_state.port = port;
|
||||
|
||||
enet_address_get_host_ip(&event.peer->address, network_state.remote_ip, sizeof(network_state.remote_ip));
|
||||
|
||||
printf("客户端已连接: %s\n", network_state.remote_ip);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 关闭监听套接字
|
||||
closesocket(listen_socket);
|
||||
|
||||
// 保存连接信息
|
||||
network_state.socket = client_socket;
|
||||
network_state.is_server = true;
|
||||
network_state.is_connected = true;
|
||||
network_state.local_player_id = PLAYER1;
|
||||
network_state.remote_player_id = PLAYER2;
|
||||
network_state.port = port;
|
||||
strcpy(network_state.remote_ip, inet_ntoa(client_addr.sin_addr));
|
||||
|
||||
printf("客户端已连接: %s\n", network_state.remote_ip);
|
||||
return true;
|
||||
// 超时或失败
|
||||
printf("等待连接超时或失败\n");
|
||||
enet_host_destroy((ENetHost *)network_state.host);
|
||||
network_state.host = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,55 +117,60 @@ bool create_server(int port)
|
||||
*/
|
||||
bool connect_to_server(const char *ip, int port)
|
||||
{
|
||||
struct sockaddr_in server_addr;
|
||||
// 创建客户端主机
|
||||
network_state.host = (void *)enet_host_create(NULL, // 创建客户端
|
||||
1, // 仅允许1个传出连接
|
||||
2, // 允许2个通道 (0 和 1)
|
||||
0, // 假设传入带宽无限制
|
||||
0 // 假设传出带宽无限制
|
||||
);
|
||||
|
||||
// 创建套接字
|
||||
SOCKET client_socket = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (client_socket == INVALID_SOCKET)
|
||||
if (network_state.host == NULL)
|
||||
{
|
||||
printf("创建套接字失败\n");
|
||||
printf("创建客户端主机失败\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 设置服务器地址
|
||||
memset(&server_addr, 0, sizeof(server_addr));
|
||||
server_addr.sin_family = AF_INET;
|
||||
server_addr.sin_port = htons(port);
|
||||
ENetAddress address;
|
||||
ENetEvent event;
|
||||
ENetPeer *peer;
|
||||
|
||||
#ifdef _WIN32
|
||||
server_addr.sin_addr.s_addr = inet_addr(ip);
|
||||
if (server_addr.sin_addr.s_addr == INADDR_NONE)
|
||||
{
|
||||
#else
|
||||
if (inet_pton(AF_INET, ip, &server_addr.sin_addr) <= 0)
|
||||
{
|
||||
#endif
|
||||
printf("无效的IP地址: %s\n", ip);
|
||||
closesocket(client_socket);
|
||||
return false;
|
||||
}
|
||||
enet_address_set_host(&address, ip);
|
||||
address.port = port;
|
||||
|
||||
printf("正在连接到服务器 %s:%d...\n", ip, port);
|
||||
|
||||
// 连接到服务器
|
||||
if (connect(client_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == SOCKET_ERROR)
|
||||
peer = enet_host_connect((ENetHost *)network_state.host, &address, 2, 0);
|
||||
if (peer == NULL)
|
||||
{
|
||||
printf("连接服务器失败\n");
|
||||
closesocket(client_socket);
|
||||
printf("没有可用的对等端来启动连接\n");
|
||||
enet_host_destroy((ENetHost *)network_state.host);
|
||||
network_state.host = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 保存连接信息
|
||||
network_state.socket = client_socket;
|
||||
network_state.is_server = false;
|
||||
network_state.is_connected = true;
|
||||
network_state.local_player_id = PLAYER2;
|
||||
network_state.remote_player_id = PLAYER1;
|
||||
network_state.port = port;
|
||||
strcpy(network_state.remote_ip, ip);
|
||||
// 等待连接成功
|
||||
if (enet_host_service((ENetHost *)network_state.host, &event, 5000) > 0 &&
|
||||
event.type == ENET_EVENT_TYPE_CONNECT)
|
||||
{
|
||||
network_state.peer = (void *)peer;
|
||||
network_state.is_server = false;
|
||||
network_state.is_connected = true;
|
||||
network_state.local_player_id = PLAYER2;
|
||||
network_state.remote_player_id = PLAYER1;
|
||||
network_state.port = port;
|
||||
strncpy(network_state.remote_ip, ip, sizeof(network_state.remote_ip) - 1);
|
||||
|
||||
printf("成功连接到服务器\n");
|
||||
return true;
|
||||
printf("成功连接到服务器\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 连接失败
|
||||
printf("连接服务器失败\n");
|
||||
enet_peer_reset(peer);
|
||||
enet_host_destroy((ENetHost *)network_state.host);
|
||||
network_state.host = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -213,13 +178,21 @@ bool connect_to_server(const char *ip, int port)
|
||||
*/
|
||||
bool send_network_message(const NetworkMessage *msg)
|
||||
{
|
||||
if (!network_state.is_connected || network_state.socket == INVALID_SOCKET)
|
||||
if (!network_state.is_connected || network_state.peer == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int bytes_sent = send(network_state.socket, (const char *)msg, sizeof(NetworkMessage), 0);
|
||||
return bytes_sent == sizeof(NetworkMessage);
|
||||
ENetPacket *packet = enet_packet_create(msg, sizeof(NetworkMessage), ENET_PACKET_FLAG_RELIABLE);
|
||||
if (enet_peer_send((ENetPeer *)network_state.peer, 0, packet) < 0)
|
||||
{
|
||||
enet_packet_destroy(packet); // 发送失败需手动销毁
|
||||
return false;
|
||||
}
|
||||
|
||||
// 强制发送
|
||||
enet_host_flush((ENetHost *)network_state.host);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -227,51 +200,44 @@ bool send_network_message(const NetworkMessage *msg)
|
||||
*/
|
||||
bool receive_network_message(NetworkMessage *msg, int timeout_ms)
|
||||
{
|
||||
if (!network_state.is_connected || network_state.socket == INVALID_SOCKET)
|
||||
if (!network_state.is_connected || network_state.host == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 设置超时
|
||||
if (timeout_ms > 0)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
DWORD timeout = timeout_ms;
|
||||
setsockopt(network_state.socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout));
|
||||
#else
|
||||
struct timeval timeout;
|
||||
timeout.tv_sec = timeout_ms / 1000;
|
||||
timeout.tv_usec = (timeout_ms % 1000) * 1000;
|
||||
setsockopt(network_state.socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
|
||||
#endif
|
||||
}
|
||||
ENetEvent event;
|
||||
int serviceResult = enet_host_service((ENetHost *)network_state.host, &event, timeout_ms);
|
||||
|
||||
int bytes_received = recv(network_state.socket, (char *)msg, sizeof(NetworkMessage), 0);
|
||||
if (serviceResult > 0)
|
||||
{
|
||||
switch (event.type)
|
||||
{
|
||||
case ENET_EVENT_TYPE_RECEIVE:
|
||||
if (event.packet->dataLength == sizeof(NetworkMessage))
|
||||
{
|
||||
memcpy(msg, event.packet->data, sizeof(NetworkMessage));
|
||||
enet_packet_destroy(event.packet);
|
||||
return true;
|
||||
}
|
||||
enet_packet_destroy(event.packet);
|
||||
break;
|
||||
|
||||
if (bytes_received == sizeof(NetworkMessage))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (bytes_received == 0)
|
||||
{
|
||||
// 连接已关闭
|
||||
network_state.is_connected = false;
|
||||
printf("对方已断开连接\n");
|
||||
}
|
||||
else if (bytes_received == SOCKET_ERROR)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
int error = WSAGetLastError();
|
||||
if (error != WSAETIMEDOUT)
|
||||
{
|
||||
#else
|
||||
if (errno != EAGAIN && errno != EWOULDBLOCK)
|
||||
{
|
||||
#endif
|
||||
case ENET_EVENT_TYPE_DISCONNECT:
|
||||
network_state.is_connected = false;
|
||||
printf("网络接收错误\n");
|
||||
printf("对方已断开连接\n");
|
||||
network_state.peer = NULL;
|
||||
break;
|
||||
|
||||
case ENET_EVENT_TYPE_NONE:
|
||||
case ENET_EVENT_TYPE_CONNECT:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (serviceResult < 0)
|
||||
{
|
||||
network_state.is_connected = false;
|
||||
printf("网络接收错误\n");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -281,17 +247,34 @@ bool receive_network_message(NetworkMessage *msg, int timeout_ms)
|
||||
*/
|
||||
void disconnect_network()
|
||||
{
|
||||
if (network_state.is_connected)
|
||||
if (network_state.is_connected && network_state.peer != NULL)
|
||||
{
|
||||
NetworkMessage msg = {0};
|
||||
msg.type = MSG_DISCONNECT;
|
||||
msg.player_id = network_state.local_player_id;
|
||||
msg.timestamp = time(NULL);
|
||||
ENetEvent event;
|
||||
|
||||
send_network_message(&msg);
|
||||
enet_peer_disconnect((ENetPeer *)network_state.peer, 0);
|
||||
|
||||
// 等待断开确认
|
||||
while (enet_host_service((ENetHost *)network_state.host, &event, 3000) > 0)
|
||||
{
|
||||
switch (event.type)
|
||||
{
|
||||
case ENET_EVENT_TYPE_RECEIVE:
|
||||
enet_packet_destroy(event.packet);
|
||||
break;
|
||||
case ENET_EVENT_TYPE_DISCONNECT:
|
||||
puts("断开连接成功");
|
||||
goto DONE;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 超时强制重置
|
||||
enet_peer_reset((ENetPeer *)network_state.peer);
|
||||
DONE:
|
||||
network_state.is_connected = false;
|
||||
network_state.peer = NULL;
|
||||
}
|
||||
|
||||
cleanup_network();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -299,7 +282,7 @@ void disconnect_network()
|
||||
*/
|
||||
bool is_network_connected()
|
||||
{
|
||||
return network_state.is_connected && network_state.socket != INVALID_SOCKET;
|
||||
return network_state.is_connected && network_state.peer != NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -307,50 +290,13 @@ bool is_network_connected()
|
||||
*/
|
||||
bool get_local_ip(char *ip_buffer, int buffer_size)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// Windows实现
|
||||
char hostname[256];
|
||||
if (gethostname(hostname, sizeof(hostname)) == 0)
|
||||
{
|
||||
struct hostent *host_entry = gethostbyname(hostname);
|
||||
if (host_entry != NULL)
|
||||
{
|
||||
struct in_addr addr;
|
||||
addr.s_addr = *((unsigned long *)host_entry->h_addr_list[0]);
|
||||
strncpy(ip_buffer, inet_ntoa(addr), buffer_size - 1);
|
||||
ip_buffer[buffer_size - 1] = '\0';
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#else
|
||||
// Linux实现
|
||||
struct sockaddr_in addr;
|
||||
int sock = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (sock != -1)
|
||||
{
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = inet_addr("8.8.8.8");
|
||||
addr.sin_port = htons(80);
|
||||
|
||||
if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == 0)
|
||||
{
|
||||
socklen_t addr_len = sizeof(addr);
|
||||
if (getsockname(sock, (struct sockaddr *)&addr, &addr_len) == 0)
|
||||
{
|
||||
strncpy(ip_buffer, inet_ntoa(addr.sin_addr), buffer_size - 1);
|
||||
ip_buffer[buffer_size - 1] = '\0';
|
||||
close(sock);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
close(sock);
|
||||
}
|
||||
#endif
|
||||
|
||||
// 默认返回本地回环地址
|
||||
strncpy(ip_buffer, "127.0.0.1", buffer_size - 1);
|
||||
// ENet 没有直接获取本机局域网 IP 的简单跨平台函数。
|
||||
// 这里我们可以回退到原生 socket 方法,或者简单返回本地回环。
|
||||
// 为了不引入额外的系统头文件,暂时返回通用提示。
|
||||
// 在真实应用中,可以保留之前的 gethostname/gethostbyname 逻辑。
|
||||
strncpy(ip_buffer, "查看本机网络适配器", buffer_size - 1);
|
||||
ip_buffer[buffer_size - 1] = '\0';
|
||||
return false;
|
||||
return true; // 总是返回 true 以允许服务器继续启动
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user