基于UDP的网络聊天室

三. 基于UDP的网络聊天室

项目需求:

  1. 如果有用户登录,其他用户可以收到这个人的登录信息
  2. 如果有人发送信息,其他用户可以收到这个人的群聊信息
  3. 如果有人下线,其他用户可以收到这个人的下线信息
  4. 服务器可以发送系统信息

linklist.h

#ifndef LINKLIST_H
#define LINKLIST_H
#include <myhead.h>

//定义数据类型
typedef struct datatype
{
    char usrName[20]; //用户名
    struct sockaddr_in cin;
} datatype, *datatype_ptr;

//定义结点类型
typedef struct Node
{

    int len;           //头结点数据域
    datatype data;     //普通结点数据域
    struct Node *next; //指针域
} Node, *NodePtr;

//给线程传送消息
struct qtr
{
    int sfd;
    struct sockaddr_in sin;
    NodePtr L;
};

//创建链表
NodePtr list_create();

//申请结点封装数据函数
NodePtr apply_node(datatype e);

//链表判空
int list_empty(NodePtr L);

//头插
int list_insert_head(NodePtr L, datatype e);

//链表遍历函数
int list_show(NodePtr L, char buf[128] );

//通过位置查找结点
NodePtr list_search_pos(NodePtr L, int pos);

//任意位置插入
int list_insert_pos(NodePtr L, int pos, datatype e);

//链表头删
int list_delete_head(NodePtr L);

//链表任意位置删除
int list_delete_pos(NodePtr L, int pos);

//链表按值查找返回位置
int list_search_value(NodePtr L, datatype e);

//链表按位置进行修改
int list_update_pos(NodePtr L, int pos, datatype e);

//按值进行修改
int list_update_value(NodePtr L, datatype old_e, datatype new_e);

//将链表进行翻转
void list_reverse(NodePtr L);

//释放链表
void list_destroy(NodePtr L);

#endif

linklist.c

#include "linklist.h"

//创建链表
NodePtr list_create()
{
    //只需要在堆区申请一个头结点
    NodePtr L = (NodePtr)malloc(sizeof(Node));
    if (NULL == L)
    {
        printf("创建失败\n");
        return NULL;
    }

    //程序执行至此,说明头结点创建结束
    L->len = 0;     //表示链表长度为0
    L->next = NULL; ///防止野指针

    printf("链表创建成功\n");
    return L;
}

//申请结点封装数据函数
NodePtr apply_node(datatype e)
{
    //在堆区申请一个结点的大小
    NodePtr p = (NodePtr)malloc(sizeof(Node));
    if (NULL == p)
    {
        printf("结点申请失败\n");
        return NULL;
    }

    //给结点内容赋值
    strncpy(p->data.usrName, e.usrName, sizeof(e.usrName));   // 复制用户名
    if (memcpy(&p->data.cin, &e.cin, sizeof(struct sockaddr_in)) == &p->data.cin) {
        printf("memcpy successful!\n");
    } else {
        printf("memcpy failed!\n");
    } // 复制sockaddr_in结构体

    p->next = NULL; //指针域

    return p;
}

//链表判空
int list_empty(NodePtr L)
{
    return L->next == NULL;
}

//头插
int list_insert_head(NodePtr L, datatype e)
{
    //判断逻辑
    if (NULL == L)
    {
        printf("链表不合法\n");
        return -1;
    }

    //申请结点封装数据
    NodePtr p = apply_node(e);
    if (NULL == p)
    {
        return -1;
    }

    //头插逻辑
    p->next = L->next;
    L->next = p;

    //表的变化
    L->len++;
    printf("头插成功\n");
    return 0;
}

