×

使用C语言编写一个简单聊天软件

hqy hqy 发表于2025-10-21 21:49:59 浏览37 评论0

抢沙发发表评论

咱们天天用微信、QQ聊天,有没有想过:我打的字是怎么跑到朋友手机上的?今天我就用最基础的C语言,带你亲手做一个超简单的聊天程序,保证你看完直呼:“哦~原来是这样!”

服务端:就像个咖啡厅老板

  • 有个固定店面(IP地址和端口)
  • 每天准时开门营业(一直运行)
  • 负责帮客人传话(转发消息)
  • 同时能招呼好多客人(多用户支持)

客户端:就像来喝咖啡的客人

  • 得知道咖啡厅在哪儿(连接服务器)
  • 可以点单聊天(发送消息)
  • 也能听别人聊天(接收消息)
  • 想走随时能走(断开连接)

简单说吧,没有咖啡厅,大家就没地方聚会;没有客人,咖啡厅开着也没意思。
咱们每个人手里的手机,就是一个客户端;每次发消息,其实都是先发到一个服务器上,再由这个服务器转发给另一个客户端——也就是对方的手机。这样,一次完整的聊天就完成啦。

一次完整的聊天,就是信息在“寄件人 → 邮局 → 收件人”这个路径上成功走了一圈。

第一步,完成服务端的搭建






































































































































































































































































