网络编程作业4

1. 基于UDP的网络聊天室

项目需求:

1.如果有用户登录,其他用户可以收到这个人的登录信息

2.如果有人发送信息,其他用户可以收到这个人的群聊信息

3.如果有人下线,其他用户可以收到这个人的下线信息

4.服务器可以发送系统信息

代码:

chat_ser.c:

#include <myhead.h>

// 定义链表节点结构
typedef struct Node {
    int len;  // 消息长度
    char name[128];  // 用户名
    struct sockaddr_in cin;  // 用户的地址信息
    struct Node* next;  // 指向下一个节点
} No, *NodePtr;

// 定义聊天列表结构
struct chatlist {
    int sfd;  // 套接字描述符
    char name[128];  // 用户名
    char text[128];  // 消息文本
    struct sockaddr_in sin;  // 服务器地址信息
    NodePtr L;  // 用户链表头指针
};

// 定义结构体,用于传递参数到线程
struct NB {
    int sfd;  // 套接字描述符
    char name[128];  // 用户名
    char text[128];  // 消息文本
    struct sockaddr_in sin;  // 服务器地址信息
    NodePtr L;  // 用户链表头指针
};

// 创建用户链表
NodePtr create() {
    NodePtr head = (NodePtr)malloc(sizeof(No));  // 分配内存
    if (head) {
        head->next = NULL;  // 初始化链表为空
    }
    return head;  // 返回链表头指针
}

// 添加用户到链表
void add(NodePtr L, char* name, struct sockaddr_in cin) {
    NodePtr newNode = (NodePtr)malloc(sizeof(No));  // 创建新节点
    if (newNode) {
        strcpy(newNode->name, name);  // 复制用户名
        newNode->cin = cin;  // 复制用户地址信息
        newNode->next = L->next;  // 将新节点插入链表
        L->next = newNode;  // 更新链表头指针
    }
}

// 显示在线用户
void show_user(NodePtr L) {
    NodePtr current = L->next;  // 从链表头开始遍历
    printf("当前在线用户:\n");
    while (current) {
        printf("%s\n", current->name);  // 打印用户名
        current = current->next;  // 移动到下一个节点
    }
}

// 查找用户
NodePtr find(NodePtr L, struct sockaddr_in cin) {
    NodePtr current = L->next;  // 从链表头开始遍历
    while (current) {
        // 如果找到匹配的用户地址
        if (current->cin.sin_addr.s_addr == cin.sin_addr.s_addr && current->cin.sin_port == cin.sin_port) {
            return current;  // 返回找到的节点
        }
        current = current->next;  // 移动到下一个节点
    }
    return NULL;  // 未找到用户,返回NULL
}

// 从链表中删除用户
void list_delete(NodePtr L, struct sockaddr_in cin) {
    NodePtr current = L;  // 当前节点指向链表头
    while (current->next) {
        // 如果找到匹配的用户地址
        if (current->next->cin.sin_addr.s_addr == cin.sin_addr.s_addr && current->next->cin.sin_port == cin.sin_port) {
            NodePtr temp = current->next;  // 保存要删除的节点
            current->next = temp->next;  // 更新链表
            free(temp);  // 释放内存
            return;  // 删除成功,返回
        }
        current = current->next;  // 移动到下一个节点
    }
}

// 广播消息给所有用户
void list_sendto(NodePtr L, int sfd, char* message) {
    NodePtr current = L->next;  // 从链表头开始遍历
    while (current!=NULL) {
        // 发送消息到当前用户
        sendto(sfd, message, strlen(message), 0, (struct sockaddr*)&current->cin, sizeof(current->cin));
        current = current->next;  // 移动到下一个节点
    }
}