//链表遍历函数
int list_show(NodePtr L,char buf[128])
{
    int sfd = socket(AF_INET, SOCK_DGRAM, 0);
    //判断逻辑
    if (NULL == L || list_empty(L))
    {
        printf("遍历失败\n");
        return -1;
    }
    //遍历逻辑
    NodePtr q = L->next; //定义遍历指针从第一个结点出发
    while (q != NULL)
    {
        //输出数据域
        //输出数据域
        sendto(sfd, buf, strlen(buf), 0, (struct sockaddr *)&q->data.cin, sizeof(q->data.cin));

        q = q->next; //指针向后偏移一个
    }
    printf("\n");
}

//通过位置查找结点
NodePtr list_search_pos(NodePtr L, int pos)
{
    //判断逻辑
    if (NULL == L || list_empty(L) || pos < 0 || pos > L->len)
    {
        printf("查找失败\n");
        return NULL;
    }

    //查找逻辑
    //定义遍历指针从头结点出发
    NodePtr q = L;
    for (int i = 0; i < pos; i++)
    {
        q = q->next;
    }

    return q; //将找到的结点地址返回
}

//任意位置插入
int list_insert_pos(NodePtr L, int pos, datatype e)
{
    //判断逻辑
    if (NULL == L || pos < 1 || pos > L->len + 1)
    {
        printf("插入失败\n");
        return -1;
    }

    //申请结点封装数据
    NodePtr p = apply_node(e);
    if (NULL == p)
    {
        return -1;
    }

    //调用函数查找前驱结点
    NodePtr q = list_search_pos(L, pos - 1);

    //插入逻辑
    p->next = q->next;
    q->next = p;

    //表的变化
    L->len++;
    printf("插入成功\n");
    return 0;
}

//链表头删
int list_delete_head(NodePtr L)
{
    //判断逻辑
    if (NULL == L || list_empty(L))
    {
        printf("删除失败\n");
        return -1;
    }

    //删除三部曲
    NodePtr p = L->next; //标记
    L->next = p->next;   //L->next->next  孤立
    free(p);             //释放
    p = NULL;

    //表长变化
    L->len--;

    printf("头删成功\n");
    return 0;
}

//链表任意位置删除
int list_delete_pos(NodePtr L, int pos)
{
    //判断逻辑
    if (NULL == L || list_empty(L) || pos < 1 || pos > L->len)
    {
        printf("删除失败\n");
        return -1;
    }

    //找到前驱结点
    NodePtr q = list_search_pos(L, pos - 1);

    //删除逻辑
    NodePtr p = q->next;     //标记
    q->next = q->next->next; //p->next 孤立
    free(p);                 //释放
    p = NULL;

    //表的变化
    L->len--;
    printf("删除成功\n");
    return 0;
}

//链表按值查找返回位置
int list_search_value(NodePtr L, datatype e)
{
    //判断逻辑
    if (NULL == L || list_empty(L))
    {
        printf("查找失败\n");
        return -1;
    }

    //查找逻辑
    //定义遍历指针从第一个结点出发
    NodePtr q = L->next;
    for (int index = 1; index <= L->len; index++)
    {
        //判断当前结点的值是否为要找的数据
        if (memcmp(&q->data.cin, &e.cin, sizeof(struct sockaddr_in)) == 0)
        {
            printf("找到了\n");
            return index;
        }

        q = q->next; //继续向后遍历
    }

    //程序执行至此,表示没找到
    printf("没找到\n");
    return -1;
}

//链表按位置进行修改
int list_update_pos(NodePtr L, int pos, datatype e)
{
    //判断逻辑
    if (NULL == L || list_empty(L) || pos < 1 || pos > L->len)
    {
        printf("修改失败\n");
        return -1;
    }

    //按位置查找逻辑
    NodePtr p = list_search_pos(L, pos);

    //修改逻辑
    p->data = e;

    printf("修改成功\n");
    return 0;
}

//按值进行修改
int list_update_value(NodePtr L, datatype old_e, datatype new_e)
{
    //判断逻辑
    if (NULL == L || list_empty(L))
    {
        printf("修改失败\n");
        return -1;
    }

    //按值查找位置
    int res = list_search_value(L, old_e);
    if (res == -1)
    {
        printf("没有要修改的值\n");
        return -1;
    }

    //按位置修改
    list_update_pos(L, res, new_e);

    printf("修改成功\n");
    return 0;
}

