基于UDP的网络聊天室

1. 流程图

2. 框架

2.1 客户端

//UDP网络编程之客户端

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>

#define N 128

//__FILE__: 获取文件名
//__func__:获取函数名
//__LINE__:获取行号
#define ERRLOG(errmsg) do{\
                            perror(errmsg);\
                            printf("%s-%s-%d\n", __FILE__, __func__, __LINE__);\
                            exit(1);\
                            }while(0)

int main(int argc, char const *argv[])
{
    if(argc < 3)
    {
        fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
        exit(1);
    }

    int sockfd;
    struct sockaddr_in serveraddr;
    socklen_t addrlen = sizeof(struct sockaddr_in);
    char buf[N] = {0};

    //第一步:创建套接字
    if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
    {
        ERRLOG("socket error");
    }

    //第二步:填充服务器网络信息结构体
    //inet_addr:将点分十进制ip地址转化为无符号32位整形数据
    //htons:将主机字节序转化为网络字节序
    //atoi:将数字型字符串转化为整形数据   "1568" --> 1568
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    serveraddr.sin_port = htons(atoi(argv[2]));

    //执行登录操作
    //客户端输入用户名并将操作码和用户名发送给服务器
    //do_login();
    
    //登录成功之后创建子进程实现一边发送数据一边接收数据
    pid_t pid;
    if((pid = fork()) == -1)
    {
        ERRLOG("fork error");
    }
    else if(pid > 0) //父进程负责发送数据
    {
        //do_send();
    }
    else //子进程负责接收数据
    {
        //do_recv();
    }

    return 0;
}  

2.2 服务器

//UDP网络编程之服务器

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>

#define N 128

//__FILE__: 获取文件名
//__func__:获取函数名
//__LINE__:获取行号
#define ERRLOG(errmsg) do{\
                            perror(errmsg);\
                            printf("%s-%s-%d\n", __FILE__, __func__, __LINE__);\
                            exit(1);\
                            }while(0)

int main(int argc, char const *argv[])
{
    if(argc < 3)
    {
        fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
        exit(1);
    }

    int sockfd;
    struct sockaddr_in serveraddr, clientaddr;
    socklen_t addrlen = sizeof(struct sockaddr_in);
    char buf[N] = {0};
    ssize_t bytes;

    //第一步:创建套接字
    if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
    {
        ERRLOG("socket error");
    }

    //第二步:填充服务器网络信息结构体
    //inet_addr:将点分十进制ip地址转化为无符号32位整形数据
    //htons:将主机字节序转化为网络字节序
    //atoi:将数字型字符串转化为整形数据   "1568" --> 1568
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    serveraddr.sin_port = htons(atoi(argv[2]));

    //第三步:将套接字与服务器网络信息结构体绑定
    if(bind(sockfd, (struct sockaddr *)&serveraddr, addrlen) == -1)
    {
        ERRLOG("bind error");
    }

    //服务器创建子进程实现一边发送系统数据一边接收数据
    pid_t pid;
    if((pid = fork()) == -1)
    {
        ERRLOG("fork error");
    }
    else if(pid > 0) //父进程负责发送系统信息
    {
        //do_send();
    }
    else //子进程负责接收数据
    {
        if(recvfrom() == -1)
        {
            ERRLOG("recvfrom error");
        }

        //login:1 chat:2 quit:3
        switch()
        {
        case 1:
            //do_login();
            break;
        case 2:
            //do_chat();
            break;
        case 3:
            //do_quit();
            break;    
        }
    }

    return 0;
}

3. 功能实现

3.1 客户端

//UDP网络编程之客户端

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>

//__FILE__: 获取文件名
//__func__:获取函数名
//__LINE__:获取行号
#define ERRLOG(errmsg) do{\
                            perror(errmsg);\
                            printf("%s-%s-%d\n", __FILE__, __func__, __LINE__);\
                            exit(1);\
                            }while(0)

#define N 32
typedef struct{
    int code; //login:1 chat:2 quit:3
    char name[N];
    char text[N];
}MSG;

