网络编程 8/15 基于UDP多人聊天室

//客户端代码
#include <myhead.h>
struct msgType
{
    char type; // 消息类型L:登录,Q:退出,C:聊天
    char usrName[20];
    char msgText[1024];
};
#define SER_PORT 6666          // 服务器端口
#define SER_IP "192.168.2.161" // 服务器IP
int main(int argc, char const *argv[])
{
    int cfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (cfd == -1)
    {
        perror("socket error");
        return -1;
    }
    // 端口号快速重用
    int reuse = 1;
    if (setsockopt(cfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
    {
        perror("setsockopt error");
        return -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地址
    socklen_t len = sizeof(sin);             // 地址长度

    struct msgType msg;
    msg.type = 'L';
    printf("请输入用户名:");
    fgets(msg.usrName, 20, stdin);
    msg.usrName[strlen(msg.usrName) - 1] = '\0';
    char temp[20]; // 将用户名保存起来
    strcpy(temp, msg.usrName);
    strcpy(msg.msgText, "");
    sendto(cfd, &msg, sizeof(msg), 0, (struct sockaddr *)&sin, len);

    // 创建子进程用于收消息
    pid_t pid = fork();
    if (pid == -1)
    {
        perror("fork error");
        return -1;
    }

    if (pid == 0)
    {
        // 子进程
        {
            while (1)
            {
                bzero(&msg, sizeof(msg));
                // 接收消息
                recvfrom(cfd, &msg, sizeof(msg), 0, NULL, NULL);
                switch (msg.type)
                {
                case 'L':
                    printf("%s\n", msg.msgText);
                    break;
                case 'Q':
                {
                    if (strcmp(temp, msg.usrName) == 0)
                    {
                        printf("退出成功\n");
                        exit(EXIT_SUCCESS);
                    }
                    printf("%s\n", msg.msgText);
                }
                break;
                case 'C':
                    printf("[%s]:%s\n", msg.usrName, msg.msgText);
                    break;
                }
            }
        }
    }
    else if (pid > 0)
    {
        // 父进程
        msg.type = 'C';
        while (1)
        {
            sleep(1);
            // 发送消息
            fgets(msg.msgText, 1024, stdin);
            msg.msgText[strlen(msg.msgText) - 1] = '\0';
            if (strcmp(msg.msgText, "q") == 0 || strcmp(msg.msgText, "Q") == 0)
            {
                msg.type = 'Q';
                sendto(cfd, &msg, sizeof(msg), 0, (struct sockaddr *)&sin, len);
                sleep(2);
                goto end;
            }

            sendto(cfd, &msg, sizeof(msg), 0, (struct sockaddr *)&sin, len);
        }
    }

end:
    kill(pid, SIGKILL);
    wait(NULL);
    close(cfd);

    return 0;
}
//服务器主代码
#include "head.h"
#define SER_PORT 6666          // 服务器端口
#define SER_IP "192.168.2.161" // 服务器IP
int main(int argc, char const *argv[])
{
    // 创建用于通信的服务器套接字文件描述符
    int sfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sfd == -1)
    {
        perror("socket error");
        return 1;
    }
    // 将端口号快速重启
    int reuse = 1;
    if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
    {
        perror("setsockopt error");
        return 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);
    if (bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) == -1)
    {
        perror("bind error");
        return 1;
    }
    printf("服务器启动成功\n");
    // 创建链表
    NodePtr L = list_create();
    // 接收客户端发送的数据
    struct sockaddr_in cin;
    socklen_t len = sizeof(cin);
    struct msgType msg;
    bzero(&msg, sizeof(msg));
    while (1)
    {
        // 创建子进程
        pid_t pid = fork();
        // 接收客户端发送的数据
        if (pid == 0)
        {
            while (1)
            {
                // 接收信息
                recvfrom(sfd, &msg, sizeof(msg), 0, (struct sockaddr *)&cin, &len);
                switch (msg.type) // 判断信息类型
                {
                case 'L':
                {
                    list_insert(L, msg.usrName, &cin);
                    strcpy(msg.msgText, "欢迎您进入聊天室 开始聊天吧"); // 发送欢迎信息
                    sendto(sfd, &msg, sizeof(msg), 0, (struct sockaddr *)&cin, len);

                    strcpy(msg.msgText, "*******欢迎");
                    strcat(msg.msgText, msg.usrName);
                    strcat(msg.msgText, "进入聊天室*******");
                    NodePtr p = L->next;
                    while (p != NULL) // 发送所有人进入聊天室信息(除了刚进来的人 因为已经发了欢迎)
                    {

                        if (p->addr.sin_port == cin.sin_port)
                        {
                            p = p->next;
                            continue;
                        }
                        if (p == NULL)
                        {
                            break;
                        }
                        sendto(sfd, &msg, sizeof(msg), 0, (struct sockaddr *)&p->addr, len);
                        p = p->next;
                    }
                }
                break;
                case 'C':
                {
                    printf("[%s]:%s\n", msg.usrName, msg.msgText); // 先在终端展示
                    for (NodePtr p = L->next; p != NULL; p = p->next)
                    {
                        sendto(sfd, &msg, sizeof(msg), 0, (struct sockaddr *)&p->addr, len);
                    }
                    printf("发送成功\n");
                }
                break;
                case 'Q':
                {
                    strcpy(msg.msgText, "*********");
                    strcat(msg.msgText, msg.usrName);
                    strcat(msg.msgText, "退出聊天室*********");
                    for (NodePtr p = L->next; p != NULL; p = p->next)
                    {
                        sendto(sfd, &msg, sizeof(msg), 0, (struct sockaddr *)&p->addr, len);
                    }
                    sleep(1);
                    list_delete(L, msg.usrName);
                }
                break;
                }
            }
        }
        else if (pid > 0)
        {
            // 发送数据给客户端

            while (1)
            {
                msg.type = 'C';
                strcpy(msg.usrName, "GM");
                fgets(msg.msgText, sizeof(msg.msgText), stdin);
                msg.msgText[strlen(msg.msgText) - 1] = '\0';
                sendto(sfd, &msg, sizeof(msg), 0, (struct sockaddr *)&sin, len);
                if (strcmp(msg.msgText, "exit") == 0)
                {
                    goto end;
                }
            }
        }
        else if (pid < 0)
        {
            perror("fork error");
            return 1;
        }
    end:
        kill(pid, SIGKILL);
        list_destroy(L);
        close(sfd);
        wait(NULL);
        return 0;
    }
}
//头文件
#include <myhead.h>
#ifndef __HEAD_H__
#define __HEAD_H__
// 定义结构体

typedef struct Node
{

    union
    {
        int len;       // 节点长度
        char name[20]; // 节点名称
    };
    struct sockaddr_in addr; // 地址信息结构体
    struct Node *next;       // 指向下一个节点
} Node, *NodePtr;

struct msgType
{
    char type;          // 消息类型 L:登录,C:聊天,Q;退出
    char usrName[20];   // 姓名
    char msgText[1024]; // 消息
};
// 创建链表
NodePtr list_create();
// 申请封装节点函数
NodePtr apply_node(char *name, struct sockaddr_in *addr);
// 链表判空
int list_empty(NodePtr L);
// 链表插入
int list_insert(NodePtr L, char *name, struct sockaddr_in *addr);


// 按名字找位置函数
int list_search_name(NodePtr L, char *name);
// 任意位置查找节点
NodePtr list_search_pos(NodePtr L, int pos);
// 任意位置删
int list_delete(NodePtr L, char *name);

// 链表销毁
void list_destroy(NodePtr L);

#endif

//函数文件
#include "head.h"
// 创建链表
NodePtr list_create()
{
    // 申请头结点
    NodePtr L = (NodePtr)malloc(sizeof(Node));
    if (NULL == L)
    {
        printf("创建失败\n");
        return NULL;
    }
    // 赋值
    L->len = 0;
    L->next = NULL;
    printf("聊天室创建成功\n");
    return L;
}
// 申请封装节点函数
NodePtr apply_node(char *name, struct sockaddr_in *addr)
{
    // 申请空间
    NodePtr p = (NodePtr)malloc(sizeof(Node));
    if (NULL == p)
    {
        printf("申请失败\n");
        return NULL;
    }
    // 赋值
    strcpy(p->name, name);
    p->addr.sin_family = addr->sin_family;
    p->addr.sin_port = addr->sin_port;
    p->addr.sin_addr.s_addr = addr->sin_addr.s_addr;
    p->next = NULL;
    
    return p;
}
// 链表判空
int list_empty(NodePtr L)
{   
    return L->next == NULL;
}
// 链表插入
int list_insert(NodePtr L, char *name, struct sockaddr_in *addr)
{
    // 判断逻辑
    if (NULL == L)
    {
        return -1;
    }
    
    // 申请节点
    NodePtr p = apply_node(name, addr);
    if (NULL == p)
    {
        return -1;
    }
    // 插入逻辑
    p->next = L->next;
    L->next = p;
    printf("*********%s进入聊天室*********\n", p->name);
    L->len++;
    return 0;
}
// 按名字找位置函数
int list_search_name(NodePtr L, char *name)
{
    // 判断逻辑
    if (NULL == L || list_empty(L))
    {
        return -1;
    }
    // 遍历逻辑
    NodePtr q = L->next;
    for (int index = 1;index<=L->len; index++)
    {
        if(strcmp(q->name, name) == 0)
        {
            return index;
            
        }
        q = q->next;
    }
    printf("未找到该用户\n");
    return -1;
}
// 任意位置查找节点
NodePtr list_search_pos(NodePtr L, int pos)
{
    if (NULL == L || list_empty(L) || pos < 0 || pos > L->len)
    {
        return NULL;
    }
    //查找逻辑
    //定义指针从头结点出发
    NodePtr q = L;
    for (int i = 0; i < pos; i++)
    {
        q = q->next;
    }
    return q; //返回找到的节点
}
// 任意明治删
int list_delete(NodePtr L, char *name)
{
    // 判断逻辑)
    if (NULL == L || list_empty(L))
    {
        return -1;
    }
    // 找到前一个节点
    int pos = list_search_name(L, name);
    NodePtr q = list_search_pos(L, pos-1);
    // 删除逻辑
    NodePtr p = q->next;
    q->next = p->next;
    // 释放空间
    free(p);
    p = NULL;
    printf("*********%s退出聊天室*********\n", name);
    return 0;
}

// 链表销毁
void list_destroy(NodePtr L)
{
    // 判断逻辑
    if (NULL == L)
    {
        return;
    }
    // 遍历逻辑
    while (!list_empty(L))
    {
        list_delete(L, L->next->name);
    }
    // 释放头结点
    free(L);
    L = NULL;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值