c语言 多进程实现基于UDP的网络群聊聊天室

功能

有新用户登录,其他在线的用户可以收到登录信息

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

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

服务器可以发送系统信息

流程图如下:

c语言 多进程实现基于UDP的网络群聊聊天室_第1张图片

 

提示:

客户端登录之后,为了实现一边发送数据一边接收数据,可以使用多进程或者多线程

服务器既可以发送系统信息,又可以接收客户端信息并处理,可以使用多进程或者多线程

服务器需要给多个用户发送数据,所以需要保存每一个用户的信息,使用链表来保存数据传输的时候要定义结构体,结构体中包含操作码、用户名以及数据。

代码如下:

        服务器代码

//服务器代码
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define ERRLOG(msg)                                        \
    do {                                                   \
        printf("%s %s %d:", __FILE__, __func__, __LINE__); \
        perror(msg);                                       \
        exit(-1);                                          \
    } while (0)

typedef struct _MSG {
    char code; //操作码 ('L' 登录),('C' 群聊),('Q' 退出)
    char name[32];
    char txt[128];
} msg_t;
typedef struct _NODE {
    struct sockaddr_in clientaddr; //客户端网络信息结构体
    struct _NODE* next; //指针域
} node_t;

int create_node(node_t **p);
int do_login(struct sockaddr_in clientaddr, node_t* phead, int sockfd, msg_t msg);
int do_chat(struct sockaddr_in clientaddr, node_t* phead, int sockfd, msg_t msg);
int do_quit(struct sockaddr_in clientaddr, node_t* phead, int sockfd, msg_t msg);
int main(int argc, char const* argv[])
{
    //入参合理性检查
    if (3 != argc) {
        printf("Usage : %s  \n", argv[0]);
        return -1;
    }

    // 1.创建套接字
    int sockfd = 0;
    if (-1 == (sockfd = socket(AF_INET, SOCK_DGRAM, 0))) {
        ERRLOG("socket error");
    }
    // 2.填充服务器网络信息结构体
    struct sockaddr_in serveraddr;
    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    socklen_t serveraddr_len = sizeof(serveraddr);

    //绑定
    if (-1 == bind(sockfd, (struct sockaddr*)&serveraddr, serveraddr_len)) {
        ERRLOG("bind error");
    }

    //用来保存客户端网络信息结构体
    struct sockaddr_in clientaddr;
    socklen_t clientaddr_len = sizeof(clientaddr);

    msg_t msg;

    pid_t pid = 0;
    if (-1 == (pid = fork())) {
        ERRLOG("fork error");
    } else if (0 == pid) {
        //子进程,用来接收数据并处理
        //创建用来保存客户端网络信息结构体的链表
        node_t *phead = NULL;
        create_node(&phead);
        phead->next =NULL;
        printf("聊天室已创建,等待用户加入群聊!!!\n");
        while(1) {
            memset(&msg, 0, sizeof(msg));
            memset(&clientaddr, 0, sizeof(clientaddr));
            if (-1 == recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr*)&clientaddr, &clientaddr_len)) {
                ERRLOG("recvfrom error");
            }
            printf("[%s]: [%s]\n", msg.name, msg.txt);
            switch (msg.code) {
            case 'L':
                do_login(clientaddr, phead, sockfd,msg);
                break;
            case 'C':
                do_chat(clientaddr, phead, sockfd, msg);
                break;
            case 'Q':
                do_quit(clientaddr, phead, sockfd,  msg);
                break;
            }
        }
    } else if (0 < pid) {
        //父进程,用来发送系统消息
        //把父进程当作一个客户端,以群聊的方式发送消息给所有人(系统消息)
        msg.code = 'C';
        strcpy(msg.name, "系统");
        while (1)
        {
            fgets(msg.txt, 128, stdin);
            msg.txt[strlen(msg.txt) - 1] = '\0';
            if (-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr*)&serveraddr, serveraddr_len)) {
                ERRLOG("sendto error");
            }
        }
    }

    close(sockfd);

        return 0;
}
//创建链表节点的函数
int create_node(node_t** p)
{
    *p = (node_t*)malloc(sizeof(node_t));
    if (NULL == *p || NULL == p) {
        ERRLOG("malloc error");
    }
}
//登录的函数
int do_login(struct sockaddr_in clientaddr, node_t* phead, int sockfd, msg_t msg)
{
    //先遍历链表,将xxxx登录的消息 转发给所有人
    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))) {
            ERRLOG("sendto error");
        }
    }
    //将新登录的用户头插到链表中
    node_t* pnew = NULL;
    create_node(&pnew);
    pnew->clientaddr = clientaddr;
    pnew->next = phead->next;
    phead->next = pnew;
    return 0;
}