int main(int argc, char const *argv[])
{
    if(argc < 3)
    {
        fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
        exit(1);
    }

    int sockfd;
    struct sockaddr_in serveraddr;
    socklen_t addrlen = sizeof(struct sockaddr_in);

    //第一步:创建套接字
    if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
    {
        ERRLOG("socket error");
    }

    //第二步:填充服务器网络信息结构体
    //inet_addr:将点分十进制ip地址转化为无符号32位整形数据
    //htons:将主机字节序转化为网络字节序
    //atoi:将数字型字符串转化为整形数据   "1568" --> 1568
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    serveraddr.sin_port = htons(atoi(argv[2]));

    MSG msg;
    memset(&msg, 0, sizeof(msg));
    //执行登录操作
    //客户端输入用户名并将操作码和用户名发送给服务器
    msg.code = 1;
    printf("请输入用户名:");
    fgets(msg.name, N, stdin);
    msg.name[strlen(msg.name) - 1] = '\0';
    if(sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, addrlen) == -1)
    {
        ERRLOG("sendto error");
    }
    
    //登录成功之后创建子进程实现一边发送数据一边接收数据
    pid_t pid;
    if((pid = fork()) == -1)
    {
        ERRLOG("fork error");
    }
    else if(pid > 0) //父进程负责发送数据
    {
        while(1)
        {
            fgets(msg.text, N, stdin);
            msg.text[strlen(msg.text) - 1] = '\0';

            //判断是群聊信息还是退出信息
            //退出信息
            if(strcmp(msg.text, "quit") == 0)
            {
                //设置操作码并发送给服务器
                msg.code = 3;
                if(sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, addrlen) == -1)
                {
                    ERRLOG("sendto error");
                }

                //让整个客户端退出
                kill(pid, SIGKILL);
                exit(0);
            }
            //群聊信息
            else
            {
                //设置操作码并发送数据给服务器
                msg.code = 2;
                if(sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, addrlen) == -1)
                {
                    ERRLOG("sendto error");
                }
            }
        }
    }
    else //子进程负责接收数据
    {
        char buf[128] = {0};
        while(1)
        {
            if(recvfrom(sockfd, buf, 128, 0, NULL, NULL) == -1)
            {
                ERRLOG("recvfrom error");
            }

            printf("%s\n", buf);
        }
    }

    return 0;
}  

3.2 服务器

//UDP网络编程之服务器

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>

//__FILE__: 获取文件名
//__func__:获取函数名
//__LINE__:获取行号
#define ERRLOG(errmsg) do{\
                            perror(errmsg);\
                            printf("%s-%s-%d\n", __FILE__, __func__, __LINE__);\
                            exit(1);\
                            }while(0)
#define N 32
typedef struct{
    int code; //login:1 chat:2 quit:3
    char name[N];
    char text[N];
}MSG;

//

typedef struct node{
    struct sockaddr_in addr; //数据域,保存每一个登录的用户的信息
    struct node *next; //指针域,保存下一个结点的地址
}linklist;

