用纯C语言写的一个网络聊天室

纯C语言实现网络聊天室的核心是基于TCP/IP协议的套接字(Socket)编程,仅使用标准C库和操作系统提供的底层Socket接口(如Linux的 sys/socket.h )。以下是实现的核心思路、关键模块及简化代码示例。

 

一、核心原理与架构

 

网络聊天室本质是“服务器-客户端”模型,核心依赖Socket通信和多线程/多进程处理并发连接。

 

- 服务器:绑定固定IP和端口,监听客户端连接,为每个客户端创建独立线程处理消息收发,并将消息转发给所有在线客户端(群聊逻辑)。

- 客户端:连接服务器,创建两个线程(一个收消息、一个发消息),实现“边发边收”的实时交互。

 

关键概念:

 

- Socket:网络通信的“端点”,类似电话的“听筒+话筒”,用于收发数据。

- TCP协议:可靠的面向连接协议,确保消息不丢失、不混乱,适合聊天室场景。

- 多线程:服务器用主线程监听连接,子线程处理单个客户端;客户端用线程分离“收”和“发”的逻辑。

 

二、环境依赖

 

仅支持Linux/macOS(依赖POSIX标准的Socket接口和线程库 pthread ),Windows需用Winsock但题目禁止,故不支持。编译时需链接线程库(加 -pthread 参数)。

 

三、核心模块实现(简化代码)

 

1. 公共头文件(common.h)

 

定义常量和函数声明,统一服务器IP、端口等配置。

 

#ifndef COMMON_H

#define COMMON_H

 

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include <pthread.h>

 

#define SERVER_IP "127.0.0.1" // 服务器IP(本地测试用)

#define SERVER_PORT 8888 // 服务器端口

#define BUF_SIZE 1024 // 消息缓冲区大小

 

// 服务器:客户端信息结构体(含Socket和线程ID)

typedef struct {

    int sockfd;

    pthread_t tid;

} ClientInfo;

 

// 服务器:全局客户端数组(简化版,实际需动态管理)

ClientInfo clients[1024];

int client_count = 0;

pthread_mutex_t mutex; // 保护客户端数组的互斥锁

 

// 函数声明

void *server_handle_client(void *arg); // 服务器处理单个客户端

void *client_recv_msg(void *arg); // 客户端接收消息

void client_send_msg(int sockfd); // 客户端发送消息

 

#endif

 

 

2. 服务器端(server.c)

 

核心逻辑:创建Socket → 绑定端口 → 监听连接 → 接受客户端并创建线程处理。

 

#include "common.h"

 

// 转发消息给所有在线客户端(群聊核心)

void broadcast_msg(int sender_fd, const char *msg) {

    pthread_mutex_lock(&mutex); // 加锁,避免并发修改客户端数组

    for (int i = 0; i < client_count; i++) {

        if (clients[i].sockfd != sender_fd) { // 不发给自己

            send(clients[i].sockfd, msg, strlen(msg), 0);

        }

    }

    pthread_mutex_unlock(&mutex);

}

 

// 处理单个客户端的消息(子线程函数)

void *server_handle_client(void *arg) {

    int sockfd = *(int *)arg;

    char buf[BUF_SIZE];

    char msg[BUF_SIZE + 32]; // 附加发送者标识

    int n;

 

    // 1. 接收客户端的用户名

    memset(buf, 0, BUF_SIZE);

    if ((n = recv(sockfd, buf, BUF_SIZE, 0)) <= 0) {

        printf("Client disconnected (no username)\n");

        close(sockfd);

        free(arg);

        return NULL;

    }

    char username[64];

    strncpy(username, buf, sizeof(username)-1);

    printf("New client connected: %s (sockfd: %d)\n", username, sockfd);

 

    // 2. 循环接收消息并转发

    while (1) {

        memset(buf, 0, BUF_SIZE);

        n = recv(sockfd, buf, BUF_SIZE, 0);

        if (n <= 0) { // 客户端断开

            printf("Client disconnected: %s\n", username);

            break;

        }

        // 拼接消息格式:[用户名] 消息内容

        snprintf(msg, sizeof(msg), "[%s] %s", username, buf);

        printf("%s", msg); // 服务器打印日志

        broadcast_msg(sockfd, msg); // 转发给所有人

    }

 

    // 3. 清理:从客户端数组移除(简化版,实际需遍历删除)

    pthread_mutex_lock(&mutex);

    for (int i = 0; i < client_count; i++) {

        if (clients[i].sockfd == sockfd) {

            clients[i] = clients[--client_count]; // 用最后一个覆盖

            break;

        }

    }

    pthread_mutex_unlock(&mutex);

 

    close(sockfd);

    free(arg);

    return NULL;

}

 

