参考网上代码
接收端
#include<myhead.h>
#define PRINT_ERR(msg) \
do \
{ \
printf("%s,%d,%s\n", __FILE__, __LINE__, __func__); \
perror(msg); \
exit(-1); \
} while (0)
typedef struct
{
char code; // 操作码 'L' 登录 'C' 群聊 'Q' 退出
char name[32];
char txt[128];
} msg_t;
// 链表结构体
typedef struct _NODE
{
struct sockaddr_in c_addr;
struct _NODE *next;
} node_t;
void creat_link(node_t **head);
int do_register(int sockfd, msg_t msg, struct sockaddr_in clientaddr, node_t *phead);
int do_group_chat(int sockfd, msg_t msg, struct sockaddr_in clientaddr, node_t *phead);
int quit_group_chat(int sockfd, msg_t msg, struct sockaddr_in clientaddr, node_t *phead);
int main(int argc, const char *argv[])
{
// 入参合理性判断
if (argc != 3)
{
printf("age:%s ip port\n", argv[0]);
return -1;
}
// 创建套接字
int sockfd;
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
PRINT_ERR("socket error");
}
// 创建服务器网络信息结构体
struct sockaddr_in serviceaddr;
memset(&serviceaddr, 0, sizeof(serviceaddr));
serviceaddr.sin_family = AF_INET;
serviceaddr.sin_addr.s_addr = inet_addr(argv[1]);
serviceaddr.sin_port = htons(atoi(argv[2]));
socklen_t serviceaddr_len = sizeof(serviceaddr);
// 将服务器网络信息结构体与套接字绑定
if (bind(sockfd, (struct sockaddr *)&serviceaddr, serviceaddr_len) == -1)
{
PRINT_ERR("bind error");
}
// 创建客户端网络信息结构体
struct sockaddr_in clientaddr;
memset(&clientaddr, 0, sizeof(clientaddr));
socklen_t clientaddr_len = sizeof(clientaddr);
msg_t msg;
// 创建父子进程
pid_t pid;
pid = fork();
if (pid == -1)
{
PRINT_ERR("fork error");
}
else if (pid == 0)
{
// 子进程
// 接受数据并处理
// 定义链表头节点
node_t *phead = NULL;
creat_link(&phead);
phead->next = NULL;
while (1)
{
memset(&msg, 0, sizeof(msg));
memset(&clientaddr, 0, sizeof(clientaddr));
if ((recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&clientaddr, &clientaddr_len)) == -1)
{
PRINT_ERR("recvfrom error");
}
printf("%8s : [%s]\n", msg.name, msg.txt);
switch (msg.code)
{
case 'L':
do_register(sockfd, msg, clientaddr, phead);
break;
case 'C':
do_group_chat(sockfd, msg, clientaddr, phead);
break;
case 'Q':
quit_group_chat(sockfd, msg, clientaddr, phead);
break;
}
}
}
else if (pid > 0)
{
// 父进程
// 发系统消息
msg.code = 'C';
strcpy(msg.name, "server");
while (1)
{
fgets(msg.txt, 128, stdin);
msg.txt[strlen(msg.txt) - 1] = '\0';
if (sendto(sockfd, &msg, sizeof(msg_t), 0, (struct sockaddr *)&serviceaddr, serviceaddr_len) == -1)
{
PRINT_ERR("sendto error");
}
}
}
close(sockfd);
return 0;
}
// 创建链表头节点函数
void creat_link(node_t **head)
{
*head = (node_t *)malloc(sizeof(node_t));
}
// 登录操作
int do_register(int sockfd, msg_t msg, struct sockaddr_in clientaddr, node_t *phead)
{
// 遍历链表将登录信息发送给所以人
node_t *p = phead;
while (p->next != NULL)
{
p = p->next;
if (sendto(sockfd, &msg, sizeof(msg_t), 0, (struct sockaddr *)&(p->c_addr), sizeof(p->c_addr)) == -1)
{
PRINT_ERR("recvfrom error");
}
}
// 将登录的客户端信息插入保存在链表
// 头插
// 定义一个新的指针保存客户端信息
node_t *newp = NULL;
creat_link(&newp);
newp->c_addr = clientaddr;
newp->next = phead->next;
phead->next = newp;
return 0;
}
int do_group_chat(int sockfd, msg_t msg, struct sockaddr_in clientaddr, node_t *phead)
{
// 遍历链表,将消息发给除自己之外的所有人
node_t *p = phead;
while (p->next != NULL)
{
p = p->next;
// 判断链表客户端信息是否是自己
// 是自己就不发送
if (memcmp(&(p->c_addr), &clientaddr, sizeof(clientaddr)))
{
if (sendto(sockfd, &msg, sizeof(msg_t), 0, (struct sockaddr *)&(p->c_addr), sizeof(p->c_addr)) == -1)
{
PRINT_ERR("recvfrom error");
}
}
}
return 0;
}
// 退出群聊操作
int quit_group_chat(int sockfd, msg_t msg, struct sockaddr_in clientaddr, node_t *phead)
{
node_t *p = phead;
while (p->next != NULL)
{
// 判断链表客户端信息是否是自己
// 是自己就不发送并且将自己的客户端信息在链表内删除
if (memcmp(&(p->next->c_addr), &clientaddr, sizeof(clientaddr)))
{
p = p->next;
if (sendto(sockfd, &msg, sizeof(msg_t), 0, (struct sockaddr *)&(p->c_addr), sizeof(p->c_addr)) == -1)
{
PRINT_ERR("recvfrom error");
}
}
else
{
node_t *pnew;
pnew = p->next;
p->next = pnew->next;
pnew->next = NULL;
free(pnew);
pnew = NULL;
}
}
return 0;
}
发送端
#include<myhead.h>
#define PRINT_ERR(msg) \
do \
{ \
printf("%s,%d,%s\n", __FILE__, __LINE__, __func__); \
perror(msg); \
exit(-1); \
} while (0)
typedef struct
{
char code; // 操作码 'L' 登录 'C' 群聊 'Q' 退出
char name[32];
char txt[128];
} msg_t;
int main(int argc, const char *argv[])
{
// 入参合理性判断
if (argc != 3)
{
printf("age:%s ip port\n", argv[0]);
return -1;
}
// 创建套接字
int sockfd;
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
PRINT_ERR("socket error");
}
// 创建服务器网络信息结构体
struct sockaddr_in serviceaddr;
memset(&serviceaddr, 0, sizeof(serviceaddr));
serviceaddr.sin_family = AF_INET;
serviceaddr.sin_addr.s_addr = inet_addr(argv[1]);
serviceaddr.sin_port = htons(atoi(argv[2]));
socklen_t serviceaddr_len = sizeof(serviceaddr);
// 给服务器发送登录数据包
msg_t msg;
memset(&msg, 0, sizeof(msg_t));
msg.code = 'L';
printf("请输入用户名:");
fgets(msg.name, 32, stdin);
msg.name[strlen(msg.name) - 1] = '\0';
strcpy(msg.txt, "加入群聊");
if (sendto(sockfd, &msg, sizeof(msg_t), 0, (struct sockaddr *)&serviceaddr, serviceaddr_len) == -1)
{
PRINT_ERR("sendto error");
}
// 创建父子进程
pid_t pid;
pid = fork();
if (pid == -1)
{
PRINT_ERR("fork error");
}
else if (pid == 0)
{
// 子进程
// 接受数据并处理
while (1)
{
// 每次循环前将msg置零
memset(&msg, 0, sizeof(msg));
// 接受服务器发过来的信息并打印到终端上
if (recvfrom(sockfd, &msg, sizeof(msg_t), 0, NULL, NULL) == -1)
{
PRINT_ERR("recvfrom error");
}
printf("%8s:[%s]\n", msg.name, msg.txt);
}
}
else if (pid > 0)
{
// 父进程
// 发送消息
while (1)
{
// memset会把name清除
msg.code = 'C';
fgets(msg.txt, 128, stdin);
msg.txt[strlen(msg.txt) - 1] = '\0';
if (strcmp(msg.txt, "quit") == 0)
{
msg.code = 'Q';
strcpy(msg.txt, "退出群聊");
}
if (sendto(sockfd, &msg, sizeof(msg_t), 0, (struct sockaddr *)&serviceaddr, serviceaddr_len) == -1)
{
PRINT_ERR("sendto error");
}
if (strcmp(msg.txt, "退出群聊") == 0)
{
break;
}
}
kill(pid, SIGKILL);
wait(NULL);
close(sockfd);
}
return 0;
}