// 处理接收消息的线程
void* stak1(void* arg) {
    NodePtr P = NULL;  // 用于保存找到的用户节点
    NodePtr Q = NULL;  // 用于保存找到的用户节点
    NodePtr L = (*(struct NB*)arg).L;  // 获取用户链表头指针
    char rbuf[128] = "";  // 接收缓冲区
    char name[128] = "";  // 用户名缓冲区
    char crr[128] = "";  // 消息缓冲区
    int sfd = (*(struct NB*)arg).sfd;  // 获取套接字描述符
    struct sockaddr_in sin = (*(struct NB*)arg).sin;  // 获取服务器地址信息
    socklen_t addrlen = sizeof(sin);  // 地址结构体大小

    while (1) {
        bzero(rbuf, sizeof(rbuf));  // 清空接收缓冲区
        // 接收消息
        if (recvfrom(sfd, rbuf, sizeof(rbuf), 0, (struct sockaddr*)&sin, &addrlen) == -1) {
            perror("recv");  // 输出错误信息
            pthread_exit(NULL);  // 退出线程
        }
        // 检查用户是否存在
        int a = find(L, sin) ? 1 : 0; 
        if (a == 0) {  // 用户不存在
            bzero(name, sizeof(name));  // 清空用户名缓冲区
            strcpy(name, rbuf);  // 复制用户名
            strcat(name, "上线");  // 添加上线提示
            printf("%s\n", name);  // 打印上线信息
            list_sendto(L, sfd, name);  // 广播上线消息
            add(L, rbuf, sin);  // 添加用户到链表
            show_user(L);  // 显示当前在线用户
        } else if (a > 0) {  // 用户已存在
            if (strcmp(rbuf, "quit") == 0) {  // 检查是否退出
                P = find(L, sin);  // 查找用户节点
                char brr[128] = "已经下线";  // 下线提示
                bzero(name, sizeof(name));  // 清空用户名缓冲区
                strcpy(name, P->name);  // 复制用户名
                strcat(name, brr);  // 添加下线提示
                list_delete(L, sin);  // 从链表中删除用户
                printf("%s\n", name);  // 打印下线信息
                list_sendto(L, sfd, name);  // 广播下线消息
                show_user(L); // 显示在线用户
                P = NULL;  // 清空指针
            } else {
                Q = find(L, sin);  // 查找用户节点
                strcpy(crr, Q->name);  // 复制用户名
                strcat(crr, ":");  // 添加分隔符
                strcat(crr, rbuf);  // 添加消息内容
                printf("%s\n", crr);  // 打印消息
                list_sendto(L, sfd, crr);  // 广播消息
                Q = NULL;  // 清空指针
            }
        }
    }
    pthread_exit(NULL);  // 退出线程
}

// 处理发送消息的线程
void* stak2(void* arg) {
    char wrr[128] = "";  // 消息缓冲区
    NodePtr L = (*(struct NB*)arg).L;  // 获取用户链表头指针
    int sfd = (*(struct NB*)arg).sfd;  // 获取套接字描述符
    char buf[128] = "";  // 输入缓冲区

    while (1) {
        strcpy(wrr, "服务器:");  // 消息前缀
        bzero(buf, sizeof(buf));  // 清空输入缓冲区
        fgets(buf, sizeof(buf), stdin);  // 从标准输入读取消息
        buf[strlen(buf) - 1] = 0;  // 去除换行符
        strcat(wrr, buf);  // 添加用户输入的消息
        list_sendto(L, sfd, wrr);  // 广播消息
    }
    pthread_exit(NULL);  // 退出线程
}

// 主函数
int main(int argc, const char* argv[]) {
    NodePtr L = create();  // 创建用户链表
    if (L != NULL) {
        printf("链表创建完成\n");
    }

    // 创建UDP套接字
    int sfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sfd == -1) {
        perror("socket");  // 输出错误信息
        return -1;  // 退出程序
    }
    printf("Socket创建成功\n");

    // 设置套接字选项,允许地址重用
    int reuse = 1;
    if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1) {
        perror("reuse");  // 输出错误信息
        return -1;  // 退出程序
    }
    printf("快速重启设置成功\n");

    // 初始化服务器地址结构体
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;  // 使用IPv4地址
    sin.sin_port = htons(6666);  // 设置端口号6666
    sin.sin_addr.s_addr = inet_addr("192.168.2.153");  // 设置服务器IP地址

    // 绑定套接字
    if (bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) == -1) {
        perror("bind");  // 输出错误信息
        return -1;  // 退出程序
    }
    printf("绑定成功\n");

    // 创建结构体实例并初始化
    struct NB nb = { sfd, "", "", sin, L };
    pthread_t tid1, tid2;  // 定义线程ID

    // 创建接收消息线程
    if (pthread_create(&tid1, NULL, stak1, &nb) == -1) {
        perror("create1");  // 输出错误信息
        return -1;  // 退出程序
    }

    // 创建发送消息线程
    if (pthread_create(&tid2, NULL, stak2, &nb) == -1) {
        perror("create2");  // 输出错误信息
        return -1;  // 退出程序
    }

    // 等待线程结束
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    close(sfd);  // 关闭套接字
    return 0;  // 正常结束程序
}