int main() {

    int server_fd, *client_sockfd;

    struct sockaddr_in server_addr, client_addr;

    socklen_t client_addr_len = sizeof(client_addr);

 

    // 1. 初始化互斥锁

    pthread_mutex_init(&mutex, NULL);

 

    // 2. 创建TCP Socket(AF_INET:IPv4,SOCK_STREAM:TCP,0:默认协议)

    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {

        perror("socket create failed");

        exit(1);

    }

 

    // 3. 设置Socket选项(允许端口复用,避免重启服务器报错)

    int opt = 1;

    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

 

    // 4. 绑定IP和端口

    memset(&server_addr, 0, sizeof(server_addr));

    server_addr.sin_family = AF_INET;

    server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听所有网卡

    server_addr.sin_port = htons(SERVER_PORT); // 端口转换为网络字节序

    if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {

        perror("bind failed");

        close(server_fd);

        exit(1);

    }

 

    // 5. 开始监听(backlog=10:最多缓存10个未处理连接)

    if (listen(server_fd, 10) == -1) {

        perror("listen failed");

        close(server_fd);

        exit(1);

    }

    printf("Server started, listening on %s:%d...\n", SERVER_IP, SERVER_PORT);

 

    // 6. 循环接受客户端连接

    while (1) {

        // 接受连接,返回客户端专属Socket

        client_sockfd = malloc(sizeof(int));

        *client_sockfd = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len);

        if (*client_sockfd == -1) {

            perror("accept failed");

            free(client_sockfd);

            continue;

        }

 

        // 7. 将客户端加入数组(加锁保护)

        pthread_mutex_lock(&mutex);

        clients[client_count++].sockfd = *client_sockfd;

        pthread_mutex_unlock(&mutex);

 

        // 8. 创建线程处理该客户端

        pthread_t tid;

        if (pthread_create(&tid, NULL, server_handle_client, (void *)client_sockfd) != 0) {

            perror("pthread create failed");

            close(*client_sockfd);

            free(client_sockfd);

            pthread_mutex_lock(&mutex);

            client_count--;

            pthread_mutex_unlock(&mutex);

        }

        // 分离线程(无需主线程等待,线程结束后自动释放资源)

        pthread_detach(tid);

    }

 

    // 理论上不会执行到这里

    pthread_mutex_destroy(&mutex);

    close(server_fd);

    return 0;

}

 

 

3. 客户端(client.c)

 

核心逻辑:创建Socket → 连接服务器 → 输入用户名 → 启动收消息线程 → 循环发送消息。

 

#include "common.h"

 

// 接收服务器消息的线程(后台运行,实时显示)

void *client_recv_msg(void *arg) {

    int sockfd = *(int *)arg;

    char buf[BUF_SIZE];

    int n;

 

    while (1) {

        memset(buf, 0, BUF_SIZE);

        n = recv(sockfd, buf, BUF_SIZE, 0);

        if (n <= 0) { // 服务器断开或出错

            printf("\nServer disconnected, exit.\n");

            exit(0);

        }

        printf("%s", buf); // 直接打印收到的消息(含其他用户的发言)

    }

}

 

// 发送消息到服务器(主线程处理,读取用户输入)

void client_send_msg(int sockfd) {

    char buf[BUF_SIZE];

    while (1) {

        memset(buf, 0, BUF_SIZE);

        // 读取用户输入(fgets会保留换行符,无需额外加)

        if (fgets(buf, BUF_SIZE, stdin) == NULL) {

            printf("Input error, exit.\n");

            break;

        }

        // 发送消息到服务器

        if (send(sockfd, buf, strlen(buf), 0) == -1) {

            perror("send failed");

            break;

        }

    }

}

 

int main() {

    int sockfd;

    struct sockaddr_in server_addr;

    char username[64];

 

    // 1. 创建TCP Socket

    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {

        perror("socket create failed");

        exit(1);

    }

 

    // 2. 填写服务器地址信息

    memset(&server_addr, 0, sizeof(server_addr));

    server_addr.sin_family = AF_INET;

    server_addr.sin_addr.s_addr = inet_addr(SERVER_IP); // 服务器IP转换

    server_addr.sin_port = htons(SERVER_PORT); // 服务器端口

 

    // 3. 连接服务器

    if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {

        perror("connect failed (server not running?)");

        close(sockfd);

        exit(1);

    }

    printf("Connected to server %s:%d\n", SERVER_IP, SERVER_PORT);

 

    // 4. 输入用户名并发送给服务器

    printf("Please enter your username: ");

    if (fgets(username, sizeof(username), stdin) == NULL) {

        printf("Invalid username.\n");

        close(sockfd);

        exit(1);

    }

    // 去掉用户名后的换行符(fgets会读入)

    username[strcspn(username, "\n")] = '\0';

    send(sockfd, username, strlen(username), 0);

    printf("Welcome %s! Type your message (enter to send):\n", username);

 

    // 5. 创建线程接收服务器消息(后台运行)

    pthread_t tid;

    if (pthread_create(&tid, NULL, client_recv_msg, (void *)&sockfd) != 0) {

        perror("pthread create failed");

        close(sockfd);

        exit(1);

    }

    pthread_detach(tid); // 分离线程

 

    // 6. 主线程处理发送消息

    client_send_msg(sockfd);

 

    // 清理

    close(sockfd);

    return 0;

}

 

 

四、编译与运行

 

1. 编译服务器: gcc server.c -o server -pthread 

2. 编译客户端: gcc client.c -o client -pthread 

3. 运行:

- 先启动服务器: ./server 

- 再启动多个客户端(新终端): ./client 

- 输入用户名后即可群聊,消息会同步给所有在线客户端。

 

五、简化说明与优化方向

 

简化点:

 

- 客户端数组用固定大小(1024),实际需用链表/动态数组+互斥锁管理。

- 无异常处理(如用户强制退出、网络中断),无私聊功能。

- 未做消息长度校验,超过 BUF_SIZE 会截断。

 

优化方向:

 

1. 用I/O多路复用( select / poll / epoll )替代多线程,减少资源消耗。

2. 实现客户端动态管理(链表+互斥锁/信号量)。

3. 增加私聊(指定用户名发送)、消息历史、在线用户列表等功能。

4. 加入消息校验(如结尾标识),避免粘包问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值