基于linux虚拟机的TCP网络聊天室

Ubuntu 22.04 LTS

VMware workstation 17

代码分为服务器server和客户端client两个文件,可以多机连接(但是需要配置虚拟机,后面会简要说明),编译后在终端运行:

服务器    ./server   

客户端    ./client “服务器地址”,这个服务器地址会在服务器程序运行后显示在终端上

代码如下:

/*server*/
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <errno.h> 
#include <signal.h>
#include <string.h>
#include <netdb.h> 
#include <unistd.h>
#include <sys/types.h> 
#include <ifaddrs.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>

#define BUFF_SIZE 128       // 缓冲区大小
#define PORT   3333         // 端口号
#define MAX_CLIENTS 5       // 最大客户端数量

int clientSockets[MAX_CLIENTS];
int clientCount = 0;
pthread_mutex_t clientMutex = PTHREAD_MUTEX_INITIALIZER;

struct ClientInfo {
    int clientSocket;
    struct sockaddr_in clientAddr;
};

void error_handler(const char* msg)
{
    perror(msg);
    exit(1);
}

void* handle_client(void* arg)
{
    struct ClientInfo* pClientInfo = (struct ClientInfo*)arg;
    int clientSocket = pClientInfo->clientSocket;
    struct sockaddr_in clientAddr = pClientInfo->clientAddr;
    free(pClientInfo);  // 释放分配的内存

    char buffer[BUFF_SIZE];
    int str_len;
    char message[2 * BUFF_SIZE];

    while ((str_len = read(clientSocket, buffer, BUFF_SIZE - 1)) > 0)
    {
        buffer[str_len] = '\0';  // 确保字符串正确终止
        memset(message, 0, sizeof(message));  // 清空message
        snprintf(message, sizeof(message), "--------------------------------\n>>Client/%s/%d :\n>>%s", 
                inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port), buffer);
        
        printf("%s", message);

        pthread_mutex_lock(&clientMutex);
        for (int i = 0; i < clientCount; ++i)
        {
            if (clientSockets[i] != clientSocket)
            {
                write(clientSockets[i], message, strlen(message));
            }
        }
        pthread_mutex_unlock(&clientMutex);
    }

    close(clientSocket);

    pthread_mutex_lock(&clientMutex);
    for (int i = 0; i < clientCount; ++i)
    {
        if (clientSockets[i] == clientSocket)
        {
            clientSockets[i] = clientSockets[clientCount - 1];
            --clientCount;
            break;
        }
    }
    pthread_mutex_unlock(&clientMutex);

    printf("✕Client/%s/%d disconnected.\n", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));
    return NULL;
}

int main(int argc, char** argv)
{
    printf("\033[H\033[J"); // ANSI转义序列清除屏幕内容
    /***获取ip**************************************************/
    struct ifaddrs *ifap, *ifa;
    struct sockaddr_in *sa;
    char *addr;

    if (getifaddrs(&ifap) == -1) {
        perror("getifaddrs");
        return -1;
    }

    for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
        if (ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET) {
            sa = (struct sockaddr_in *) ifa->ifa_addr;
            addr = inet_ntoa(sa->sin_addr);
            printf("-->%s: %s\n", ifa->ifa_name, addr);
        }
    }
    freeifaddrs(ifap);
    /*********************************************************/

    int serverSocket;
    struct sockaddr_in serverAddr, clientAddr;
    socklen_t addrSize;

    memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAddr.sin_port = htons(PORT);

    serverSocket = socket(PF_INET, SOCK_STREAM, 0);
    if (serverSocket == -1) error_handler("!Failed to create socket");

    if (bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == -1)
    {
        close(serverSocket);
        error_handler("!Failed to bind server address");
    }

    if (listen(serverSocket, 5) == -1)
    {
        close(serverSocket);
        error_handler("!Failed to listen client");
    }

    printf("Init server...\nWaiting for connections...\n");

    while (1)
    {
        addrSize = sizeof(clientAddr);
        int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &addrSize);

        if (clientSocket == -1)
            continue;

        printf("✓Client/%s/%d connected.\n", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));

        pthread_mutex_lock(&clientMutex);
        if (clientCount < MAX_CLIENTS)
        {
            clientSockets[clientCount++] = clientSocket;
            pthread_mutex_unlock(&clientMutex);

            // 创建并填充ClientInfo结构体
            struct ClientInfo* pClientInfo = malloc(sizeof(struct ClientInfo));
            pClientInfo->clientSocket = clientSocket;
            pClientInfo->clientAddr = clientAddr;

            pthread_t t_id;
            pthread_create(&t_id, NULL, handle_client, (void*)pClientInfo);
            pthread_detach(t_id);
        }
        else
        {
            pthread_mutex_unlock(&clientMutex);
            close(clientSocket);
            printf("Maximum clients reached. Connection rejected.\n");
        }
    }

    close(serverSocket);
    return 0;
}
 

