基于UDP的网络聊天室(2024.5.26号上交)
项目需求:
- 如果有用户登录,其他用户可以收到这个人的登录信息
- 如果有人发送信息,其他用户可以收到这个人的群聊信息
- 如果有人下线,其他用户可以收到这个人的下线信息
- 服务器可以发送系统信息
服务器
#include <myhead.h>
#define SER_IP "192.168.124.38"
#define SER_PORT 8888
#define RBUFSIZE 128
#define WBUFSIZE 128
typedef struct node{
char usrName[20];
struct sockaddr_in cli_sockaddr;
struct node* next;
}node, *node_p;
node_p create_head_node();
node_p create_node(char* usrName, struct sockaddr_in cli_sockaddr);
void insert_tail(node_p H, char* usrName, struct sockaddr_in cli_sockaddr);
char* find(node_p H, struct sockaddr_in cli_sockaddr);
void delete(node_p H, struct sockaddr_in cli_sockaddr);
int main(int argc, const char *argv[]){
int sockfd;
struct sockaddr_in ser_sockaddr;
struct sockaddr_in cli_sockaddr;
char wbuf[WBUFSIZE]={0};
char rbuf[RBUFSIZE]={0};
socklen_t addrlen = sizeof(struct sockaddr);
struct pollfd pfds[2];
// 创建头结点
node_p H = create_head_node();
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd == -1){
perror("socket");
return -1;
}
ser_sockaddr.sin_family = AF_INET;
ser_sockaddr.sin_port = htons(SER_PORT);
ser_sockaddr.sin_addr.s_addr = inet_addr(SER_IP);
if(bind(sockfd, (struct sockaddr*)&ser_sockaddr, sizeof(struct sockaddr)) == -1){
perror("bind");
return -1;
}
pfds[0].fd = 0;
pfds[0].events = POLLIN;
pfds[1].fd = sockfd;
pfds[1].events = POLLIN;
puts("Waiting for client connection...");
//开始通信
while (1) {
poll(pfds, 2, -1);
// 接受客户端发送的消息,并转发给其他客户端
if (pfds[1].revents == POLLIN) {
memset(rbuf, 0, sizeof(rbuf));
recvfrom(sockfd, rbuf, RBUFSIZE, 0, (struct sockaddr*)&cli_sockaddr, &addrlen);
// 判断客户端是否在链表内,即客户端是否已经连接
char* ret = find(H, cli_sockaddr);
// 用户未存在于链表中
if (ret == NULL) {
// 在客户端首次连接时,利用链表尾插存储客户端的用户名和地址信息
insert_tail(H, rbuf, cli_sockaddr);
// 服务器端输出客户端加入消息
printf("%s [%s:%d] 加入聊天室\n", rbuf, inet_ntoa(cli_sockaddr.sin_addr), ntohs(cli_sockaddr.sin_port));
// 将客户端上线的消息,发送给出了除了刚刚加入的客户端的所有主机
node_p temp = H->next;
//格式化客户端登录的消息
char str[1024]={0};
sprintf(str, "----%s加入聊天室----", rbuf);
while (temp->next != NULL) {
if (sendto(sockfd, str, strlen(str), 0, (struct sockaddr*)&temp->cli_sockaddr, sizeof(struct sockaddr)) == -1) {
perror("sendto");
}
temp = temp->next;
}
}
// 用户存在于链表中
else if (ret > 0) {
// 已登录客户端发送了退出信号,通知服务器以及其他所有用户
if(strcmp(rbuf, "quit") == 0){
// 格式化客户端退出消息
char str[1024]={0};
sprintf(str, "----%s退出聊天室----", ret);
// 服务器端输出客户端退出消息
printf("%s [%s:%d] 退出聊天室\n", ret, inet_ntoa(cli_sockaddr.sin_addr), ntohs(cli_sockaddr.sin_port));
// 通知其他人
node_p temp = H->next;
while (temp != NULL) {
if (sendto(sockfd, str, strlen(str), 0, (struct sockaddr*)&temp->cli_sockaddr, sizeof(struct sockaddr)) == -1) {
perror("sendto");
}
temp = temp->next;
}
// 将退出的客户端从链表中删除
delete(H, cli_sockaddr);
}
else{
// 将客户端发送的消息转发给所有人(除了发送消息的客户端)
// 格式化用户发送的消息
char str[1024]={0};
sprintf(str, "%s: %s", ret, rbuf);
node_p temp = H->next;
while (temp != NULL) {
if (temp->cli_sockaddr.sin_addr.s_addr == cli_sockaddr.sin_addr.s_addr && temp->cli_sockaddr.sin_port == cli_sockaddr.sin_port){
temp = temp->next;
continue;
}
if (sendto(sockfd, str, strlen(str), 0, (struct sockaddr*)&temp->cli_sockaddr, sizeof(struct sockaddr)) == -1) {
perror("sendto");
}
temp = temp->next;
}
printf("%s [%s:%d] 发送了一条消息\n", ret, inet_ntoa(cli_sockaddr.sin_addr), ntohs(cli_sockaddr.sin_port));
}
}
}
// 服务器向所有客户端发送消息
if (pfds[0].revents == POLLIN) {
memset(wbuf, 0, sizeof(wbuf));
fgets(wbuf, sizeof(wbuf), stdin);
wbuf[strlen(wbuf)-1] = 0;
// 格式化服务器发送的消息
char str[1024]={0};
sprintf(str, "server: %s", wbuf);
node_p temp = H->next;
while (temp != NULL) {
if (sendto(sockfd, str, strlen(str), 0, (struct sockaddr*)&temp->cli_sockaddr, sizeof(struct sockaddr)) == -1) {
perror("sendto");
}
temp = temp->next;
}
printf("服务器 [%s:%d] 发送了一条消息\n",inet_ntoa(ser_sockaddr.sin_addr), ntohs(ser_sockaddr.sin_port));
}
}
//关闭socket
close(sockfd);
return 0;
}
node_p create_head_node(){
node_p H=(node_p)malloc(sizeof(node));
if(H==NULL){
printf("空间申请失败\n");
return NULL;
}
H->next=NULL;
return H;
}
node_p create_node(char* usrName, struct sockaddr_in cli_sockaddr){
node_p p=(node_p)malloc(sizeof(node));
if(p==NULL){
printf("空间申请失败\n");
return NULL;
}
strcpy(p->usrName, usrName);
p->cli_sockaddr = cli_sockaddr;
return p;
}
void insert_tail(node_p H, char* usrName, struct sockaddr_in cli_sockaddr){
if(H==NULL){
printf("入参为空\n");
return;
}
node_p p=H;
while(p->next!=NULL){
p=p->next;
}
node_p new = create_node(usrName, cli_sockaddr);
new->next=p->next;
p->next=new;
}
/*
@brief 判断客户端信息是否已经保存在链表内
@param H 头结点
cli_sockaddr 待判断的客户端信息
@retval NULL 客户端信息不在链表内
非空 客户端的用户名字符串指针
*/
char* find(node_p H, struct sockaddr_in cli_sockaddr){
if (H == NULL){
printf("入参为空\n");
return NULL;
}
if (H->next == NULL){
// 链表为空
return NULL;
}
node_p temp = H->next;
while (temp != NULL){
if (temp->cli_sockaddr.sin_addr.s_addr == cli_sockaddr.sin_addr.s_addr && temp->cli_sockaddr.sin_port == cli_sockaddr.sin_port){
return temp->usrName;
}
temp = temp->next;
}
return NULL;
}
/*
@brief 删除链表中指定的客户端信息
@param H 头结点
cli_sockaddr 待删除的客户端信息
@retval 无
*/
void delete(node_p H, struct sockaddr_in cli_sockaddr){
if (H == NULL){
printf("入参为空\n");
return;
}
node_p temp = H;
while (temp->next->cli_sockaddr.sin_addr.s_addr != cli_sockaddr.sin_addr.s_addr || \
temp->next->cli_sockaddr.sin_port != cli_sockaddr.sin_port)temp = temp->next;
node_p p = temp->next;
temp->next = temp->next->next;
free(p);
}
客户端
#include <myhead.h>
#define SER_PORT 8888
#define SER_IP "192.168.124.38"
#define CLI_PORT 6666
#define CLI_IP "192.168.125.117"
#define WBUFSIZE 128
#define RBUFSIZE 128
int main(int argc, const char *argv[])
{
int sockfd;
char wbuf[WBUFSIZE] ={0};
char rbuf[RBUFSIZE] ={0};
struct sockaddr_in ser_sockaddr;
struct pollfd pfds[2];
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd == -1)
{
perror("socket");
return -1;
}
ser_sockaddr.sin_family = AF_INET;
ser_sockaddr.sin_port = htons(SER_PORT);
ser_sockaddr.sin_addr.s_addr = inet_addr(SER_IP);
pfds[0].fd = 0;
pfds[0].events = POLLIN;
pfds[1].fd = sockfd;
pfds[1].events = POLLIN;
printf("请输入聊天昵称->");
fflush(stdout);
//开始通信
while(1)
{
// 通信缓冲区
memset(wbuf, 0, sizeof(wbuf));
memset(rbuf, 0, sizeof(rbuf));
// 阻塞等待事件发生
poll(pfds, 2, -1);
// 向服务器端发送消息
if(pfds[0].revents == POLLIN){
fgets(wbuf, sizeof(wbuf), stdin);
wbuf[strlen(wbuf)-1] = 0;
sendto(sockfd, wbuf, strlen(wbuf), 0, (struct sockaddr*)&ser_sockaddr, sizeof(struct sockaddr));
if(strcmp(wbuf, "quit") == 0){
close(sockfd);
return 0;
}
}
// 接收服务器消息
if(pfds[1].revents == POLLIN){
recvfrom(sockfd, rbuf, sizeof(rbuf), 0,NULL, NULL);
printf("%s\n", rbuf);
}
}
return 0;
}