UDP聊天室

目录

项目要求

问题思考

流程图

服务器端流程

 客户端流程

总体流程

服务器端代码

客户端代码

运行效果


项目要求

利用UDP协议,实现一套聊天室软件。服务器端记录客户端的地址,客户端发送消息后,服务器群发给各个客户端软件。类似于微信群聊。

问题思考

  • 客户端会不会知道其它客户端地址?

UDP客户端不会直接互连,所以不会获知其它客户端地址,所有客户端地址存储在服务器端

  • 有几种消息类型?

登录:服务器存储新的客户端的地址。把某个客户端登录的消息发给其它客户端。

聊天:服务器只需要把某个客户端的聊天消息转发给所有其它客户端。

  • 服务器如何存储客户端的地址?

数据结构可以选择线性数据结构,可以选择链表

  • 客户端如何同时处理发送和接收?

客户端不仅需要读取服务器消息,而且需要发送消息。读取需要调用recvfrom,发送需要先调用gets,两个都是阻塞函数。所以必须使用多任务来同时处理,可以使用多进程来处理。

流程图

服务器端流程

创建流式套接字,绑定本机地址,监听客户端的连接请求,当有客户端请求连接时,与客户端建立连接,客户端第一次连接时,需要登录。

登录时:

        当有客户端登录时,调用登录函数,将新登录的客户端地址存入链表中,并且将客户端

        的登录消息提示给其他客户端

聊天时:接收客户端的消息,并且把消息转发给其他客户端,通过遍历链表中的客户端地

        址,挨个发送消息。

退出时:当客户端退出时,接收客户端的退出信息,把客户端的地址从链表中删除,并且把

        客户端退出的消息提示给其他客户端

 

 客户端流程

从终端获取信息,向服务器发送消息,同时接收服务器发送的消息。

 

总体流程

 

服务器端代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <dirent.h>
#include <sys/stat.h>
#include <signal.h>
#include <pthread.h>

enum type_t // 枚举
{
    Login,
    Chat,
    Quit,
};
typedef struct msg
{
    char type;      // L C Q
    char name[32];  //
    char text[128]; //
} msg_t;

typedef struct NODE // 链表
{
    struct sockaddr_in cliaddr;
    struct NODE *next;
} node_t;

node_t *create_node(void) // 建头节点
{
    node_t *p = (node_t *)malloc(sizeof(node_t));
    if (p == NULL)
    {
        perror("malloc err");
        return NULL;
    }
    p->next = NULL;
    return p;
}

// 登录的函数
// 功能:
// 1》将新登录的用户转发给所有已经登录的用户(遍历链表发送谁登录的消息)
// 2》创建新节点来保存新登录用户的信息,链接到链表尾就可以
void do_login(int serverfd, msg_t msg, node_t *p, struct sockaddr_in cliaddr)
{

    sprintf(msg.text, "%s 以上线", msg.name);
    // 循环链表,向其他客户端发送上线信息
    while (p->next != NULL)
    {
        p = p->next;
        sendto(serverfd, &msg, sizeof(msg), 0,
               (struct sockaddr *)&(p->cliaddr), sizeof(p->cliaddr));
    }

    // 将新登录的客户端信息插入到链表中
    node_t *new = (node_t *)malloc(sizeof(node_t));
    // 初始化
    new->cliaddr = cliaddr;
    new->next = NULL;
    // 链接到链表尾
    p->next = new;
    return;
}

// 群聊的函数
// 功能:将客户端发来的聊天内容转发给所有已登录的用户,除了发送聊天内容的用户以外
void do_chat(int serverfd, msg_t msg, node_t *p, struct sockaddr_in cliaddr)
{
    // 遍历链表
    while (p->next != NULL)
    {
        p = p->next;
        // 比较链表中的存的客户端信息,向爱他客户端发送信息
        if (strcmp("p->cliaddr.sin_addr", "cliaddr.sin_addr") != 0)
        {
            sendto(serverfd, &msg, sizeof(msg), 0,
                   (struct sockaddr *)&(p->cliaddr), sizeof(p->cliaddr));
        }
    }
    return;
}

// 退出函数
// 功能:
// 1》将谁退出的消息转发给i所有用户
// 2》将链表中保存这个推出的用户信息的节点删除
void do_quit(int serverfd, msg_t msg, node_t *p, struct sockaddr_in cliaddr)
{
    sprintf(msg.text, "%s 以下线", msg.name);
    while (p->next != NULL)
    {
        // 比较链表中的存的客户端信息
        if (strcmp("p->cliaddr.sin_addr", "cliaddr.sin_addr") == 0)
        {
            // 删掉退出客户端的节点
            node_t *dele = NULL;
            dele = p->next;
            p->next = dele->next;
            free(dele);
            dele = NULL;
        }
        else
        {
            // 向节点发送客户端退出的信息
            p = p->next;
            sendto(serverfd, &msg, sizeof(msg), 0,
                   (struct sockaddr *)&(p->cliaddr), sizeof(p->cliaddr));
        }
    }
    return;
}