/*client*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdbool.h>
#include <sys/wait.h> // 包含 waitpid 函数声明的头文件

#define BUFF_SIZE  128
#define PORT       3333

void readRoutine(int sock, char* buf);
void writeRoutine(int sock, char* buf);

int main(int argc, char *argv[])
{   
    printf("\033[H\033[J"); // ANSI转义序列清除屏幕内容
    if (argc != 2) 
    { 
        fprintf(stderr, "Usage: %s hostname \n", argv[0]); 
        exit(1); 
    } 
    
    struct hostent *host;
    if ((host = gethostbyname(argv[1])) == NULL) 
    { 
        fprintf(stderr, "Gethostname error\n"); 
        exit(1); 
    }

    char buffer[BUFF_SIZE];
    struct sockaddr_in serverAddr;               
    memset(&serverAddr, 0, sizeof(serverAddr));   
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = *((unsigned long *)host->h_addr_list[0]);
    serverAddr.sin_port = htons(PORT);

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);

    if (sockfd == -1)
    {
        perror("!Failed to create socket");
        exit(1);
    }

    if (connect(sockfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == -1)
    {
        perror("!Failed to connect to server");
        exit(1);
    }

    printf("✓Server connected.(Type '/quit' to quit)\n");

    pid_t pid = fork();

    if (pid < 0)
    {
        perror("!Fork failed");
        exit(1);
    }
    else if (pid == 0)
    {
        // Child process handles sending messages
        writeRoutine(sockfd, buffer);
        exit(0); // 子进程结束时退出
    }
    else
    {
        // Parent process handles receiving messages
        readRoutine(sockfd, buffer);
         // 等待子进程结束
        int status;
        waitpid(pid, &status, 0);
    }

    close(sockfd);

    return 0;
}

void readRoutine(int sock, char* buf)
{
    while (true)
    {
        memset(buf, 0, BUFF_SIZE);

        int str_len = read(sock, buf, BUFF_SIZE);

        if (str_len <= 0)    // Handle connection closed or error
        {
            printf("\n✕Server disconnected.\n");
            return;
        }
        printf("\033[2K\033[1G");
        printf("\033[F\033[2K\033[1G");
        printf("\033[F\033[2K\033[1G");
        printf("%s", buf);
        printf("--------------------------------\n<<You :\n<<");
        fflush(stdout);
    }
}

void writeRoutine(int sock, char* buf)
{
    while (true)
    {
        fputs("--------------------------------\n<<You :\n<<", stdout);
        //printf("\033[F\033[2K\033[1G");
        fgets(buf, BUFF_SIZE, stdin);
        if (strncmp(buf, "/QUIT\n", 2) == 0 || strncmp(buf, "/quit\n", 2) == 0)
        {
            shutdown(sock, SHUT_WR);
            return;
        }

        write(sock, buf, strlen(buf));
    }
}
 

配置网络,需要所有电脑的外部主机连接到同一个热点,不能是校园网,然后虚拟机的编辑选项,选择虚拟网络编辑器,然后点击更改设置,要管理员权限,然后选择net0,改成桥接模式,并且改成你的网卡,比如intel的网卡或者realtek等,具体可以点开当前连接的网看是什么硬件。然后右键虚拟机,点击设置,将网络适配器改成桥接,复制物理地址,等待即可,最后观察虚拟机的ipv4和电脑的ipv4是不是相同段,也就是最后一位不一样。

具体操作看我的b站视频:

基于linux虚拟机的TCP网络聊天室_哔哩哔哩_bilibili

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值