chat_cli:

#include <myhead.h>

#define SERVER_IP "192.168.2.153" // 服务器IP地址
#define SERVER_PORT 6666 // 服务器端口号
#define BUFFER_SIZE 128 // 缓冲区大小

// 用户结构体,包含用户名和套接字信息
struct USER {
    char name[20]; // 用户名
    int sfd; // 套接字描述符
    struct sockaddr_in server_addr; // 服务器地址信息
};

// 发送消息线程
void* send_message(void* arg) {
    struct USER* user = (struct USER*)arg; // 获取用户信息
    char message[BUFFER_SIZE]; // 消息缓冲区

    while (1) {
        // 从标准输入读取消息
        fgets(message, sizeof(message), stdin);
        message[strcspn(message, "\n")] = 0; // 去掉换行符

        // 发送消息到服务器
        if (sendto(user->sfd, message, strlen(message), 0, (struct sockaddr*)&user->server_addr, sizeof(user->server_addr)) == -1) {
            perror("sendto"); // 输出错误信息
            break; // 发送失败,退出循环
        }

        // 检查是否退出
        if (strcmp(message, "quit") == 0) {
            printf("退出成功\n");
            break; // 退出循环
        }
    }

    pthread_exit(NULL); // 退出线程
}

// 接收消息线程
void* receive_message(void* arg) {
    struct USER* user = (struct USER*)arg; // 获取用户信息
    char buffer[BUFFER_SIZE]; // 接收缓冲区
    struct sockaddr_in from_addr; // 发送者地址信息
    socklen_t addr_len = sizeof(from_addr); // 地址结构体大小

    while (1) {
        // 接收消息
        bzero(buffer, sizeof(buffer)); // 清空接收缓冲区
        if (recvfrom(user->sfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&from_addr, &addr_len) == -1) {
            perror("recvfrom"); // 输出错误信息
            break; // 接收失败,退出循环
        }

        // 打印接收到的消息
        printf("%s\n", buffer);
    }

    pthread_exit(NULL); // 退出线程
}

// 主函数
int main() {
    struct USER user; // 用户信息结构体
    user.sfd = socket(AF_INET, SOCK_DGRAM, 0); // 创建UDP套接字
    if (user.sfd == -1) {
        perror("socket"); // 输出错误信息
        return -1; // 退出程序
    }

    // 设置服务器地址信息
    user.server_addr.sin_family = AF_INET; // 使用IPv4
    user.server_addr.sin_port = htons(SERVER_PORT); // 设置服务器端口
    user.server_addr.sin_addr.s_addr = inet_addr(SERVER_IP); // 设置服务器IP地址

    // 输入用户名
    printf("请输入姓名: ");
    fgets(user.name, sizeof(user.name), stdin);
    user.name[strcspn(user.name, "\n")] = 0; // 去掉换行符

    // 发送用户名到服务器
    if (sendto(user.sfd, user.name, sizeof(user.name), 0, (struct sockaddr*)&user.server_addr, sizeof(user.server_addr)) == -1) {
        perror("sendto"); // 输出错误信息
        close(user.sfd); // 关闭套接字
        return -1; // 退出程序
    }
    printf("登录成功!\n");

    // 创建发送和接收线程
    pthread_t send_tid, recv_tid;
    if (pthread_create(&send_tid, NULL, send_message, (void*)&user) != 0) {
        perror("pthread_create"); // 输出错误信息
        close(user.sfd); // 关闭套接字
        return -1; // 退出程序
    }

    if (pthread_create(&recv_tid, NULL, receive_message, (void*)&user) != 0) {
        perror("pthread_create"); // 输出错误信息
        close(user.sfd); // 关闭套接字
        return -1; // 退出程序
    }

    // 等待线程结束
    pthread_join(send_tid, NULL);
    pthread_join(recv_tid, NULL);

    close(user.sfd); // 关闭套接字
    return 0; // 正常结束程序
}

运行结果:

2.思维导图:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值