在Windows平台上,这通常使用两个主要的API:

(图片来源网络,侵删)
- Win32 API:用于创建窗口、处理消息、绘制图形等GUI操作。
- Winsock API:用于进行网络编程(创建socket、连接、发送/接收数据)。
下面我将为你提供一个完整的、详细的C语言示例,展示如何创建一个窗口,并在该窗口的消息循环中处理一个简单的TCP套接字服务器。
核心概念
- Win32窗口程序:需要一个窗口过程函数来处理所有发送到该窗口的消息(如鼠标点击、键盘输入、窗口关闭等)。
- 消息循环:程序的主线程会进入一个循环,不断从消息队列中取出消息,并分发给窗口过程进行处理。
- Winsock初始化:在使用任何Winsock函数之前,必须调用
WSAStartup初始化。 - 异步I/O与消息:为了在等待网络数据时不阻塞GUI(导致窗口卡死),我们使用异步套接字(Non-blocking Socket),当套接字上有数据可读或连接状态改变时,Windows系统会向我们的窗口发送特定的Windows消息(如
WM_SOCKET)。 WSAAsyncSelect:这个函数是关键,它将一个套接字与一个窗口句柄关联起来,并指定我们关心的网络事件(如FD_READ,FD_ACCEPT),当这些事件发生时,系统会自动向指定窗口发送我们定义的消息。
完整代码示例:一个简单的TCP回显服务器窗口
这个程序会创建一个窗口,并在窗口上显示它监听的端口号,当有客户端连接时,它会接收客户端发来的消息,并将其回显(发回)给客户端。
第1步:包含必要的头文件
#include <winsock2.h> #include <windows.h> #include <stdio.h> // 需要链接 Winsock2 库 #pragma comment(lib, "ws2_32.lib")
第2步:定义常量和全局变量
// 自定义消息,用于接收网络事件 #define WM_SOCKET (WM_USER + 1) // 全局变量,方便在窗口过程中访问 HWND g_hwnd = NULL; // 窗口句柄 SOCKET g_listenSocket = INVALID_SOCKET; // 监听套接字 SOCKET g_clientSocket = INVALID_SOCKET; // 客户端套接字
第3步:窗口过程函数
这是窗口的核心,负责处理所有窗口消息。
// 窗口过程函数
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_CREATE:
{
// 窗口创建时,初始化Winsock并创建监听套接字
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
MessageBox(hwnd, "WSAStartup failed!", "Error", MB_OK | MB_ICONERROR);
return -1;
}
// 创建监听套接字 (TCP)
g_listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (g_listenSocket == INVALID_SOCKET)
{
MessageBox(hwnd, "socket() failed!", "Error", MB_OK | MB_ICONERROR);
return -1;
}
// 设置套接字为非阻塞模式
u_long mode = 1;
ioctlsocket(g_listenSocket, FIONBIO, &mode);
// 绑定套接字到本地地址和端口
sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = INADDR_ANY;
serverAddr.sin_port = htons(8888); // 监听8888端口
if (bind(g_listenSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
{
MessageBox(hwnd, "bind() failed!", "Error", MB_OK | MB_ICONERROR);
return -1;
}
// 开始监听
if (listen(g_listenSocket, SOMAXCONN) == SOCKET_ERROR)
{
MessageBox(hwnd, "listen() failed!", "Error", MB_OK | MB_ICONERROR);
return -1;
}
// 将监听套接字与窗口关联,并指定我们关心的事件
// 当有连接请求或数据可读时,向窗口发送 WM_SOCKET 消息
if (WSAAsyncSelect(g_listenSocket, hwnd, WM_SOCKET, FD_ACCEPT | FD_READ | FD_CLOSE) == SOCKET_ERROR)
{
MessageBox(hwnd, "WSAAsyncSelect failed!", "Error", MB_OK | MB_ICONERROR);
return -1;
}
// 在窗口标题上显示状态
SetWindowText(hwnd, "TCP Server Running on Port 8888");
MessageBox(hwnd, "Server started. Waiting for connections...", "Info", MB_OK | MB_ICONINFORMATION);
break;
}
case WM_SOCKET:
{
// wParam 包含发生事件的套接字
// lParam 包含事件类型 (FD_ACCEPT, FD_READ, FD_CLOSE 等)
SOCKET eventSocket = (SOCKET)wParam;
int event = WSAGETSELECTEVENT(lParam);
if (event == FD_ACCEPT)
{
// 有新的客户端连接
sockaddr_in clientAddr;
int addrLen = sizeof(clientAddr);
g_clientSocket = accept(g_listenSocket, (SOCKADDR*)&clientAddr, &addrLen);
if (g_clientSocket != INVALID_SOCKET)
{
// 设置客户端套接字也为非阻塞模式
u_long mode = 1;
ioctlsocket(g_clientSocket, FIONBIO, &mode);
// 将客户端套接字也关联到窗口
WSAAsyncSelect(g_clientSocket, hwnd, WM_SOCKET, FD_READ | FD_CLOSE);
char clientIP[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &clientAddr.sin_addr, clientIP, INET_ADDRSTRLEN);
char msg[100];
sprintf_s(msg, sizeof(msg), "Client connected from %s:%d", clientIP, ntohs(clientAddr.sin_port));
MessageBox(hwnd, msg, "New Connection", MB_OK | MB_ICONINFORMATION);
}
}
else if (event == FD_READ)
{
// 有数据从套接字(客户端)可读
char buffer[1024];
int bytesReceived = recv(eventSocket, buffer, sizeof(buffer) - 1, 0);
if (bytesReceived > 0)
{
buffer[bytesReceived] = '\0';
// 回显数据给客户端
send(eventSocket, buffer, bytesReceived, 0);
// 可选:在本地窗口显示收到的消息
char msg[1100];
sprintf_s(msg, sizeof(msg), "Received and echoed: %s", buffer);
MessageBox(hwnd, msg, "Data Received", MB_OK | MB_ICONASTERISK);
}
else if (bytesReceived == 0)
{
// 客户端正常断开连接
closesocket(eventSocket);
g_clientSocket = INVALID_SOCKET;
MessageBox(hwnd, "Client disconnected.", "Info", MB_OK | MB_ICONINFORMATION);
}
else
{
// 发生错误
if (WSAGetLastError() != WSAEWOULDBLOCK)
{
closesocket(eventSocket);
g_clientSocket = INVALID_SOCKET;
MessageBox(hwnd, "recv() failed!", "Error", MB_OK | MB_ICONERROR);
}
}
}
else if (event == FD_CLOSE)
{
// 套接字关闭
closesocket(eventSocket);
if (eventSocket == g_clientSocket)
{
g_clientSocket = INVALID_SOCKET;
}
MessageBox(hwnd, "Socket closed.", "Info", MB_OK | MB_ICONINFORMATION);
}
break;
}
case WM_PAINT:
{
// 窗口需要重绘时(首次显示或被其他窗口遮挡后)
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// 这里可以绘制窗口内容
TextOut(hdc, 10, 10, "TCP Server Window", 18);
EndPaint(hwnd, &ps);
break;
}
case WM_DESTROY:
{
// 窗口被销毁时,清理资源
if (g_clientSocket != INVALID_SOCKET) closesocket(g_clientSocket);
if (g_listenSocket != INVALID_SOCKET) closesocket(g_listenSocket);
WSACleanup();
PostQuitMessage(0); // 发送退出消息,结束消息循环
break;
}
default:
// 对于我们没有处理的消息,交给系统默认处理
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
第4步:WinMain函数 - 程序入口
这是Windows GUI程序的入口点,负责注册窗口类、创建窗口并启动消息循环。

(图片来源网络,侵删)
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
// 1. 注册窗口类
WNDCLASSEX wc = { 0 };
wc.cbSize = sizeof(WNDCLASSEX);
wc.lpfnWndProc = WndProc; // 指向窗口过程函数
wc.hInstance = hInstance;
wc.lpszClassName = "MySocketWindowClass";
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
if (!RegisterClassEx(&wc))
{
MessageBox(NULL, "Window Registration Failed!", "Error", MB_OK | MB_ICONERROR);
return 1;
}
// 2. 创建窗口
g_hwnd = CreateWindowEx(
0,
"MySocketWindowClass",
"C Socket Window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
NULL, NULL, hInstance, NULL);
if (g_hwnd == NULL)
{
MessageBox(NULL, "Window Creation Failed!", "Error", MB_OK | MB_ICONERROR);
return 1;
}
// 3. 显示窗口
ShowWindow(g_hwnd, nCmdShow);
UpdateWindow(g_hwnd);
// 4. 消息循环
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
如何编译和运行
-
保存代码:将以上所有代码块合并成一个文件,
socket_window.c。 -
使用Visual Studio编译(推荐):
- 创建一个新的 "Windows Desktop Application" 项目。
- 将代码粘贴到主
.c文件中。 - 直接按
F5运行即可,VS会自动链接所需的ws2_32.lib库。
-
使用命令行编译:
-
打开 "Developer Command Prompt for VS" 或 MinGW 终端。
(图片来源网络,侵删) -
运行以下命令:
cl /W4 socket_window.c ws2_32.lib
cl是微软的C编译器。/W4设置较高的警告级别。socket_window.c是你的源文件。ws2_32.lib是必须链接的Winsock库。
-
运行生成的
socket_window.exe。
-
如何测试
- 运行
socket_window.exe程序。 - 会弹出一个消息框提示 "Server started. Waiting for connections..."。
- 打开另一个命令行窗口,使用
telnet或nc(netcat) 作为客户端连接:telnet 127.0.0.1 8888
或者
nc 127.0.0.1 8888
- 连接成功后,在
telnet或nc窗口中输入任何文本,然后按回车。 - 你会看到:
- 服务器窗口会弹出一个消息框,显示 "Received and echoed: [你输入的文本]"。
- 客户端窗口会把你输入的文本显示出来(因为服务器回显了它)。
这个例子完美地结合了Win32 GUI编程和Winsock网络编程,关键点在于:
WSAAsyncSelect:它将网络事件无缝地集成到了Windows的消息驱动模型中。- 非阻塞I/O:确保了主线程(负责GUI响应)不会被网络I/O操作阻塞。
- 窗口过程:作为单一的中心点,统一处理GUI事件和网络事件,使得逻辑清晰。