#define _CRT_SECURE_NO_WARNINGS#define _WINSOCK_DEPRECATED_NO_WARNINGS#define WIN32_LEAN_AND_MEAN#include <winsock2.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <windows.h>#include <locale.h>#pragma comment(lib, "ws2_32.lib")#define PORT 8080#define BUFFER_SIZE 1024#define MAX_CLIENTS 10// 彻底解决中文编码问题void SetupConsole() {    // 使用GBK编码(中文Windows默认)    SetConsoleOutputCP(936);    SetConsoleCP(936);    setlocale(LC_ALL, "chs");}int main() {    SetupConsole();    WSADATA wsaData;    SOCKET server_socket, client_socket;    struct sockaddr_in server_addr, client_addr;    int client_addr_len = sizeof(client_addr);    char buffer[BUFFER_SIZE];    fd_set read_fds;    SOCKET client_sockets[MAX_CLIENTS] = { 0 };    char client_names[MAX_CLIENTS][50] = { 0 };    int client_count = 0;    printf("=== 聊天服务器 ===\n");    printf("正在初始化...\n");    // 初始化Winsock    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {        printf("WSAStartup失败: %d\n", WSAGetLastError());        return 1;    }    // 创建服务器socket    server_socket = socket(AF_INET, SOCK_STREAM, 0);    if (server_socket == INVALID_SOCKET) {        printf("创建socket失败: %d\n", WSAGetLastError());        WSACleanup();        return 1;    }    // 设置服务器地址    server_addr.sin_family = AF_INET;    server_addr.sin_addr.s_addr = INADDR_ANY;    server_addr.sin_port = htons(PORT);    // 绑定socket    if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) {        printf("绑定失败: %d\n", WSAGetLastError());        closesocket(server_socket);        WSACleanup();        return 1;    }    // 开始监听    if (listen(server_socket, MAX_CLIENTS) == SOCKET_ERROR) {        printf("监听失败: %d\n", WSAGetLastError());        closesocket(server_socket);        WSACleanup();        return 1;    }    printf("服务器启动成功,监听端口: %d\n", PORT);    printf("等待客户端连接...\n\n");    // 初始化客户端socket数组    for (int i = 0; i < MAX_CLIENTS; i++) {        client_sockets[i] = INVALID_SOCKET;    }    // 主循环    while (1) {        // 清空socket集合        FD_ZERO(&read_fds);        // 添加服务器socket        FD_SET(server_socket, &read_fds);        // 添加客户端sockets        for (int i = 0; i < MAX_CLIENTS; i++) {            if (client_sockets[i] != INVALID_SOCKET) {                FD_SET(client_sockets[i], &read_fds);            }        }        // 设置超时时间        struct timeval timeout;        timeout.tv_sec = 1;        timeout.tv_usec = 0;        // 等待socket活动        int activity = select(0, &read_fds, NULL, NULL, &timeout);        if (activity == SOCKET_ERROR) {            printf("select错误: %d\n", WSAGetLastError());            break;        }        // 检查新连接        if (FD_ISSET(server_socket, &read_fds)) {            client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &client_addr_len);            if (client_socket == INVALID_SOCKET) {                printf("接受连接失败: %d\n", WSAGetLastError());                continue;            }            // 查找空闲位置存放新客户端            int client_index = -1;            for (int i = 0; i < MAX_CLIENTS; i++) {                if (client_sockets[i] == INVALID_SOCKET) {                    client_sockets[i] = client_socket;                    client_index = i;                    client_count++;                    break;                }            }            if (client_index == -1) {                printf("服务器已满,拒绝新连接\n");                const char* msg = "服务器已满,请稍后重试\n";                send(client_socket, msg, (int)strlen(msg), 0);                closesocket(client_socket);                continue;            }            // 接收客户端用户名            int name_len = recv(client_socket, client_names[client_index], sizeof(client_names[client_index]) - 1, 0);            if (name_len > 0) {                client_names[client_index][name_len] = '\0';                // 尝试修复中文用户名乱码                // 如果用户名看起来是乱码,尝试转换编码                int has_chinese = 0;                for (int j = 0; j < name_len; j++) {                    if ((unsigned char)client_names[client_index][j] > 127) {                        has_chinese = 1;                        break;                    }                }                if (has_chinese) {                    // 简单处理:直接使用接收到的数据,假设客户端和服务端编码一致                    // 在实际应用中,这里应该进行编码转换                }            }            else {                strcpy_s(client_names[client_index], sizeof(client_names[client_index]), "匿名用户");            }            printf("新客户端连接: %s (IP: %s)\n", client_names[client_index], inet_ntoa(client_addr.sin_addr));            // 发送欢迎消息            char welcome_msg[BUFFER_SIZE];            _snprintf_s(welcome_msg, sizeof(welcome_msg), _TRUNCATE,                "欢迎 %s 加入聊天室!当前在线用户: %d\n",                client_names[client_index], client_count);            send(client_socket, welcome_msg, (int)strlen(welcome_msg), 0);            // 广播新用户加入消息            char join_msg[BUFFER_SIZE];            _snprintf_s(join_msg, sizeof(join_msg), _TRUNCATE,                "系统: %s 加入了聊天室\n", client_names[client_index]);            for (int i = 0; i < MAX_CLIENTS; i++) {                if (client_sockets[i] != INVALID_SOCKET && i != client_index) {                    send(client_sockets[i], join_msg, (int)strlen(join_msg), 0);                }            }        }        // 检查客户端消息        for (int i = 0; i < MAX_CLIENTS; i++) {            if (client_sockets[i] == INVALID_SOCKET) continue;            if (FD_ISSET(client_sockets[i], &read_fds)) {                int bytes_received = recv(client_sockets[i], buffer, BUFFER_SIZE - 1, 0);                if (bytes_received <= 0) {                    // 客户端断开连接                    printf("客户端断开: %s\n", client_names[i]);                    // 广播离开消息                    char leave_msg[BUFFER_SIZE];                    _snprintf_s(leave_msg, sizeof(leave_msg), _TRUNCATE,                        "系统: %s 离开了聊天室\n", client_names[i]);                    for (int j = 0; j < MAX_CLIENTS; j++) {                        if (client_sockets[j] != INVALID_SOCKET && j != i) {                            send(client_sockets[j], leave_msg, (int)strlen(leave_msg), 0);                        }                    }                    closesocket(client_sockets[i]);                    client_sockets[i] = INVALID_SOCKET;                    client_count--;                    continue;                }                // 处理收到的消息                buffer[bytes_received] = '\0';                // 移除换行符                if (bytes_received > 0 && buffer[bytes_received - 1] == '\n') {                    buffer[bytes_received - 1] = '\0';                }                // 显示消息                printf("%s: %s\n", client_names[i], buffer);                // 检查退出命令                if (strcmp(buffer, "quit") == 0 || strcmp(buffer, "exit") == 0) {                    const char* goodbye = "再见!\n";                    send(client_sockets[i], goodbye, (int)strlen(goodbye), 0);                    closesocket(client_sockets[i]);                    client_sockets[i] = INVALID_SOCKET;                    client_count--;                    continue;                }                // 广播消息给所有其他客户端                char broadcast_msg[BUFFER_SIZE + 100];                _snprintf_s(broadcast_msg, sizeof(broadcast_msg), _TRUNCATE,                    "%s: %s\n", client_names[i], buffer);                for (int j = 0; j < MAX_CLIENTS; j++) {                    if (client_sockets[j] != INVALID_SOCKET && j != i) {                        send(client_sockets[j], broadcast_msg, (int)strlen(broadcast_msg), 0);                    }                }            }        }        // 减少CPU占用        Sleep(10);    }    // 清理资源    for (int i = 0; i < MAX_CLIENTS; i++) {        if (client_sockets[i] != INVALID_SOCKET) {            closesocket(client_sockets[i]);        }    }    closesocket(server_socket);    WSACleanup();    printf("服务器已关闭\n");    return 0;}
图片

重点:这个窗口千万别关!它就是你聊天室的“场地”。

第二步:邀请朋友来玩(运行客户端1)

























































































































































































