要求:1.有新用户登录,其他在线的用户可以收到登录信息
2.有用户群聊,其他在线的用户可以收到群聊信息
3.有用户退出,其他在线的用户可以收到退出信息
4.服务器可以发送系统信息
效果图:
service.c
#include <head.h>
typedef struct _MSG
{
char type; // 类型 'L' 登录 'C' 群聊 'Q' 退出
char name[32];
char txt[128];
} msg_t;
typedef struct _NODE
{
struct sockaddr_in clientaddr;
struct _NODE *next;
} node_t;
void create_node(node_t **p);
void do_login(node_t *phead, msg_t msg, int sockfd, struct sockaddr_in clientaddr);
void do_chat(node_t *phead, msg_t msg, int sockfd, struct sockaddr_in clientaddr);
void do_quit(node_t *phead, msg_t msg, int sockfd, struct sockaddr_in clientaddr);
int main(int argc, const char *argv[])
{
// 入参合理性检查
if (argc != 3)
{
printf("usage error:%s <ip> <port>...\n", argv[0]);
exit(-1);
}
int sockfd = 0;
// 创建套接字
if (-1 == (sockfd = socket(AF_INET, SOCK_DGRAM, 0)))
{
perror("socket error");
exit(-1);
}
// 填充服务器网络信息结构体
struct sockaddr_in serveraddr;
socklen_t serveraddr_len = sizeof(serveraddr);
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
serveraddr.sin_port = htons(atoi(argv[2]));
struct sockaddr_in clientaddr;
socklen_t clientaddr_len = sizeof(clientaddr);
// 绑定
if (-1 == (bind(sockfd, (struct sockaddr *)&serveraddr, serveraddr_len)))
{
perror("bind error");
exit(-1);
}
node_t *phead = NULL;
create_node(&phead);
msg_t msg;
// 收发数据
char buff[128] = {0};
pid_t pid = 0;
pid = fork();
if (pid == -1)
{
perror("fork error");
exit(-1);
}
else if (pid == 0)
{
// 子进程用于接收数据
while (1)
{
memset(&msg, 0, sizeof(msg));
if (-1 == recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&clientaddr, &clientaddr_len))
{
perror("recvfrom error");
exit(-1);
}
printf("%s:%s\n", msg.name, msg.txt);
switch (msg.type)
{
case 'L':
do_login(phead, msg, sockfd, clientaddr);
break;
case 'C':
do_chat(phead, msg, sockfd, clientaddr);
break;
case 'Q':
do_quit(phead, msg, sockfd, clientaddr);
break;
}
}
}
else if (pid > 0)
{
// 父进程用于发送数据
// 把父进程当做一个客户端 以群聊的方式 把系统消息发给子进程
strcpy(msg.name, "server");
msg.type = 'C';
while (1)
{
memset(msg.txt, 0, 128);
fgets(msg.txt, sizeof(msg.txt), stdin);
msg.txt[strlen(msg.txt) - 1] = '\0';
if (-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, serveraddr_len))
{
perror("sento error");
exit(-1);
}
}
}
close(sockfd);
return 0;
}
void create_node(node_t **p)
{
*p = (node_t *)malloc(sizeof(node_t));
memset(*p, 0, sizeof(node_t));
}
// 登录操作函数
void do_login(node_t *phead, msg_t msg, int sockfd, struct sockaddr_in clientaddr)
{
// 遍历链表 当前在线的所有人发“***加入了群聊”的消息
node_t *ptemp = phead;
while (ptemp->next != NULL)
{
ptemp = ptemp->next;
if (-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&(ptemp->clientaddr), sizeof(ptemp->clientaddr)))
{
perror("sento error");
exit(-1);
}
}
// 把新加入的群聊客户端网络信息结构体加入到链表中
node_t *pnew = NULL;
create_node(&pnew);
pnew->clientaddr = clientaddr;
pnew->next = phead->next;
phead->next = pnew;
return;
}
void do_chat(node_t *phead, msg_t msg, int sockfd, struct sockaddr_in clientaddr)
{
// 遍历链表 将群聊的消息 发给除了自己之外的所有人
node_t *ptemp = phead;
while (ptemp->next != NULL)
{
ptemp = ptemp->next;
if (memcmp(&clientaddr, &(ptemp->clientaddr), sizeof(clientaddr)) != 0)
{
if (-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&(ptemp->clientaddr), sizeof(ptemp->clientaddr)))
{
perror("sento error");
exit(-1);
}
}
}
return;
}
void do_quit(node_t *phead, msg_t msg, int sockfd, struct sockaddr_in clientaddr)
{
// 把 xxx 退出群聊的消息 发给在线的除自己的所有人 并且将自己在链表中删除
node_t *ptemp = phead;
while (ptemp->next != NULL)
{
if (memcmp(&clientaddr, &(ptemp->next->clientaddr), sizeof(clientaddr)) != 0)
{
if (-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&(ptemp->next->clientaddr), sizeof(ptemp->next->clientaddr)))
{
perror("sento error");
exit(-1);
}
ptemp = ptemp->next;
}
else
{
node_t *pdel = ptemp->next;
ptemp->next = pdel->next;
free(pdel);
pdel = NULL;
}
}
return;
}
client.c
#include <head.h>
typedef struct _MSG
{
char type; // 类型 'L' 登录 'C' 群聊 'Q' 退出
char name[32];
char txt[128];
} msg_t;
int main(int argc, const char *argv[])
{
// 入参合理性检查
if (argc != 3)
{
printf("usage error:%s <ip> <port>...\n", argv[0]);
exit(-1);
}
int sockfd = 0;
// 创建套接字
if (-1 == (sockfd = socket(AF_INET, SOCK_DGRAM, 0)))
{
perror("socket error");
exit(-1);
}
// 填充服务器网络信息结构体
struct sockaddr_in serveraddr;
socklen_t serveraddr_len = sizeof(serveraddr);
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
serveraddr.sin_port = htons(atoi(argv[2]));
msg_t msg;
memset(&msg, 0, sizeof(msg));
printf("请输入用户名:");
fgets(msg.name, sizeof(msg.name), stdin);
msg.name[strlen(msg.name) - 1] = '\0';
msg.type = 'L';
strcpy(msg.txt, "加入群聊");
if (-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, serveraddr_len))
{
perror("sendto error");
exit(-1);
}
// 收发数据
char buff[128] = {0};
pid_t pid;
pid = fork();
if (pid == -1)
{
perror("fork error");
exit(-1);
}
else if (pid == 0)
{
// 子进程用于接收数据
while (1)
{
memset(&msg, 0, sizeof(msg));
if (-1 == recvfrom(sockfd, &msg, sizeof(msg), 0, NULL, NULL))
{
perror("recvfrom error");
exit(-1);
}
printf("%s : %s\n", msg.name, msg.txt);
}
}
else if(pid>0)
{
// //父进程 在终端获取数据 发给服务器
while (1)
{
memset(msg.txt, 0, sizeof(msg.txt));
fgets(msg.txt, 128,stdin);
msg.txt[strlen(msg.txt) - 1] = '\0';
if (strcmp(msg.txt, "quit") == 0)
{
msg.type = 'Q';
strcpy(msg.txt, "退出群聊");
}
else
{
msg.type = 'C';
}
if (-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, serveraddr_len))
{
perror("sendto error");
exit(-1);
}
if ('Q' == msg.type)
{
// 父进程退出之前先给子进程发信号 杀死子进程
kill(pid, SIGKILL);
wait(NULL);
break;
}
}
}
close(sockfd);
return 0;
}