//将链表进行翻转
void list_reverse(NodePtr L)
{
    //判断逻辑
    if (NULL == L || L->len <= 1)
    {
        printf("翻转失败\n");
        return;
    }

    //翻转逻辑
    NodePtr H = L->next; //将链表元素进行托付

    L->next = NULL; //自己白手起家

    NodePtr p = NULL; //结点的搬运工

    while (H != NULL)
    {
        p = H;       //搬运第一个结点
        H = H->next; //头指针后移

        //将p以头插的方式放入L中
        p->next = L->next;
        L->next = p;
    }

    printf("翻转成功\n");
}

//释放链表
void list_destroy(NodePtr L)
{
    //判断逻辑
    if (NULL == L)
    {
        return;
    }

    //将所有结点进行释放
    while (!list_empty(L))
    {
        //头删
        list_delete_head(L);
    }

    //释放头结点
    free(L);
    L = NULL;

    printf("释放成功\n");
}

UDP_SER.c

#include <myhead.h>
#include "linklist.h"
#define SER_PORT 9999          //服务器端口号
#define SER_IP "192.168.0.108" //服务器ip地址
NodePtr L;
void *ser_end(void *arg)
{
    char buf_ser[128] = "";
    while (1)
    {
        fgets(buf_ser, sizeof(buf_ser), stdin);
        buf_ser[strlen(buf_ser) - 1] = 0;
        if (strcmp(buf_ser, "quit") == 0)
        {
            exit(0);
            break;
        }
        list_show(((struct qtr *)arg)->L, buf_ser);
    }
}

void *read_data(void *arg)
{
    //3、数据收发
    char buf[128] = "";

    struct sockaddr_in cin;          //接受对端地址信息
    socklen_t addrlen = sizeof(cin); //接受地址长度
    while (1)
    {
        //清空容器
        bzero(buf, sizeof(buf));

        //从套接字中读取数据
        recvfrom(((struct qtr *)arg)->sfd, buf, sizeof(buf), 0, (struct sockaddr *)&cin, &addrlen);
        if (strcmp(buf, "connect") == 0)
        {
            datatype e = {"name", cin};
            list_insert_head(((struct qtr *)arg)->L, e);
           list_show(((struct qtr *)arg)->L, buf);
        }
        else if (strcmp(buf, "quit") == 0)
        {
            snprintf(buf, sizeof(buf), "[%s:%d]: 成功下线", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));
            list_show(((struct qtr *)arg)->L, buf);
            datatype e;
            memcpy(&e.cin, &cin, sizeof(struct sockaddr_in)); // 复制sockaddr_in结构体
            int pos = list_search_value(((struct qtr *)arg)->L, e);
            list_delete_pos(((struct qtr *)arg)->L, pos);
            printf("%s\n", buf);
            list_show(((struct qtr *)arg)->L, buf);
        }
        else
        {
            printf("%s\n", buf);
            list_show(((struct qtr *)arg)->L, buf);
        }
    }

    //4、关闭文件描述符
    close(((struct qtr *)arg)->sfd);
}

int main(int argc, const char *argv[])
{
    L = list_create();
    char buf_ser[128] = "";

    //1、创建用于通信的服务器套接字文件描述符
    int sfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sfd == -1)
    {
        perror("socket error");
        return -1;
    }
    printf("sfd = %d\n", sfd); //3

    //将端口号快速重用
    int reuse = 1;
    if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
    {
        perror("setsockopt error");
        return -1;
    }
    printf("端口号快速重用成功\n");

    //2、为套接字绑定ip地址和端口号
    //2.1 填充地址信息结构体
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;                //通信域
    sin.sin_port = htons(SER_PORT);          //端口号
    sin.sin_addr.s_addr = inet_addr(SER_IP); //ip地址

    //2.2 绑定工作
    if (bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) == -1)
    {
        perror("bind error");
        return -1;
    }
    printf("bind success\n");
    struct qtr A = {sfd, sin, L};
    pthread_t tid;
    pthread_create(&tid, NULL, read_data, &A);
    pthread_t end_id;
    pthread_create(&end_id, NULL, ser_end, &A);
    pthread_join(tid, NULL);

    return 0;
}

