udp.h
#ifndef _UDP_H_
#define _UDP_H_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define M 32 // 用户名最大长度
#define N 1024 // 消息内容最大长度
#define ERRLOG(msg) do { perror(msg); exit(-1); } while(0)
// 消息结构体(包含操作码、用户名、消息内容)
typedef struct {
char code; // 操作码:'d'登录 'q'群聊 't'退出
char user[M]; // 用户名
char text[N]; // 消息内容(群聊内容/系统消息)
} msg_t;
// 链表节点结构体(存储客户端地址信息)
typedef struct node {
struct sockaddr_in addr; // 客户端socket地址
struct node *next; // 指向下一个节点
} node_t;
#endif
server.c
#include "udp.h"
// 创建链表头节点
int create_node(node_t **phead) {
*phead = (node_t *)malloc(sizeof(node_t));
if (*phead == NULL) {
ERRLOG("内存分配失败");
}
(*phead)->next = NULL;
return 0;
}
// 尾插法插入客户端节点
int insert_node(node_t *phead, struct sockaddr_in addr) {
node_t *pnew = NULL;
create_node(&pnew);
pnew->addr = addr;
node_t *ptemp = phead;
while (ptemp->next != NULL) {
ptemp = ptemp->next;
}
ptemp->next = pnew;
return 0;
}
// 删除指定客户端节点
int delete_node(node_t *phead, struct sockaddr_in addr) {
node_t *prev = phead;
node_t *curr = phead->next;
while (curr != NULL) {
if (memcmp(&curr->addr, &addr, sizeof(addr)) == 0) {
prev->next = curr->next;
free(curr);
return 0;
}
prev = curr;
curr = curr->next;
}
return -1;
}
int main(int argc, const char *argv[]) {
if (argc != 3) {
printf("Usage: %s <IP> <port>\n", argv[0]);
return -1;
}
// 创建UDP套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == -1) {
ERRLOG("socket创建失败");
}
// 绑定服务器地址
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
if (bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) == -1) {
ERRLOG("bind失败");
}
printf("服务器启动,监听 %s:%d\n", argv[1], atoi(argv[2]));
// 初始化客户端链表
node_t *phead;
create_node(&phead);
pid_t pid = fork();
if (pid < 0) {
ERRLOG("fork失败");
} else if (pid == 0) { // 子进程:处理客户端消息接收与广播
struct sockaddr_in clientaddr;
socklen_t client_len = sizeof(clientaddr);
msg_t msg;
while (1) {
memset(&msg, 0, sizeof(msg));
if (recvfrom(sockfd, &msg, sizeof(msg), 0,
(struct sockaddr *)&clientaddr, &client_len) == -1) {
ERRLOG("recvfrom失败");
}
switch (msg.code) {
case 'd': // 登录处理
printf("[%s] 上线\n", msg.user);
insert_node(phead, clientaddr); // 添加到在线列表
// 广播登录通知(排除自己)
node_t *p = phead->next;
while (p != NULL) {
if (memcmp(&p->addr, &clientaddr, sizeof(clientaddr)) != 0) {
sendto(sockfd, &msg, sizeof(msg), 0,
(struct sockaddr *)&p->addr, sizeof(p->addr));
}
p = p->next;
}
break;
case 'q': // 群聊处理
if (strcmp(msg.user, "管理员") != 0) { // 非管理员消息打印
printf("[%s]: %s\n", msg.user, msg.text);
}
// 广播群聊消息(包括自己,UDP无连接需全播)
node_t *q = phead->next;
while (q != NULL) {
sendto(sockfd, &msg, sizeof(msg), 0,
(struct sockaddr *)&q->addr, sizeof(q->addr));
q = q->next;
}
break;
case 't': // 退出处理
printf("[%s] 下线\n", msg.user);
delete_node(phead, clientaddr); // 从列表删除
// 广播退出通知
node_t *r = phead->next;
while (r != NULL) {
sendto(sockfd, &msg, sizeof(msg), 0,
(struct sockaddr *)&r->addr, sizeof(r->addr));
r = r->next;
}
break;
}
}
} else { // 父进程:发送系统消息(以"管理员"身份)
msg_t sys_msg;
while (1) {
memset(&sys_msg, 0, sizeof(sys_msg));
strcpy(sys_msg.user, "管理员");
fgets(sys_msg.text, N, stdin);
sys_msg.text[strlen(sys_msg.text) - 1] = '\0'; // 去除换行符
sys_msg.code = 'q'; // 系统消息以群聊码发送
// 广播系统消息给所有在线客户端
node_t *s = phead->next;
while (s != NULL) {
sendto(sockfd, &sys_msg, sizeof(sys_msg), 0,
(struct sockaddr *)&s->addr, sizeof(s->addr));
s = s->next;
}
}
}
close(sockfd);
return 0;
}
client.c
#include "udp.h"
int main(int argc, const char *argv[]) {
if (argc != 3) {
printf("Usage: %s <服务器IP> <端口>\n", argv[0]);
return -1;
}
// 创建UDP套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == -1) {
ERRLOG("socket创建失败");
}
// 服务器地址
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t server_len = sizeof(serveraddr);
// 登录:发送用户名
msg_t msg;
char username[M] = {0};
printf("请输入用户名(退出输入quit): ");
fgets(username, M, stdin);
username[strlen(username) - 1] = '\0'; // 去除换行符
if (strcmp(username, "quit") == 0) {
printf("未登录,退出程序\n");
close(sockfd);
return 0;
}
msg.code = 'd';
strcpy(msg.user, username);
if (sendto(sockfd, &msg, sizeof(msg), 0,
(struct sockaddr *)&serveraddr, server_len) == -1) {
ERRLOG("登录消息发送失败");
}
pid_t pid = fork();
if (pid < 0) {
ERRLOG("fork失败");
} else if (pid == 0) { // 子进程:接收服务器广播消息
msg_t recv_msg;
while (1) {
memset(&recv_msg, 0, sizeof(recv_msg));
if (recvfrom(sockfd, &recv_msg, sizeof(recv_msg), 0,
(struct sockaddr *)&serveraddr, &server_len) == -1) {
ERRLOG("recvfrom失败");
}
// 过滤自己的登录/退出通知(UDP广播会收到自己的消息)
if (strcmp(recv_msg.user, username) == 0 &&
(recv_msg.code == 'd' || recv_msg.code == 't')) {
continue;
}
switch (recv_msg.code) {
case 'd': printf("[%s] 登录上线\n", recv_msg.user); break;
case 'q': printf("[%s]: %s\n", recv_msg.user, recv_msg.text); break;
case 't': printf("[%s] 退出聊天\n", recv_msg.user); break;
}
}
} else { // 父进程:发送群聊消息或退出
while (1) {
memset(msg.text, 0, N);
fgets(msg.text, N, stdin);
msg.text[strlen(msg.text) - 1] = '\0'; // 去除换行符
if (strcmp(msg.text, "quit") == 0) { // 退出操作
msg.code = 't';
sendto(sockfd, &msg, sizeof(msg), 0,
(struct sockaddr *)&serveraddr, server_len);
break;
} else { // 群聊消息
msg.code = 'q';
sendto(sockfd, &msg, sizeof(msg), 0,
(struct sockaddr *)&serveraddr, server_len);
}
}
}
close(sockfd);
return 0;
}