基于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;
}
相关推荐
久绊A1 小时前
网络信息系统的整个生命周期
网络
_PowerShell1 小时前
[ DOS 命令基础 3 ] DOS 命令详解-文件操作相关命令
网络·dos命令入门到精通·dos命令基础·dos命令之文件操作命令详解·文件复制命令详解·文件对比命令详解·文件删除命令详解·文件查找命令详解
_.Switch4 小时前
高级Python自动化运维:容器安全与网络策略的深度解析
运维·网络·python·安全·自动化·devops
qq_254674414 小时前
工作流初始错误 泛微提交流程提示_泛微协同办公平台E-cology8.0版本后台维护手册(11)–系统参数设置
网络
JokerSZ.4 小时前
【基于LSM的ELF文件安全模块设计】参考
运维·网络·安全
小松学前端6 小时前
第六章 7.0 LinkList
java·开发语言·网络
城南vision7 小时前
计算机网络——TCP篇
网络·tcp/ip·计算机网络
Ciderw7 小时前
块存储、文件存储和对象存储详细介绍
网络·数据库·nvme·对象存储·存储·块存储·文件存储
石牌桥网管7 小时前
OpenSSL 生成根证书、中间证书和网站证书
网络协议·https·openssl