基于UDP的网络聊天室(多线程实现收和发消息)

要求:1.有新用户登录,其他在线的用户可以收到登录信息

2.有用户群聊,其他在线的用户可以收到群聊信息

3.有用户退出,其他在线的用户可以收到退出信息

4.服务器可以发送系统信息

效果图:

service.c

cpp 复制代码
#include <head.h>
typedef struct _MSG
{
    char type; // 类型  'L' 登录  'C' 群聊  'Q' 退出
    char name[32];
    char txt[128];
} msg_t;
typedef struct _NODE
{
    struct sockaddr_in clientaddr;
    struct _NODE *next;
} node_t;
void create_node(node_t **p);
void do_login(node_t *phead, msg_t msg, int sockfd, struct sockaddr_in clientaddr);
void do_chat(node_t *phead, msg_t msg, int sockfd, struct sockaddr_in clientaddr);
void do_quit(node_t *phead, msg_t msg, int sockfd, struct sockaddr_in clientaddr);

int main(int argc, const char *argv[])
{
    // 入参合理性检查
    if (argc != 3)
    {
        printf("usage error:%s <ip> <port>...\n", argv[0]);
        exit(-1);
    }
    int sockfd = 0;
    // 创建套接字
    if (-1 == (sockfd = socket(AF_INET, SOCK_DGRAM, 0)))
    {
        perror("socket error");
        exit(-1);
    }
    // 填充服务器网络信息结构体
    struct sockaddr_in serveraddr;
    socklen_t serveraddr_len = sizeof(serveraddr);
    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    serveraddr.sin_port = htons(atoi(argv[2]));
    struct sockaddr_in clientaddr;
    socklen_t clientaddr_len = sizeof(clientaddr);
    // 绑定
    if (-1 == (bind(sockfd, (struct sockaddr *)&serveraddr, serveraddr_len)))
    {
        perror("bind error");
        exit(-1);
    }
    node_t *phead = NULL;
    create_node(&phead);

    msg_t msg;

    // 收发数据
    char buff[128] = {0};
    pid_t pid = 0;
    pid = fork();
    if (pid == -1)
    {
        perror("fork error");
        exit(-1);
    }
    else if (pid == 0)
    {
        // 子进程用于接收数据
        while (1)
        {
            memset(&msg, 0, sizeof(msg));
            if (-1 == recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&clientaddr, &clientaddr_len))
            {
                perror("recvfrom error");
                exit(-1);
            }
            printf("%s:%s\n", msg.name, msg.txt);
            switch (msg.type)
            {
            case 'L':
                do_login(phead, msg, sockfd, clientaddr);
                break;
            case 'C':
                do_chat(phead, msg, sockfd, clientaddr);
                break;
            case 'Q':
                do_quit(phead, msg, sockfd, clientaddr);
                break;
            }
        }
    }
    else if (pid > 0)
    {
        // 父进程用于发送数据
        // 把父进程当做一个客户端 以群聊的方式 把系统消息发给子进程
        strcpy(msg.name, "server");
        msg.type = 'C';
        while (1)
        {
            memset(msg.txt, 0, 128);
            fgets(msg.txt, sizeof(msg.txt), stdin);
            msg.txt[strlen(msg.txt) - 1] = '\0';
            if (-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, serveraddr_len))
            {
                perror("sento error");
                exit(-1);
            }
        }
    }

    close(sockfd);
    return 0;
}

void create_node(node_t **p)
{
    *p = (node_t *)malloc(sizeof(node_t));
    memset(*p, 0, sizeof(node_t));
}
// 登录操作函数
void do_login(node_t *phead, msg_t msg, int sockfd, struct sockaddr_in clientaddr)
{
    // 遍历链表 当前在线的所有人发"***加入了群聊"的消息
    node_t *ptemp = phead;
    while (ptemp->next != NULL)
    {
        ptemp = ptemp->next;
        if (-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&(ptemp->clientaddr), sizeof(ptemp->clientaddr)))
        {
            perror("sento error");
            exit(-1);
        }
    }
    // 把新加入的群聊客户端网络信息结构体加入到链表中
    node_t *pnew = NULL;
    create_node(&pnew);
    pnew->clientaddr = clientaddr;
    pnew->next = phead->next;
    phead->next = pnew;
    return;
}
void do_chat(node_t *phead, msg_t msg, int sockfd, struct sockaddr_in clientaddr)
{
    // 遍历链表 将群聊的消息 发给除了自己之外的所有人
    node_t *ptemp = phead;
    while (ptemp->next != NULL)
    {
        ptemp = ptemp->next;
        if (memcmp(&clientaddr, &(ptemp->clientaddr), sizeof(clientaddr)) != 0)
        {
            if (-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&(ptemp->clientaddr), sizeof(ptemp->clientaddr)))
            {
                perror("sento error");
                exit(-1);
            }
        }
    }
    return;
}
void do_quit(node_t *phead, msg_t msg, int sockfd, struct sockaddr_in clientaddr)
{
    // 把 xxx 退出群聊的消息 发给在线的除自己的所有人  并且将自己在链表中删除
    node_t *ptemp = phead;
    while (ptemp->next != NULL)
    {
        if (memcmp(&clientaddr, &(ptemp->next->clientaddr), sizeof(clientaddr)) != 0)
        {
            if (-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&(ptemp->next->clientaddr), sizeof(ptemp->next->clientaddr)))
            {
                perror("sento error");
                exit(-1);
            }
            ptemp = ptemp->next;
        }
        else
        {
            node_t *pdel = ptemp->next;
            ptemp->next = pdel->next;
            free(pdel);
            pdel = NULL;
        }
    }
    return;
}