linklist *LinklistCreate();
void do_login(int sockfd, MSG msg, linklist *head, struct sockaddr_in clientaddr);
void do_chat(int sockfd, MSG msg, linklist *head, struct sockaddr_in clientaddr);
void do_quit(int sockfd, MSG msg, linklist *head, struct sockaddr_in clientaddr);
int main(int argc, char const *argv[])
{
    if(argc < 3)
    {
        fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
        exit(1);
    }

    int sockfd;
    struct sockaddr_in serveraddr, clientaddr;
    socklen_t addrlen = sizeof(struct sockaddr_in);
    char buf[N] = {0};
    ssize_t bytes;

    //第一步:创建套接字
    if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
    {
        ERRLOG("socket error");
    }

    //第二步:填充服务器网络信息结构体
    //inet_addr:将点分十进制ip地址转化为无符号32位整形数据
    //htons:将主机字节序转化为网络字节序
    //atoi:将数字型字符串转化为整形数据   "1568" --> 1568
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    serveraddr.sin_port = htons(atoi(argv[2]));

    //第三步:将套接字与服务器网络信息结构体绑定
    if(bind(sockfd, (struct sockaddr *)&serveraddr, addrlen) == -1)
    {
        ERRLOG("bind error");
    }

    //服务器创建子进程实现一边发送系统数据一边接收数据
    pid_t pid;
    if((pid = fork()) == -1)
    {
        ERRLOG("fork error");
    }
    else if(pid > 0) //父进程负责发送系统信息
    {
        //将父进程当做是客户端直接给子进程发送群聊信息即可
        MSG msg;
        msg.code = 2;
        strcpy(msg.name, "server");
        while(1)
        {
            fgets(msg.text, N, stdin);
            msg.text[strlen(msg.text) - 1] = '\0';

            if(sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, addrlen) == -1)
            {
                ERRLOG("sendto error");
            }
        }
    }
    else //子进程负责接收数据
    {
        MSG msg;
        linklist *head = LinklistCreate();
        while(1)
        {
            if(recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&clientaddr, &addrlen) == -1)
            {
                ERRLOG("recvfrom error");
            }

            printf("[%s-%d]: code:%d, name:%s, text:%s\n", inet_ntoa(clientaddr.sin_addr),\
                        ntohs(clientaddr.sin_port), msg.code, msg.name, msg.text);

            //根据接收数据的操作码做出相应的处理
            //login:1 chat:2 quit:3
            switch(msg.code)
            {
            case 1:
                do_login(sockfd, msg, head, clientaddr);
                break;
            case 2:
                do_chat(sockfd, msg, head, clientaddr);
                break;
            case 3:
                do_quit(sockfd, msg, head, clientaddr);
                break;    
            }
        }
    }

    return 0;
}

linklist *LinklistCreate()
{
    linklist *h = (linklist *)malloc(sizeof(linklist));
    h->next = NULL;

    return h;
}

void do_login(int sockfd, MSG msg, linklist *head, struct sockaddr_in clientaddr)
{
    //将新用户登录的信息组包发送给其他在线的用户
    //组数据
    char buf[128] = {0};
    sprintf(buf, ">>> %s 上线了 <<<", msg.name);
    
    //将组好的数据发送给其他在线的用户
    linklist *p = head;
    while(p->next != NULL)
    {
        p = p->next;
        if(sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&p->addr, sizeof(struct sockaddr_in)) == -1)
        {
            ERRLOG("sendto error");
        }
    }

    //将新登录的用户的信息保存在链表中
    linklist *temp = (linklist *)malloc(sizeof(linklist));
    temp->addr = clientaddr;
    temp->next = NULL;

    temp->next = head->next;
    head->next = temp;

    return ;
}

void do_chat(int sockfd, MSG msg, linklist *head, struct sockaddr_in clientaddr)
{
    //将群聊信息组包后发送给其他在线的用户
    //组包
    char buf[128] = {0};
    sprintf(buf, "%s: %s", msg.name, msg.text);

    //遍历链表发送数据
    linklist *p = head;
    while(p->next != NULL)
    {
        p = p->next;
        //发送者不接受自己发送的数据
        //memcmp:比较两个内存区域的内容是否一样
        if(memcmp(&p->addr, &clientaddr, sizeof(clientaddr)) != 0)
        {
            if(sendto(sockfd, buf, 128, 0, (struct sockaddr *)&p->addr, sizeof(clientaddr)) == -1)
            {
                ERRLOG("sendto error");
            }
        }
    }

    return ;
}   

void do_quit(int sockfd, MSG msg, linklist *head, struct sockaddr_in clientaddr)
{
    //将用户下线的信息组包发送给其他在线用户
    //组包
    char buf[128] = {0};
    sprintf(buf, "------ %s 下线了 ------", msg.name);

    //遍历链表发送数据给客户端
    linklist *p = head;
    linklist *temp;
    while(p->next != NULL)
    {
        //将退出用户的结点从链表中删除
        if(memcmp(&p->next->addr, &clientaddr, sizeof(clientaddr)) == 0)
        {
            temp = p->next;
            p->next = temp->next;

            free(temp);
            temp = NULL;
        }
        else
        {
            if(sendto(sockfd, buf, 128, 0, (struct sockaddr *)&p->next->addr, sizeof(clientaddr)) == -1)
            {
                ERRLOG("sendto error");
            }

            p = p->next;
        }
    }

    return ;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小徐的记事本

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值