UDP_CLI.c

#include <myhead.h>

#define SER_PORT 9999          //服务器端口号
#define SER_IP "192.168.0.108" //服务器ip地址
#define CLI_PORT 5555          //客户端端口号
#define CLI_IP "192.168.0.108" //客户端ip地址
void *read_data(void *arg)
{
    char buf[128];
    while (1)
    {
        //接受服务器发来的消息
        recvfrom(*((int *)arg), buf, sizeof(buf), 0, NULL, NULL);
        printf("%s\n", buf);
        //清空容器
        bzero(buf, sizeof(buf));
    }
}
int main(int argc, const char *argv[])
{
    //1、创建用于通信的服务器套接字文件描述符
    int cfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (cfd == -1)
    {
        perror("socket error");
        return -1;
    }
    printf("sfd = %d\n", cfd); //3

    //将端口号快速重用
    int reuse = 1;
    if (setsockopt(cfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
    {
        perror("setsockopt error");
        return -1;
    }
    printf("端口号快速重用成功\n");

    //2、为套接字绑定ip地址和端口号
    //2.1 填充地址信息结构体
    struct sockaddr_in cin;
    cin.sin_family = AF_INET;                //通信域
    cin.sin_port = htons(CLI_PORT);          //端口号
    cin.sin_addr.s_addr = inet_addr(CLI_IP); //ip地址

    //2.2 绑定工作
    if (bind(cfd, (struct sockaddr *)&cin, sizeof(cin)) == -1)
    {
        perror("bind error");
        return -1;
    }
    printf("bind success\n");

    //3、数据收发
    char buf[256] = "";
    char buf_fgets[128] = "";
    //填充服务器的地址信息结构体
    struct sockaddr_in sin;                  //接受对端地址信息
    sin.sin_family = AF_INET;                //服务器的通信域
    sin.sin_port = htons(SER_PORT);          //服务器端口号
    sin.sin_addr.s_addr = inet_addr(SER_IP); //服务器ip地址

    //申请连接
    strcpy(buf, "connect");

    sendto(cfd, buf, strlen(buf), 0, (struct sockaddr *)&sin, sizeof(sin));
    //接受服务器发来的消息
    recvfrom(cfd, buf, sizeof(buf), 0, NULL, NULL);
    bzero(buf, sizeof(buf));

    //连接成功信号
    snprintf(buf, sizeof(buf), "[%s:%d]: 已成功连接\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));

    sendto(cfd, buf, strlen(buf), 0, (struct sockaddr *)&sin, sizeof(sin));
    bzero(buf, sizeof(buf));
    //接受服务器发来的消息
    recvfrom(cfd, buf, sizeof(buf), 0, NULL, NULL);

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

    pthread_t tid;
    pthread_create(&tid, NULL, read_data, &cfd);
    while (1)
    {
        bzero(buf, sizeof(buf));
        //从终端获取数据
        printf("请输入>>>>\n");
        fgets(buf_fgets, sizeof(buf_fgets), stdin);
        buf_fgets[strlen(buf_fgets) - 1] = 0;
        if (strcmp(buf_fgets, "quit") == 0)
        {
            strcpy(buf, buf_fgets);
            //将数据发送给服务器
            sendto(cfd, buf, strlen(buf), 0, (struct sockaddr *)&sin, sizeof(sin));
            break;
        }
        snprintf(buf, sizeof(buf), "[%s:%d]: ", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));
        strcat(buf, buf_fgets);

        //将数据发送给服务器
        sendto(cfd, buf, strlen(buf), 0, (struct sockaddr *)&sin, sizeof(sin));
    }

    //4、关闭文件描述符
    close(cfd);

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值