网络聊天室---UDP实现

此网络聊天室使用UDP协议,来实现聊天室功能。其中,服务端可以记录各客户端的地址,服务端也会将客户端发送的消息转发给各客户端。

共有3种消息类型:

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

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

退出:服务器删除退出客户端的地址,并把退出消息发送给其它客户端。

此项目使用单向链表来存储各登录用户的地址和端口信息,采用的结构体如下:

typedef struct node
{
    struct sockaddr_in addr;
    struct node *next;
} node, *node_t;
typedef struct msg
{
    int type;       //L  C  Q
    char name[32];  //用户名
    char text[128]; //消息正文
} msg;

其中type表示消息类型,当type=L时,代表登录、type=C,代表发送消息、type=Q,代表用户退出;第一个结构体的addr则用来存储登录用户的IP地址和端口信息。

  • 对于用户端,首先创建套接字文件,该套接字文件采用IPv4的通信方式;创建完套接字后开始填充其IP地址、端口、通信方式等信息;填充完成后将其登录信息发送到客户端,然后通过多进程来进行发送信息和接收信息,其中子进程用来发送消息,父进程用来接收消息,当输入quit时将会退出系统。

用户端的具体代码如下:

#include <stdio.h>
#include "8-chatroom.h"
void quit_handler(int sig)
{
    kill(getpid(), SIGKILL);
}
int main(int argc, char const *argv[])
{
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("socket err.");
        return -1;
    }
    if (argc != 3)
    {
        printf("please input ./11-chatroom <port>\n");
        return -1;
    }
    struct sockaddr_in serveraddr, caddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    socklen_t len = sizeof(caddr);
    char buf[128];
    msg usr;
    printf("请输入用户名:");
    fgets(usr.name, sizeof(usr.name), stdin);
    usr.type = 'L';
    sendto(sockfd, &usr, sizeof(usr), 0, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
    pid_t pid = fork();
    if (pid < 0)
    {
        perror("fork err");
        return -1;
    }
    else if (pid == 0)
    {
        while (1)
        {
            fgets(usr.text, sizeof(usr.text), stdin);
            usr.type = 'C';
            if (usr.text[strlen(usr.text) - 1] == '\n')
                usr.text[strlen(usr.text) - 1] = '\0';
            sendto(sockfd, &usr, sizeof(usr), 0,
                   (struct sockaddr *)&serveraddr, sizeof(serveraddr));
            if (strncmp(usr.text, "quit", 4) == 0)
            {
                usr.type = 'Q';
                sendto(sockfd, &usr, sizeof(usr), 0,
                       (struct sockaddr *)&serveraddr, sizeof(serveraddr));
                break;
            }
        }
    }
    else
    {
        int recvbyte;
        while (1)
        {
            signal(SIGCHLD, quit_handler);//子进程状态发生变化时,回收子进程
            recvbyte = recvfrom(sockfd, &usr, sizeof(usr), 0,
                                (struct sockaddr *)&caddr, &len);
            if (recvbyte < 0)
            {
                perror("recvfrom err");
                return -1;
            }
            if (usr.type == 'L')//接收其他用户的上线信息
            {
                if (usr.name[strlen(usr.name) - 1] == '\n')
                    usr.name[strlen(usr.name) - 1] = '\0';
                printf("name:%s上线\n", usr.name);
            }
            if (usr.type == 'C')//接收其他用户发送的消息
            {
                if (usr.name[strlen(usr.name) - 1] == '\n')
                    usr.name[strlen(usr.name) - 1] = '\0';
                printf("%s:%s\n", usr.name, usr.text);
            }
            if (usr.type == 'Q')//接收其他用户下线的信息
            {
                if (usr.name[strlen(usr.name) - 1] == '\n')
                    usr.name[strlen(usr.name) - 1] = '\0';
                printf("name:%s下线\n", usr.name);
            }
        }
        wait(NULL);
        exit(0);
    }

    close(sockfd);
    return 0;
}

服务端同样创建套接字文件、信息填充、绑定套接字,但父进程采用了多线程的方式来接收消息和发送消息,子线程用来服务端发送消息。具体流程可观看本人分享的代码。

其具体代码如下:

#include <stdio.h>
#include "8-chatroom.h"
struct sockaddr_in serveraddr, caddr;
void *handler(void *arg);
void login(int sockfd, node_t H, msg m, struct sockaddr_in caddr);
void send_news(int sockfd, node_t H, msg m, struct sockaddr_in caddr);
void quit(int sockfd, node_t H, msg m, struct sockaddr_in caddr);
int main(int argc, char const *argv[])
{
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("socket err");
        return -1;
    }
    if (argc != 2)
    {
        printf("please input ./11-chatroom <port>\n");
        return -1;
    }
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[1]));
    serveraddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    socklen_t len = sizeof(caddr);

    if (bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
    {
        perror("bind err");
        return -1;
    }
    pthread_t tid;
    pthread_create(&tid, NULL, handler, &sockfd);
    pthread_detach(tid);
    node_t H = head_create();//创建头节点
    msg usr;
    int recvbyte;
    while (1)
    {
        recvbyte = recvfrom(sockfd, &usr, sizeof(usr), 0,
                            (struct sockaddr *)&caddr, &len);
        if (recvbyte < 0)
        {
            perror("recvfrom err");
            return -1;
        }
        if (usr.type == 'L')//用户登录时
        {
            login(sockfd, H, usr, caddr);
        }
        if (usr.type == 'C')//用户发送消息时
        {
            send_news(sockfd, H, usr, caddr);
        }
        if (usr.type == 'Q')//用户退出时
        {
            quit(sockfd, H, usr, caddr);
        }
    }
    close(sockfd);
    return 0;
}
//有用户登录时,储存其ip地址,并发送
void login(int sockfd, node_t H, msg m, struct sockaddr_in caddr)
{
    node_t p = H->next;
    while (p != NULL)
    {
        sendto(sockfd, &m, sizeof(m), 0,
               (struct sockaddr *)&(p->addr), sizeof(p->addr));
        p = p->next;
    }
    tail_insert(H, caddr);//此函数是链表的尾插函数
    if (m.name[strlen(m.name) - 1] == '\n')
        m.name[strlen(m.name) - 1] = '\0';
    printf("ip:%s port:%d name:%s登录\n", inet_ntoa(caddr.sin_addr),
           ntohs(caddr.sin_port), m.name);
}
//接收消息,并转发
void send_news(int sockfd, node_t H, msg m, struct sockaddr_in caddr)
{
    node_t p = H->next;

    while (p != NULL)
    {
        if (memcmp(&(p->addr), &caddr, sizeof(caddr)) == 0)
        {
            p = p->next;
            continue;
        }
        sendto(sockfd, &m, sizeof(m), 0,
               (struct sockaddr *)&(p->addr), sizeof(p->addr));
        p = p->next;
    }
    if (m.name[strlen(m.name) - 1] == '\n')
        m.name[strlen(m.name) - 1] = '\0';
    printf("%s:%s\n", m.name, m.text);
}
//用户退出
void quit(int sockfd, node_t H, msg m, struct sockaddr_in caddr)
{
    node_t p = H->next;
    while (p != NULL)
    {
        sendto(sockfd, &m, sizeof(m), 0,
               (struct sockaddr *)&(p->addr), sizeof(p->addr));
        p = p->next;
    }
    if (m.name[strlen(m.name) - 1] == '\n')
        m.name[strlen(m.name) - 1] = '\0';
    printf("ip:%s port:%d name:%s已退出\n", inet_ntoa(caddr.sin_addr),
           ntohs(caddr.sin_port), m.name);
    local_delete(H, caddr);//此函数是链表的按位置删除函数
}
//服务端发送消息
void *handler(void *arg)
{
    msg m;
    int sockfd = *((int *)arg);
    m.type = 'C';
    strcpy(m.name, "服务器");
    while (1)
    {
        fgets(m.text, sizeof(m.text), stdin);
        if (m.name[strlen(m.name) - 1] == '\n')
            m.name[strlen(m.name) - 1] = '\0';

        sendto(sockfd, &m, sizeof(m), 0,
               (struct sockaddr *)&serveraddr, sizeof(serveraddr));
    }
    pthread_exit(NULL);
}

完成后的效果展示:

 当然,本项目只能用于局域网之间的通信,本次分享到这里就结束了,下次再见!

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值