//群聊消息的函数
int do_chat(struct sockaddr_in clientaddr, node_t* phead, int sockfd, msg_t msg)
{
    //遍历链表,将群聊的数据发送给除自己外的所有人
    node_t* ptemp = phead;
    while (ptemp->next != NULL) {
        ptemp = ptemp->next;
        if (0 != memcmp(&clientaddr, &(ptemp->clientaddr), sizeof(clientaddr))) {
            if (-1 == sendto(sockfd, &msg, sizeof(msg), 0, ((struct sockaddr*)&(ptemp->clientaddr)), sizeof(ptemp->clientaddr))) {
                ERRLOG("sendto error");
            }
        }
    }
}

int do_quit(struct sockaddr_in clientaddr, node_t* phead, int sockfd, msg_t msg)
{
    //将xxxx退出群聊的消息发送给除自己外所有的所有客户端,并且将自己在链表中删除
    node_t* ptemp = phead;
    while (ptemp->next != NULL) {

        if (0 != memcmp(&clientaddr, &(ptemp->clientaddr), sizeof(clientaddr))) {
            //判断是不是自己,不是自己就发送数据
            ptemp = ptemp->next;
            if (-1 == sendto(sockfd, &msg, sizeof(msg), 0, ((struct sockaddr*)&(ptemp->clientaddr)), sizeof(ptemp->clientaddr))) {
                ERRLOG("sendto error");
            }
        } else {
            //是自己,就将自己在链表中删除
            node_t* pdel = ptemp->next;
            ptemp->next = pdel->next;
            free(pdel);
            pdel == NULL;
        }
    }
}

        客户端代码

//客户端代码
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define ERRLOG(msg)                                        \
    do {                                                   \
        printf("%s %s %d:", __FILE__, __func__, __LINE__); \
        perror(msg);                                       \
        exit(-1);                                          \
    } while (0)

typedef struct _MSG {
    char code; //操作码 ('L' 登录),('C' 群聊),('Q' 退出)
    char name[32];
    char txt[128];
} msg_t;
int main(int argc, const char* argv[])
{
    //入参合理性检查
    if (3 != argc) {
        printf("Usage : %s  \n", argv[0]);
        return -1;
    }

    // 1.创建套接字
    int sockfd = 0;
    if (-1 == (sockfd = socket(AF_INET, SOCK_DGRAM, 0))) {
        ERRLOG("socket error");
    }
    // 2.填充服务器网络信息结构体
    struct sockaddr_in serveraddr;
    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    socklen_t serveraddr_len = sizeof(serveraddr);

    msg_t msg;
    memset(&msg, 0, sizeof(msg));
    printf("请输入用户名: ");
    fgets(msg.name, 32, stdin);
    msg.name[strlen(msg.name) - 1] = '\0';

    //给服务器发送登录的数据包

    msg.code = 'L';
    strcpy(msg.txt, "加入群聊");
    if (-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr*)&serveraddr, serveraddr_len)) {
        ERRLOG("sendto error");
    }

    pid_t pid = 0;
    if (-1 == (pid = fork())) {
        ERRLOG("fork error");
    } else if (0 == pid) {
        //子进程
        //接收由服务器发来的数据并打印
        while (1) {
            memset(&msg, 0, sizeof(msg));
            if (-1 == recvfrom(sockfd, &msg, sizeof(msg), 0, NULL, NULL)) {
                ERRLOG("recvfrom error");
            }
            printf("[%s]: [%s]\n", msg.name, msg.txt);
        }
    } else if (0 < pid) {
        //父进程
        while (1) {
            
            msg.code = 'C';
            fgets(msg.txt, 128, stdin);
            printf("我: ");
            msg.txt[strlen(msg.txt) - 1] = '\0';
            //判断是不是要退出
            if (!strncmp(msg.txt, "quit", 5)) {
                msg.code = 'Q';
                strcpy(msg.txt, "退出群聊");
            }

            if (-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr*)&serveraddr, serveraddr_len)) {
                ERRLOG("sendto error");
            }
            if (!strcmp(msg.txt, "退出群聊")) {
                break;
            }
        }
        //先让子进程自杀
        kill(pid, SIGKILL);
        wait(NULL);
        close(sockfd);
    }

    return 0;
}

你可能感兴趣的:(udp,服务器,网络协议)