基于UDP协议的聊天室项目

就是一个超级简单的UDP聊天室的实现,主要就是基于UDP协议的服务器客户端搭建,以及IO进程线程的应用

通信协议

#ifndef __HEAD_H__
#define __HEAD_H__
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>

//类型
enum type_t
{
    login,
    chat,
    quit,
};

//定义消息传递的协议
typedef struct mag_t
{
    int type;//类型
    char name[32];//ip地址
    char text[128];//发送内容
} MSG_t;

//链表节点结构体
typedef struct node_t
{
    struct sockaddr_in addr;//ip地址
    struct node_t *next;//链表下一个地址
}list_t;

#endif

服务器端

/*服务器创建代码 */
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include "head.h"
MSG_t msg;

list_t *createList();                                                       //创建头节点
int loginclient(list_t *p, struct sockaddr_in addr, MSG_t msg, int sockfd); //首次登录,加入链表
int quitclient(list_t *p, struct sockaddr_in addr, MSG_t msg, int sockfd);  //客户端退出,删除链表中对应内容
void chatclient(list_t *p, struct sockaddr_in addr, MSG_t msg, int sockfd); //遍历发送

int main(int argc, char const *argv[])
{
    if (argc < 2)
    {
        printf("plase input <ip><port>\n");
        return -1;
    }
    //1.创建套接字,用于链接
    int sockfd;
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("socket err");
        return -1;
    }
    //2.绑定 ip+port 填充结构体
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[1]));
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    socklen_t len = sizeof(saddr); //结构体大小
    //bind绑定ip和端口
    if (bind(sockfd, (struct sockaddr *)&saddr, len) < 0)
    {
        perror("bind err");
        return -1;
    }
    printf("udp聊天室已创建,等待加入\n");
    list_t *= createList(); //创建有头单向链表表头
    pid_t pid = fork();       //创建一个子进程
    if (pid < 0)
    {
        perror("fork err");
        return -1;
    }
    else if (pid == 0) //子进程,发送消息
    {
        //发送信息
        while (1)
        {
            fgets(msg.text, sizeof(msg.text), stdin); //从终端获取内容存放到数组中
            if (msg.text[strlen(msg.text)] == '\0')
            {
                msg.text[strlen(msg.text) - 1] = '\0';
            }
            strncpy(msg.name, "server", 6);
            msg.type = chat;
            list_t *temp = p;
            sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, sizeof(saddr));
        }
    }
    else //父进程,接收消息
    {
        while (1)
        {
            //接收信息
            if (recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, &len) < 0)
            {
                perror("recvfrom err");
                return -1;
            }
            // printf("client ip:%s ,port:%d buf:%s\n", inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port),buf);
            switch (msg.type)
            {
            case login:
                loginclient(p, saddr, msg, sockfd);
                break;
            case chat:
                chatclient(p, saddr, msg, sockfd);
                break;
            case quit:
                quitclient(p, saddr, msg, sockfd);
                break;
            }
        }
        char buff[32];
        sprintf(buff, "kill -9 %d", pid);
        system(buff); // 向子进程发送结束进程的信
        kill(getpid(), SIGINT);
        waitpid(pid, NULL, 0);
        exit(0);
    }
    close(sockfd);
    return 0;
}
//创建一个空的有头单项链表
list_t *createList()
{
    //1.创建节点,作为连表的头节点。
    list_t *= (list_t *)malloc(sizeof(list_t));
    if (NULL == h)
    {
        perror("createList malloc err");
        return NULL;
    }
    //2.初始化
    h->next = NULL;
    return h;
}
//向链表中插入数据,传入的参数:头节点,结构体(链表数据域),数据包,套接字描述符
int loginclient(list_t *p, struct sockaddr_in addr, MSG_t msg, int sockfd)
{
    list_t *temp = p;                          //定义一个新的指针保存头节点,防止头节点改变.
    sprintf(msg.text, "%s 已登录.", msg.name); //将登录成员名格式化输入到发送内容中
    while (temp->next != NULL)
    {
        temp = temp->next;
        sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&temp->addr, sizeof(temp->addr));
        //接收方保存在链表中,通过遍历链表访问
    }
    printf("成员:%s ,内容:%s\n", msg.name, msg.text);
    list_t *pnew = NULL;
    //创建一个新节点,保存插入数据
    pnew = (list_t *)malloc(sizeof(list_t));
    if (pnew == NULL) //判断开辟成功
    {
        perror("loginclient pnew malloc err");
        return -1;
    }
    pnew->addr = addr; //将传入的结构体保存到新节点中
    pnew->next = NULL;
    //将新节点插入到链表,先连后面,再连前面。前插法.
    pnew->next = p->next;
    p->next = pnew;
    return 0;
}
//删除内容
int quitclient(list_t *p, struct sockaddr_in addr, MSG_t msg, int sockfd)
{   
    list_t *pdel = NULL; //用于指向被删除的节点
    sprintf(msg.text, "%s 已退出.", msg.name); //将登录成员名格式化输入到发送内容中
    while (p->next != NULL)
    {   
        if (memcmp(&(p->next->addr), &addr, sizeof(addr)) != 0)
        {   
            p = p->next;
            sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&(p->addr), sizeof(p->addr));
        }
        else
        {
            pdel = p->next;             //(1)将pdel指向被删除节点
            p->next = pdel->next; //(2)跨过被删除节点
            free(pdel);           //(3)释放被删除节点
            pdel = NULL;
        }
    }
    printf("成员:%s ,内容:%s\n", msg.name, msg.text);
    return 0;
}
//遍历转发成员消息
void chatclient(list_t *p, struct sockaddr_in addr, MSG_t msg, int sockfd)
{
    list_t *temp = p;
    while (temp->next != NULL)
    {
        temp = temp->next;
        if (memcmp(&(temp->addr), &addr, sizeof(addr)) != 0)
        {
            sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&temp->addr, sizeof(temp->addr));
        }
    }
    printf("成员:%s ,内容:%s\n", msg.name, msg.text);
}

客户端

/*客户端创建代码 */
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
#include <sys/wait.h>
#include "head.h"
int sockfd;
char names[32];
struct sockaddr_in saddr;
MSG_t msg;            //消息包
void handler(int sig) // 信号处理函数 处理Ctrl C
{
    if (sig == SIGINT)
    {
        msg.type = quit;
        strcpy(msg.text, "quit");
        sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, sizeof(saddr));
        close(sockfd); // 关闭套接字
        wait(NULL);
        exit(0); // 退出程序
    }
}
int main(int argc, char const *argv[])
{
    if (argc < 3)
    {
        printf("plase input <ip><port>");
        return -1;
    }
    //1.创建套接字,用于链接
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("socket err");
        return -1;
    }
    printf("sockfd:%d\n", sockfd);
    //2.填充结构体
    saddr.sin_family = AF_INET;                 //协议族
    saddr.sin_port = htons(atoi(argv[2]));      //端口
    saddr.sin_addr.s_addr = inet_addr(argv[1]); //IP
    //登录-----只发送一次,发送协议结构体
    socklen_t len = sizeof(saddr); //结构体大小
    msg.type = login;
    //输入客户端ID
    printf("请输入您的ID:\n");
    fgets(names, sizeof(names), stdin);
    if (names[strlen(names) - 1] == '\n')
    {
        names[strlen(names) - 1] = '\0';
    }
    strncpy(msg.name, names, 9); //客户端昵称
    strncpy(msg.text, "login", 6);
    sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, len); //发送信号

    pid_t pid = fork(); //创建父子进程
    if (pid < 0)
    {
        perror("fork err");
        return -1;
    }
    else if (pid == 0) //子进程接收消息
    {
        while (1)
        {
            //接受信息
            if (recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, &len) < 0)
            {
                perror("recvfrom err");
                return -1;
            }
            printf("成员:%s 内容:%s\n", msg.name, msg.text);
        }
    }
    else //父进程发送消息
    {
        while (1)
        {
            // 捕捉键盘输入 Ctrl C
            signal(SIGINT, handler);
            //发送信息
            memset(msg.text, 0, sizeof(msg.text));    //清空数组内容
            fgets(msg.text, sizeof(msg.text), stdin); //从终端获取内容存放到数组中
            if (msg.text[strlen(msg.text) - 1] == '\n')
            {
                msg.text[strlen(msg.text) - 1] = '\0';
            }
            if (strncmp(msg.text, "quit", 4) == 0) //输入quit退出客户端
            {
                msg.type = quit; //退出状态
                sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, len);
                char buff[32];
                sprintf(buff, "kill -9 %d", pid);
                system(buff);          // 从终端向子进程发送结束进程的信号
                                       // 防止kill触发SIGINT信号导致二次发送quit信息
                waitpid(pid, NULL, 0); //等待子进程结束回收资源
                exit(0);
            }
            msg.type = chat;                                                      //交互状态
            sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, len); //发送信号
        }
    }
    close(sockfd);
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

满山的猴子我的腚最红

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

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

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

打赏作者

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

抵扣说明:

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

余额充值