int main(int argc, char const *argv[])
{
    int serverfd;
    int res;
    struct sockaddr_in myaddr, cliaddr;

    // 创建UDP套接字
    serverfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (serverfd < 0)
    {
        perror("socket err");
        exit(-1);
    }
    printf("socket scuess  ");
    printf("sserverfd: %d\n", serverfd);

    // 填充服务器网络信息结构体
    myaddr.sin_family = AF_INET;
    myaddr.sin_port = htons(atoi(argv[1]));
    myaddr.sin_addr.s_addr = INADDR_ANY;
    socklen_t addrlen = sizeof(myaddr);

    // 设置套接字属性,可以重用本地地址与端口
    int optval = 1;
    res = setsockopt(serverfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
    if (res < 0)
    {
        perror("setcoskopt error");
        return -1;
    }

    // 绑定套接字和服务器网络信息的结构体
    res = bind(serverfd, (struct sockaddr *)&myaddr, addrlen);
    if (res < 0)
    {
        perror("bind error");
        return -1;
    }
    printf("bind ok!\n");

    msg_t msg;                 // 结构体变量
    node_t *p = create_node(); // 创建链表

    while (1)
    {
        // 接收客户端的消息
        if (recvfrom(serverfd, &msg, sizeof(msg), 0,
                     (struct sockaddr *)&cliaddr, &addrlen) < 0)
        {
            perror("recvfrom err");
            return -1;
        }

        if (msg.type == Login) // 登录
        {
            printf("有新用户登录\n");
            strcpy(msg.text, "以上线");
            printf("ip:%s pord:%d name:%s  ", inet_ntoa(cliaddr.sin_addr),
                   ntohs(cliaddr.sin_port), msg.name);

            printf("状态:%s\n", msg.text);
            do_login(serverfd, msg, p, cliaddr);
        }
        else if (msg.type == Chat) // 聊天状态
        {
            do_chat(serverfd, msg, p, cliaddr);
        }
        else if (msg.type == Quit) // 退出
        {
            strcpy(msg.text, "以下线");
            printf("ip:%s pord:%d name:%s  ", inet_ntoa(cliaddr.sin_addr),
                   ntohs(cliaddr.sin_port), msg.name);

            printf("状态:%s\n", msg.text);
            do_quit(serverfd, msg, p, cliaddr);
        }
    }

    close(serverfd);
    return 0;
}

客户端代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <dirent.h>
#include <sys/stat.h>
#include <signal.h>
#include <pthread.h>

enum type_t // 枚举
{
    Login,
    Chat,
    Quit,
};
typedef struct msg
{
    char type;      // L C Q
    char name[32];  //
    char text[128]; //
} msg_t;

typedef struct NODE // 链表
{
    struct sockaddr_in cliaddr;
    struct NODE *next;
} node_t;

node_t *create_node(void) // 建头节点
{
    node_t *p = (node_t *)malloc(sizeof(node_t));
    if (p == NULL)
    {
        perror("malloc err");
        return NULL;
    }
    p->next = NULL;
    return p;
}

// 登录的函数
// 功能:
// 1》将新登录的用户转发给所有已经登录的用户(遍历链表发送谁登录的消息)
// 2》创建新节点来保存新登录用户的信息,链接到链表尾就可以
void do_login(int serverfd, msg_t msg, node_t *p, struct sockaddr_in cliaddr)
{

    sprintf(msg.text, "%s 以上线", msg.name);
    // 循环链表,向其他客户端发送上线信息
    while (p->next != NULL)
    {
        p = p->next;
        sendto(serverfd, &msg, sizeof(msg), 0,
               (struct sockaddr *)&(p->cliaddr), sizeof(p->cliaddr));
    }

    // 将新登录的客户端信息插入到链表中
    node_t *new = (node_t *)malloc(sizeof(node_t));
    // 初始化
    new->cliaddr = cliaddr;
    new->next = NULL;
    // 链接到链表尾
    p->next = new;
    return;
}

// 群聊的函数
// 功能:将客户端发来的聊天内容转发给所有已登录的用户,除了发送聊天内容的用户以外
void do_chat(int serverfd, msg_t msg, node_t *p, struct sockaddr_in cliaddr)
{
    // 遍历链表
    while (p->next != NULL)
    {
        p = p->next;
        // 比较链表中的存的客户端信息,向爱他客户端发送信息
        if (strcmp("p->cliaddr.sin_addr", "cliaddr.sin_addr") != 0)
        {
            sendto(serverfd, &msg, sizeof(msg), 0,
                   (struct sockaddr *)&(p->cliaddr), sizeof(p->cliaddr));
        }
    }
    return;
}

// 退出函数
// 功能:
// 1》将谁退出的消息转发给i所有用户
// 2》将链表中保存这个推出的用户信息的节点删除
void do_quit(int serverfd, msg_t msg, node_t *p, struct sockaddr_in cliaddr)
{
    sprintf(msg.text, "%s 以下线", msg.name);
    while (p->next != NULL)
    {
        // 比较链表中的存的客户端信息
        if (strcmp("p->cliaddr.sin_addr", "cliaddr.sin_addr") == 0)
        {
            // 删掉退出客户端的节点
            node_t *dele = NULL;
            dele = p->next;
            p->next = dele->next;
            free(dele);
            dele = NULL;
        }
        else
        {
            // 向节点发送客户端退出的信息
            p = p->next;
            sendto(serverfd, &msg, sizeof(msg), 0,
                   (struct sockaddr *)&(p->cliaddr), sizeof(p->cliaddr));
        }
    }
    return;
}

int main(int argc, char const *argv[])
{
    int serverfd;
    int res;
    struct sockaddr_in myaddr, cliaddr;

    // 创建UDP套接字
    serverfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (serverfd < 0)
    {
        perror("socket err");
        exit(-1);
    }
    printf("socket scuess  ");
    printf("sserverfd: %d\n", serverfd);

    // 填充服务器网络信息结构体
    myaddr.sin_family = AF_INET;
    myaddr.sin_port = htons(atoi(argv[1]));
    myaddr.sin_addr.s_addr = INADDR_ANY;
    socklen_t addrlen = sizeof(myaddr);

    // 设置套接字属性,可以重用本地地址与端口
    int optval = 1;
    res = setsockopt(serverfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
    if (res < 0)
    {
        perror("setcoskopt error");
        return -1;
    }

    // 绑定套接字和服务器网络信息的结构体
    res = bind(serverfd, (struct sockaddr *)&myaddr, addrlen);
    if (res < 0)
    {
        perror("bind error");
        return -1;
    }
    printf("bind ok!\n");

    msg_t msg;                 // 结构体变量
    node_t *p = create_node(); // 创建链表

    while (1)
    {
        // 接收客户端的消息
        if (recvfrom(serverfd, &msg, sizeof(msg), 0,
                     (struct sockaddr *)&cliaddr, &addrlen) < 0)
        {
            perror("recvfrom err");
            return -1;
        }

        if (msg.type == Login) // 登录
        {
            printf("有新用户登录\n");
            strcpy(msg.text, "以上线");
            printf("ip:%s pord:%d name:%s  ", inet_ntoa(cliaddr.sin_addr),
                   ntohs(cliaddr.sin_port), msg.name);

            printf("状态:%s\n", msg.text);
            do_login(serverfd, msg, p, cliaddr);
        }
        else if (msg.type == Chat) // 聊天状态
        {
            do_chat(serverfd, msg, p, cliaddr);
        }
        else if (msg.type == Quit) // 退出
        {
            strcpy(msg.text, "以下线");
            printf("ip:%s pord:%d name:%s  ", inet_ntoa(cliaddr.sin_addr),
                   ntohs(cliaddr.sin_port), msg.name);

            printf("状态:%s\n", msg.text);
            do_quit(serverfd, msg, p, cliaddr);
        }
    }

    close(serverfd);
    return 0;
}

运行效果

UDP聊天室是一种基于用户数据报协议(UDP)的即时通讯系统。它的主要功能是让用户能够实时发送和接收文本消息,以便进行交流和沟通。 首先,UDP聊天室需要一个用户界面,使用户能够方便地登录和注册账号。用户界面应该提供一些基本信息,如用户名、密码和昵称等。此外,用户界面还应该有一个聊天窗口,用于显示用户的消息和其他用户发送的消息。 其次,UDP聊天室需要一个服务器来管理用户的登录和连接。服务器应该能够接收用户的登录请求并验证其身份。在用户登录之后,服务器还应该能够将用户加入到相应的聊天室中,并将其他用户的消息转发给相应的用户。 为了保证通信的实时性和稳定性,UDP聊天室应该有一套协议来约定消息的格式和传输方式。这样可以确保用户发送的消息能够顺利到达其他用户,并能够实时地显示在聊天窗口上。 此外,为了提供更好的用户体验,UDP聊天室还可以提供一些额外的功能。例如,可以允许用户在聊天窗口上发送和接收图片、表情等多媒体内容。还可以提供私聊功能,使用户能够与特定的其他用户进行一对一的对话。 总结起来,UDP聊天室的需求分析包括用户界面的设计、服务器的管理和通信协议的制定。通过这些功能和设计,UDP聊天室可以提供一个实时、稳定和多功能的聊天平台,方便用户进行即时通讯和交流。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值