#define _CRT_SECURE_NO_WARNINGS#define _WINSOCK_DEPRECATED_NO_WARNINGS#define WIN32_LEAN_AND_MEAN#include <winsock2.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <conio.h>#include <windows.h>#include <process.h>#include <locale.h>#pragma comment(lib, "ws2_32.lib")#define PORT 8080#define BUFFER_SIZE 1024// 彻底解决中文编码问题void SetupConsole() {    // 使用GBK编码(中文Windows默认)    SetConsoleOutputCP(936);    SetConsoleCP(936);    setlocale(LC_ALL, "chs");}// 接收消息的线程函数unsigned __stdcall ReceiveThread(void* socket_ptr) {    SOCKET client_socket = *(SOCKET*)socket_ptr;    char buffer[BUFFER_SIZE];    while (1) {        int bytes_received = recv(client_socket, buffer, sizeof(buffer) - 1, 0);        if (bytes_received > 0) {            buffer[bytes_received] = '\0';            printf("%s", buffer);            printf("> ");            fflush(stdout);        }        else if (bytes_received == 0) {            printf("\n服务器连接已断开\n");            break;        }        else {            int error = WSAGetLastError();            if (error == WSAECONNRESET) {                printf("\n连接被服务器重置\n");                break;            }            else if (error != WSAEWOULDBLOCK) {                printf("\n接收错误: %d\n", error);                break;            }        }        Sleep(100);    }    return 0;}// 获取控制台输入,处理中文int GetConsoleInput(char* buffer, int buffer_size) {    if (fgets(buffer, buffer_size, stdin) == NULL) {        return 0;    }    // 移除换行符    int len = (int)strlen(buffer);    if (len > 0 && buffer[len - 1] == '\n') {        buffer[len - 1] = '\0';        len--;    }    return len;}int main() {    SetupConsole();    WSADATA wsaData;    SOCKET client_socket;    struct sockaddr_in server_addr;    char buffer[BUFFER_SIZE];    char username[50];    HANDLE receive_thread;    printf("=== 聊天客户端 ===\n\n");    // 获取用户名 - 使用专门的函数处理    printf("请输入您的用户名: ");    fflush(stdout);    int username_len = GetConsoleInput(username, sizeof(username));    // 默认用户名    if (username_len == 0) {        strcpy_s(username, sizeof(username), "匿名用户");        username_len = (int)strlen(username);    }    printf("正在连接到服务器...\n");    // 初始化Winsock    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {        printf("WSAStartup失败: %d\n", WSAGetLastError());        return 1;    }    // 创建客户端socket    client_socket = socket(AF_INET, SOCK_STREAM, 0);    if (client_socket == INVALID_SOCKET) {        printf("创建socket失败: %d\n", WSAGetLastError());        WSACleanup();        return 1;    }    // 设置服务器地址    server_addr.sin_family = AF_INET;    server_addr.sin_port = htons(PORT);    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 本地服务器    // 连接到服务器    if (connect(client_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) {        printf("连接服务器失败: %d\n", WSAGetLastError());        printf("请确保服务器正在运行\n");        closesocket(client_socket);        WSACleanup();        return 1;    }    // 立即发送用户名到服务器 - 这是关键修复    // 在连接建立后立即发送用户名,避免与其他数据混淆    send(client_socket, username, (int)strlen(username), 0);    printf("连接成功!\n");    printf("输入消息开始聊天,输入 'quit' 退出\n\n");    // 设置socket为非阻塞模式    u_long mode = 1;    ioctlsocket(client_socket, FIONBIO, &mode);    // 创建接收线程    receive_thread = (HANDLE)_beginthreadex(NULL, 0, ReceiveThread, &client_socket, 0, NULL);    if (receive_thread == NULL) {        printf("创建接收线程失败\n");        closesocket(client_socket);        WSACleanup();        return 1;    }    // 主循环 - 发送消息    while (1) {        printf("> ");        fflush(stdout);        int input_len = GetConsoleInput(buffer, sizeof(buffer));        if (input_len == 0) {            continue;        }        // 检查退出命令        if (strcmp(buffer, "quit") == 0 || strcmp(buffer, "exit") == 0) {            send(client_socket, buffer, (int)strlen(buffer), 0);            break;        }        // 发送消息        if (send(client_socket, buffer, (int)strlen(buffer), 0) == SOCKET_ERROR) {            printf("发送失败: %d\n", WSAGetLastError());            break;        }    }    // 清理资源    WaitForSingleObject(receive_thread, 2000);    CloseHandle(receive_thread);    closesocket(client_socket);    WSACleanup();    printf("客户端已退出\n");    return 0;}
图片
这样张三同学就进入了聊天室,如果李四同学也想进入聊天室,那就在打开一个客户端就行。
图片
这样张三李四都进入了聊天室。然后他们就可以聊天了!
以下展示一下聊天过程!
原来我们发的每一条消息,都会在服务器的“中转站”里经过。所以,在数字世界里聊天,就像寄出一张会被许多人看到的明信片,记得要为自己写下的内容负责哦。


打赏

本文链接:https://kinber.cn/post/5740.html 转载需授权!

分享到:


推荐本站淘宝优惠价购买喜欢的宝贝:

image.png

 您阅读本篇文章共花了: 

群贤毕至

访客