client.c

cpp 复制代码
#include <head.h>
typedef struct _MSG
{
    char type; // 类型  'L' 登录  'C' 群聊  'Q' 退出
    char name[32];
    char txt[128];
} msg_t;

int main(int argc, const char *argv[])
{
    // 入参合理性检查
    if (argc != 3)
    {
        printf("usage error:%s <ip> <port>...\n", argv[0]);
        exit(-1);
    }
    int sockfd = 0;
    // 创建套接字
    if (-1 == (sockfd = socket(AF_INET, SOCK_DGRAM, 0)))
    {
        perror("socket error");
        exit(-1);
    }
    // 填充服务器网络信息结构体
    struct sockaddr_in serveraddr;
    socklen_t serveraddr_len = sizeof(serveraddr);
    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    serveraddr.sin_port = htons(atoi(argv[2]));
    msg_t msg;
    memset(&msg, 0, sizeof(msg));

    printf("请输入用户名:");
    fgets(msg.name, sizeof(msg.name), stdin);
    msg.name[strlen(msg.name) - 1] = '\0';
    msg.type = 'L';
    strcpy(msg.txt, "加入群聊");
    if (-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, serveraddr_len))
    {
        perror("sendto error");
        exit(-1);
    }
    // 收发数据
    char buff[128] = {0};
    pid_t pid;
    pid = fork();
    if (pid == -1)
    {
        perror("fork error");
        exit(-1);
    }
    else if (pid == 0)
    {
        // 子进程用于接收数据
        while (1)
        {
            memset(&msg, 0, sizeof(msg));
            if (-1 == recvfrom(sockfd, &msg, sizeof(msg), 0, NULL, NULL))
            {
                perror("recvfrom error");
                exit(-1);
            }
            printf("%s : %s\n", msg.name, msg.txt);
        }
    }
    else if(pid>0)
    {
        // //父进程 在终端获取数据 发给服务器
        while (1)
        {
            memset(msg.txt, 0, sizeof(msg.txt));
            fgets(msg.txt, 128,stdin);
            msg.txt[strlen(msg.txt) - 1] = '\0';
            if (strcmp(msg.txt, "quit") == 0)
            {
                msg.type = 'Q';
                strcpy(msg.txt, "退出群聊");
            }
            else
            {
                msg.type = 'C';
            }
            if (-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, serveraddr_len))
            {
                perror("sendto error");
                exit(-1);
            }
            if ('Q' == msg.type)
            {
                // 父进程退出之前先给子进程发信号 杀死子进程
                kill(pid, SIGKILL);
                wait(NULL);
                break;
            }
        }
    }
    close(sockfd);
    return 0;
}
相关推荐
小鼠米奇22 分钟前
13.什么是Proxy?
网络·安全·web安全
都适、隶仁ミ32 分钟前
SQL注入工具Sqlmap
linux·网络·数据库·sql·安全·网络安全·系统安全
2401_857622661 小时前
如何在Laravel中实现数据验证:确保数据准确性的最佳实践
前端·网络·laravel
andylauren1 小时前
Hi3861 OpenHarmony嵌入式应用入门--UDP Server
udp·openharmony
网安加社区2 小时前
行业洞察 | 2024应用程序安全领域现状报告
网络·安全·网络安全
&木头人&2 小时前
JavaScript如何声明json对象
javascript·udp·json
念晚9173 小时前
Linux——/etc/passwd文件含义,grep,cut
linux·运维·服务器·前端·网络·数据库
DHDN——19973 小时前
C语言 实现socket服务器客户端通信
服务器·c语言·网络
蓝桉8028 小时前
考研计算机网络(第二章 物理层3)
网络·计算机网络·考研
Monster✺◟(∗❛ัᴗ❛ั∗)◞✺9 小时前
firewalld(7)NAT、端口转发
linux·运维·服务器·网络·iptables·nat·firewalld