UDP聊天框

一、聊天框项目 

主要思想是:实现并发

1.采用了网络编程的UDP和串口、数据结构中的链表、IO的父子进程

2.主要功能是:登录,聊天,退出

聊天显示,加入群聊成功,聊天信息,退出提示,串口的复用,父子进程,链表等知识;

可以实现的方式有很多,IO多路复用比较难理解,进程线程

二、项目的流程

服务器:

 

客户端:

 

三、代码部分

服务器部分:

//服务器
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <string.h>
#include "head.h"

list_t *createEmptyListLink();
void radio_add(int sockfd, list_t *p, struct sockaddr_in caddr);
void users_chatting(int sockfd, list_t *p, struct sockaddr_in caddr);
void users_exit(int sockfd, list_t *p, struct sockaddr_in caddr);

MSG_t msg;
char buf[128];
int main(int argc, char const *argv[])
{
    if (argc != 2)
    {
        perror("please input <port>");
        return -1;
    }
    //1.服务器的创建UDP的创建
    // 2.创建一个套节字文件
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("socket err");
        return -1;
    }
    printf("socket success\n");

    //3.绑定套节字
    struct sockaddr_in saddr, caddr;
    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);

    int p = 1;
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &p, sizeof(p));
    if (bind(sockfd, (struct sockaddr *)&saddr, len) < 0)
    {
        perror("bind err");
        return -1;
    }
    printf("connet success\n");

    pid_t pid = fork();
    // 创建父子进程
    if (pid < 0)
    {
        perror("fork err");
        return -1;
    }
    else if (pid == 0) //子进程
    {
        while (1)
        {
            msg.type = chat;
            strcpy(msg.name,"server");
            fgets(msg.text, sizeof(msg.text), stdin);
            if (msg.text[strlen(msg.text) - 1] == '\n')
                msg.text[strlen(msg.text) - 1] = '\0';
            sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, len);
        }
    }
    else //父进程
    {
        //定义链表
        list_t *p = createEmptyListLink();
        while (1)
        {
            //链接客户端
            ssize_t ret = recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&caddr, &len);
            if (ret < 0)
            {
                perror("recvfrom err");
                return -1;
            }
            if (msg.type == login)
            {
                radio_add(sockfd, p, caddr);
                printf("ip:%s port:%d id: %s\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port), msg.name);
            }
            if (msg.type == chat)
            {
                users_chatting(sockfd, p, caddr);
            }
            if (msg.type == quit)
            {
                users_exit(sockfd, p, caddr);
            }
        }
    }
    close(sockfd);
    return 0;
}
//创建链表
list_t *createEmptyListLink()
{
    list_t *p = (list_t *)malloc(sizeof(list_t));
    if (p == NULL)
    {
        perror("malloc err");
        return NULL;
    }
    p->next = NULL;
    return p;
}

//用户登录
void radio_add(int sockfd, list_t *p, struct sockaddr_in caddr)
{

    //拼接信息
    strcpy(msg.text,"login");
    //遍历向其他人发送消息
    while (p->next != NULL)
    {
        p = p->next;
        sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&(p->addr), sizeof(p->addr));
    }
    //创建一个新的节点
    list_t *pnew = (list_t *)malloc(sizeof(list_t));
    //给新节点赋值
    pnew->addr = caddr;
    //将ptail尾节点的指针域指向新节点的地址
    pnew->next = NULL;
    //移动尾指针到新节点
    p->next = pnew;
    printf("end success\n");
}

//聊天
void users_chatting(int sockfd, list_t *p, struct sockaddr_in caddr)
{
    while (p->next != NULL)
    {
        p = p->next;
        if (memcmp(&(p->addr), &caddr, sizeof(caddr)) != 0) //使用将存储区里面得内容进行比较
        {
            sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&(p->addr), sizeof(p->addr));
        }
    }
}

//退出
void users_exit(int sockfd, list_t *p, struct sockaddr_in caddr)
{
    list_t *pdel = NULL;
    // sprintf(msg.text, "%s:exit", msg.name);
    while (p->next != NULL)
    {

        //发送退出信息给所有客户端
        if (memcmp(&(p->next->addr), &caddr, sizeof(caddr)) != 0)
        {
            p = p->next;
            sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&(p->addr), sizeof(p->addr));
        }
        else //删除链表的节点
        {
            pdel = p->next;
            p->next = pdel->next;
            free(pdel);
            pdel = NULL;
        }
    }
    printf("del success\n");
}

客户端部分

#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include "head.h"

MSG_t msg;
int sockfd;
socklen_t len;
struct sockaddr_in saddr, caddr;
void handler(int sig)
{
    msg.type = quit;
    strcpy(msg.text,"quit");
    sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, len);
    exit(0);
}
int main(int argc, char const *argv[])
{
    if (argc < 3)
    {
        perror("please input <ip> <port>");
        return -1;
    }
        //创建套节字UDP的套节字
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("socket err");
        return -1;
    }
    //创建结构体来存放要连接服务器的ip+port
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[2]));
    saddr.sin_addr.s_addr = inet_addr(argv[1]);
    len = sizeof(saddr);

    //获取登录的信息//将信息放到协议里
    //执行一次
    msg.type = login;
    printf("plese input id:");
     fgets(msg.name, sizeof(msg.name), stdin);
    if (msg.name[strlen(msg.name) - 1] == '\n')
        msg.name[strlen(msg.name) - 1] = '\0';
    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)
        {
            //接收数据,服务器的数据
            ssize_t ret = recvfrom(sockfd, &msg, sizeof(msg), 0, NULL, NULL);
            if (ret < 0)
            {
                perror("recvfrom err");
                return -1;
            }
            //打数据
            printf("%s:%s\n",msg.name,msg.text);
        }
    }
    else
    {
        signal(SIGINT,handler);
        while(1)
        {
            //用来发送数据
            //终端输入数据
            ssize_t ss = read(0, msg.text, sizeof(msg.text));
            //清除换行
            if (msg.text[strlen(msg.text) - 1] == '\n')
                msg.text[strlen(msg.text) - 1] = '\0';
            //将数据发送到服务器
            if (!strncmp(msg.text, "quit\n", 4))
            {
                msg.type = quit;
                sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, len);
                break;
            }
            else
            {
                //将标志改为chat
                msg.type = chat;
                if (sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, len) < 0)
                {
                    perror("sendto err");
                    return -1;
                }
            }
        }
        waitpid(-1, NULL, WNOHANG); //非阻塞
        exit(0);
    }
    close(sockfd);
    return 0;
}

头文件

#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 msg_t
{
    int type;
    char name[32];
    char text[128];
} MSG_t;



//链表节点结构体
typedef struct node_t
{
    struct sockaddr_in addr;
    struct node_t *next;
}list_t;



#endif

四、总结

本代码思路方便理解,当然还有很多的思路和功能可以添加,思路是活的,综合使用,逻辑跟方式可以就行,感谢你的观看!

  • 21
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值