提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
一、项目要求
利用UDP协议,实现一套聊天室软件。
服务器端记录客户端的地址,客户端发送消息后,服务器群发给各个客户端软件。
二、功能实现
登录:服务器存储新的客户端的地址。把某个客户端登录的消息发给其它客户端。
聊天:服务器只需要把某个客户端的聊天消息转发给所有其它客户端。
退出:服务器删除退出客户端的地址,并把退出消息发送给其它客户端。
三、注意事项
客户端不仅需要读取服务器消息,而且需要发送消息。
读取需要调用recvfrom,发送需要先调用gets,两个都是阻塞函数。
使用多任务来同时处理,可以使用多进程或者多线程来处理。
服务器需要给多个用户发送数据,所以需要保存每一个用户的信息,使用链表来保存
四、流程
服务器流程:
创建用户数据报套接字
填充服务器的网络信息结构体
绑定套接字与服务器网络信息结构体
收发数据
关闭套接字
客户端流程:
创建用户数据报套接字
填充服务器的网络信息结构体
收发数据
关闭套接字
五、项目实现
1.构建结构体
代码如下(示例):
#ifndef __HEAD_H__
#define __HEAD_H__
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
enum type_t
{
Login,
Chat,
Quit,
};
//定义消息结构体
typedef struct mag_t
{
int type; //消息类型
char name[32]; //用户名
char text[128]; //消息内容
} MSG_t;
//链表的节点
typedef struct node_t
{
struct sockaddr_in caddr;
struct node_t *next;
} list_t;
#endif
2.构建服务端
代码如下(示例):
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include <pthread.h>
#include "head.h"
struct sockaddr_in serveraddr, caddr;
//创建有头单向链表
list_t *createList(void);
int login_client(int sockfd, MSG_t msg, list_t *p, struct sockaddr_in caddr);
int chat_client(int sockfd, MSG_t msg, list_t *p, struct sockaddr_in caddr);
int quit_client(int sockfd, MSG_t msg, list_t *p, struct sockaddr_in caddr);
void *pthread(void *arg)
{
MSG_t msg;
int sockfd = (*(int *)arg);
msg.type = Chat;
strcpy(msg.name, "管理员");
while (1)
{
fgets(msg.text, sizeof(msg.text), stdin);
if (msg.text[strlen(msg.text) - 1] == '\n')
msg.text[strlen(msg.text) - 1] = '\0';
sendto(sockfd, &msg, sizeof(msg), 0,
(struct sockaddr *)&serveraddr, sizeof(serveraddr));
}
pthread_exit(NULL);
}
int main(int argc, char const *argv[])
{
if (argc != 2)
{
printf("please input %s <ip> <port>\n", argv[0]);
return -1;
}
//1.创建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("socket err.");
return -1;
}
//填充服务器端ip和端口
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[1]));
serveraddr.sin_addr.s_addr = inet_addr("0.0.0.0");
socklen_t len = sizeof(caddr);
//2.绑定
if (bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
{
perror("bind err.");
return -1;
}
//创建链表
list_t *p = createList();
//创建线程-服务器端发送消息
pthread_t tid;
pthread_create(&tid, NULL, pthread, &sockfd);
pthread_detach(tid);
//循环收发消息
MSG_t msg;
int recvbyte;
while (1)
{
//接收 recvfrom
recvbyte = recvfrom(sockfd, &msg, sizeof(msg), 0,
(struct sockaddr *)&caddr, &len);
if (recvbyte < 0)
{
perror("recvfrom err.");
return -1;
}
printf("[%s]:%s\n", msg.name, msg.text);
switch (msg.type)
{
case Login:
login_client(sockfd, msg, p, caddr);
break;
case Chat:
chat_client(sockfd, msg, p, caddr);
break;
case Quit:
quit_client(sockfd, msg, p, caddr);
break;
}
}
close(sockfd);
return 0;
}
//创建有头单向链表
list_t *createList(void)
{
//1.创建一个无效头节点:数据域无效,指针域有效
list_t *p = (list_t *)malloc(sizeof(list_t));
if (NULL == p)
{
perror("malloc head node err.");
return NULL;
}
//2.初始化节点 空链表
p->next = NULL;
return p;
}
//1.客户端登录-服务器工作:
//1》遍历链表,将谁登录的消息发送所有已经登录的用户
//2》将新登录的客户端的ip和端口保存到链表中
int login_client(int sockfd, MSG_t msg, list_t *p, struct sockaddr_in caddr)
{
list_t *pnew = NULL;
//1》遍历链表,将谁登录的消息发送所有已经登录的用户
//sprintf(msg.text, "已上线...");
while (p->next != NULL)
{
p = p->next;
sendto(sockfd, &msg, sizeof(msg), 0,
(struct sockaddr *)&(p->caddr), sizeof(p->caddr));
}
//2》将新登录的客户端的ip和端口保存到链表中
//1-创建新节点保存
pnew = (list_t *)malloc(sizeof(list_t));
if (NULL == pnew)
{
perror("malloc new node err.");
return -1;
}
//2-初始化
pnew->caddr = caddr;
pnew->next = NULL;
//3-将节点连接到链表最后 p保存的链表最后一个节点的地址
//直接链接最后就可以
p->next = pnew;
return 0;
}
//2.聊天-服务工作:
//1》将消息发送给所有除自己已经登录的用户
int chat_client(int sockfd, MSG_t msg, list_t *p, struct sockaddr_in caddr)
{
//1》将消息发送给所有除自己已经登录的用户
//1-遍历链表,只有不是自己的ip和端口就发送消息
while (p->next != NULL)
{
p = p->next;
if (memcmp(&(p->caddr), &caddr, sizeof(caddr)) != 0)
{
sendto(sockfd, &msg, sizeof(msg), 0,
(struct sockaddr *)&(p->caddr), sizeof(p->caddr));
}
}
return 0;
}
//3。客户端推出-服务器工作
//1》将推出的用户消息发送给还登录着的用户
//2》从链表中删除推出用户的ip和端口
int quit_client(int sockfd, MSG_t msg, list_t *p, struct sockaddr_in caddr)
{
list_t *pdel = NULL;
while (p->next != NULL)
{
if (memcmp(&(p->next->caddr), &caddr, sizeof(caddr)) == 0)
{
pdel = p->next;
p->next = pdel->next;
free(pdel);
pdel = NULL;
}
else
{
p = p->next;
sendto(sockfd, &msg, sizeof(msg), 0,
(struct sockaddr *)&(p->caddr), sizeof(p->caddr));
}
}
return 0;
}
3.构建客户端
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include <signal.h>
#include "head.h"
int sockfd;
MSG_t msg;
struct sockaddr_in serveraddr;
void handler(int sig)
{
// msg.type = Quit;
// strcpy(msg.text, "已下线...");
// sendto(sockfd, &msg, sizeof(msg), 0,
// (struct sockaddr *)&serveraddr, sizeof(serveraddr));
// exit(-1);
msg.type = Quit;
strcpy(msg.text, "已下线...");
sendto(sockfd, &msg, sizeof(msg), 0,
(struct sockaddr *)&serveraddr, sizeof(serveraddr));
kill(getppid(), SIGKILL);
exit(-1);
}
int main(int argc, char const *argv[])
{
if (argc != 3)
{
printf("please input %s <ip> <port>\n", argv[0]);
return -1;
}
//1.创建套接字
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("socket err.");
return -1;
}
//填充服务器端ip和端口
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
//ctrl + c
signal(SIGINT, handler);
//循环收发消息
int recvbyte;
//1.登录
msg.type = Login;
//从终端获取用户名
printf("请输入用户名:");
fgets(msg.name, sizeof(msg.name), stdin);
if (msg.name[strlen(msg.name) - 1] == '\n')
msg.name[strlen(msg.name) - 1] = '\0';
strcpy(msg.text, "已上线...");
//发送登录消息 sendto
sendto(sockfd, &msg, sizeof(msg), 0,
(struct sockaddr *)&serveraddr, sizeof(serveraddr));
pid_t pid = fork();
if (pid < 0)
{
perror("fork err.");
return -1;
}
else if (pid == 0)
{
//登录成功循环聊天
while (1)
{
fgets(msg.text, sizeof(msg.text), stdin);
if (msg.text[strlen(msg.text) - 1] == '\n')
msg.text[strlen(msg.text) - 1] = '\0';
if (strncmp(msg.text, "quit", 4) == 0)
{
msg.type = Quit;
strcpy(msg.text, "已下线...");
sendto(sockfd, &msg, sizeof(msg), 0,
(struct sockaddr *)&serveraddr, sizeof(serveraddr));
kill(getppid(), SIGKILL);
exit(-1);
}
else
{
msg.type = Chat;
}
//发送消息 sendto
sendto(sockfd, &msg, sizeof(msg), 0,
(struct sockaddr *)&serveraddr, sizeof(serveraddr));
}
}
else
{
//循环接受
while (1)
{
int ret = recvfrom(sockfd, &msg, sizeof(msg), 0,
NULL, NULL);
if (ret < 0)
{
perror("recvfrom err.");
return -1;
}
printf("[%s]:%s\n", msg.name, msg.text);
}
}
close(sockfd);
return 0;
}
六、实现结果