基于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;
}
相关推荐
WIN-U61 小时前
新版华三H3C交换机配置NTP时钟步骤 示例(命令及WEB配置)
网络协议·tcp/ip·http
爱学习的小囧1 小时前
ESXi 8.0 无法选择分区方式 小白级详细解决办法
运维·服务器·网络·虚拟化·esxi8.0
F1FJJ1 小时前
什么是 Shield CLI?视频讲解:一条命令,可浏览器远程访问一切内部服务(RDP/VNC/SSH/数据库等)
运维·网络·数据库·网络协议·ssh
南湖北漠1 小时前
听说拍照的人会拿相似的鱼皮豆代替野生鹌鹑蛋拍照(防原创)
网络·人工智能·计算机网络·生活
一个有温度的技术博主2 小时前
网安实验系列四:信息收集-旁站、C段信息
网络
木下~learning2 小时前
对于Linux中等待队列和工作队列的讲解和使用|RK3399
linux·c语言·网络·模块化编程·工作队列·等待队列
F1FJJ2 小时前
Shield CLI 命令全解析:15 个命令覆盖所有远程访问场景
网络·数据库·网络协议·容器·开源软件
攻城狮在此2 小时前
华为汇聚交换机DHCP中继配置
网络·华为
婷婷_1722 小时前
【PCIe验证每日学习·阶段复盘01】Day1~Day7 纯理论深度复盘
网络·程序人生·芯片·每日学习·pcie 验证·ic 验证·pcie学习
Shepherd06193 小时前
【IT 实战】Apache 反向代理 UniFi Controller 的终极指北(解决白屏、502、400 错误)
运维·网络·apache·it·unifi