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*)¤t->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.思维导图: