C语言如何创建Socket窗口?

99ANYc3cd6
预计阅读时长 34 分钟
位置: 首页 C语言 正文

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

C语言创建socketwindow
(图片来源网络,侵删)
  1. Win32 API:用于创建窗口、处理消息、绘制图形等GUI操作。
  2. Winsock API:用于进行网络编程(创建socket、连接、发送/接收数据)。

下面我将为你提供一个完整的、详细的C语言示例,展示如何创建一个窗口,并在该窗口的消息循环中处理一个简单的TCP套接字服务器。

核心概念

  1. Win32窗口程序:需要一个窗口过程函数来处理所有发送到该窗口的消息(如鼠标点击、键盘输入、窗口关闭等)。
  2. 消息循环:程序的主线程会进入一个循环,不断从消息队列中取出消息,并分发给窗口过程进行处理。
  3. Winsock初始化:在使用任何Winsock函数之前,必须调用 WSAStartup 初始化。
  4. 异步I/O与消息:为了在等待网络数据时不阻塞GUI(导致窗口卡死),我们使用异步套接字(Non-blocking Socket),当套接字上有数据可读或连接状态改变时,Windows系统会向我们的窗口发送特定的Windows消息(如 WM_SOCKET)。
  5. 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程序的入口点,负责注册窗口类、创建窗口并启动消息循环。

C语言创建socketwindow
(图片来源网络,侵删)
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;
}

如何编译和运行

  1. 保存代码:将以上所有代码块合并成一个文件,socket_window.c

  2. 使用Visual Studio编译(推荐)

    • 创建一个新的 "Windows Desktop Application" 项目。
    • 将代码粘贴到主 .c 文件中。
    • 直接按 F5 运行即可,VS会自动链接所需的 ws2_32.lib 库。
  3. 使用命令行编译

    • 打开 "Developer Command Prompt for VS" 或 MinGW 终端。

      C语言创建socketwindow
      (图片来源网络,侵删)
    • 运行以下命令:

      cl /W4 socket_window.c ws2_32.lib
      • cl 是微软的C编译器。
      • /W4 设置较高的警告级别。
      • socket_window.c 是你的源文件。
      • ws2_32.lib 是必须链接的Winsock库。
    • 运行生成的 socket_window.exe

如何测试

  1. 运行 socket_window.exe 程序。
  2. 会弹出一个消息框提示 "Server started. Waiting for connections..."。
  3. 打开另一个命令行窗口,使用 telnetnc (netcat) 作为客户端连接:
    telnet 127.0.0.1 8888

    或者

    nc 127.0.0.1 8888
  4. 连接成功后,在 telnetnc 窗口中输入任何文本,然后按回车。
  5. 你会看到:
    • 服务器窗口会弹出一个消息框,显示 "Received and echoed: [你输入的文本]"。
    • 客户端窗口会把你输入的文本显示出来(因为服务器回显了它)。

这个例子完美地结合了Win32 GUI编程和Winsock网络编程,关键点在于:

  • WSAAsyncSelect:它将网络事件无缝地集成到了Windows的消息驱动模型中。
  • 非阻塞I/O:确保了主线程(负责GUI响应)不会被网络I/O操作阻塞。
  • 窗口过程:作为单一的中心点,统一处理GUI事件和网络事件,使得逻辑清晰。
-- 展开阅读全文 --
头像
织梦微信插件免费智能,真能省心搭建?
« 上一篇 前天
C语言中sizeof struct内存对齐如何计算?
下一篇 » 昨天

相关文章

取消
微信二维码
支付宝二维码

目录[+]