基于UDP的网络聊天室
项目需求:
- 如果有用户登录,其他用户可以收到这个人的登录信息
- 如果有人发送信息,其他用户可以收到这个人的群聊信息
- 如果有人下线,其他用户可以收到这个人的下线信息
- 服务器可以发送系统信息
服务器
#include <myhead.h>
// #define SER_IP "192.168.141.134"
// #define SER_PORT 8888
typedef struct Node // 链表存储客户端的所有信息
{
struct sockaddr_in cin; // 存储客户端的网络地址信息
struct Node *next;
} *List;
typedef struct Message // 消息结构体
{
char type;
char name[20];
char text[128];
} msg_t;
struct sockaddr_in cin; // 客户端地址信息结构体
// 单链表节点创建函数
List create_node()
{
List p = (List)malloc(sizeof(struct Node));
if (NULL == p)
return NULL;
p->next = NULL;
return p;
}
// 客户端链表尾插
List insert_rear(List head, struct sockaddr_in cin)
{
List s = create_node();
if (NULL == s)
return head;
s->cin = cin;
if (NULL == head)
{
head = s;
return s;
}
else
{
List p = head;
while (p->next != NULL)
p = p->next;
p->next = s;
return head;
}
}
// 客户端接入服务器通知函数
void chat_all_join(List head, msg_t msg, int sfd)
{
List p = head;
char buf[50] = "";
while (p->next != NULL)
{
snprintf(buf, sizeof(buf), "[%s:%d]%s加入聊天室\n", inet_ntoa(p->cin.sin_addr), ntohs(p->cin.sin_port), msg.name);
sendto(sfd, buf, sizeof(buf), 0, (struct sockaddr *)&(p->cin), sizeof(p->cin));
p = p->next;
}
}
// 客户端消息转发函数
void chat_all(List head, struct Message msg, int sfd, struct sockaddr_in cin)
{
List p = head;
char rbuf[200] = "";
while (p->next != NULL)
{
snprintf(rbuf, sizeof(rbuf), "[%s:%d]%s:%s\n", inet_ntoa(p->cin.sin_addr), ntohs(p->cin.sin_port), msg.name, msg.text);
sendto(sfd, rbuf, sizeof(rbuf), 0, (struct sockaddr *)&(p->cin), sizeof(p->cin));
p = p->next;
}
snprintf(rbuf, sizeof(rbuf), "[%s:%d]%s:%s\n", inet_ntoa(p->cin.sin_addr), ntohs(p->cin.sin_port), msg.name, msg.text);
sendto(sfd, rbuf, sizeof(rbuf), 0, (struct sockaddr *)&(p->cin), sizeof(p->cin));
}
// 客户端发送退出消息函数
void chat_all_quit(List head, struct Message msg, int sfd)
{
char wbuf[200] = "";
List p = head;
while (p->next != NULL)
{
snprintf(wbuf, sizeof(wbuf), "[%s:%d]%s:退出了聊天室\n", inet_ntoa(p->cin.sin_addr), ntohs(p->cin.sin_port), msg.name);
sendto(sfd, wbuf, sizeof(wbuf), 0, (struct sockaddr *)&(p->cin), sizeof(p->cin));
p = p->next;
}
snprintf(wbuf, sizeof(wbuf), "[%s:%d]%s:退出了聊天室\n", inet_ntoa(p->cin.sin_addr), ntohs(p->cin.sin_port), msg.name);
sendto(sfd, wbuf, sizeof(wbuf), 0, (struct sockaddr *)&(p->cin), sizeof(p->cin));
}
// 链表中删除该地址信息
List exit_chat(List head)
{
if (head->next == NULL) // 只有一个客户端时
{
free(head);
head = NULL;
return head;
}
List p = head;
while (p->next != NULL) // 两个以上客户端
{
if (memcmp(&(p->next->cin), &cin, sizeof(cin)) == 0) // 找到p下一个节点地址信息符合的
{
List del = p->next;
p->next = del->next;
free(del);
del = NULL;
break;
}
else
{
p = p->next;
}
}
return head;
}
int main(int argc, const char *argv[])
{
// 创建通信的套接字文件描述符
int sfd = -1;
if ((sfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
perror("socket error");
return -1;
}
// 快速刷新端口号
int reuse = -1;
if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
{
perror("setsockopt error");
return -1;
}
if (argc < 3)
{
fprintf(stderr, "请输入ip和端口号\n");
return -1;
}
// 给当前套接字绑定结构体信息
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(atoi(argv[2]));
sin.sin_addr.s_addr = inet_addr(argv[1]);
if (bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) == -1)
{
perror("bind error");
return -1;
}
// 准备文件描述符容器
fd_set readfds, tempfds;
FD_ZERO(&readfds);
FD_SET(0, &readfds);
FD_SET(sfd, &readfds);
int maxfd = sfd;
// 定义变量存放客户端地址信息结构体,及客户端消息
struct sockaddr_in cin;
socklen_t socklen = sizeof(cin);
struct Message msg;
List head = NULL;
char buf[128] = "";
while (1)
{
tempfds = readfds;
if (select(maxfd + 1, &tempfds, NULL, NULL, NULL) == -1)
{
perror("select error");
return -1;
}
// 收到消息
if (FD_ISSET(sfd, &tempfds))
{
recvfrom(sfd, &msg, sizeof(msg), 0, (struct sockaddr *)&cin, &socklen);
switch (msg.type)
{
case 'L': // 客户端加入
{
head = insert_rear(head, cin); // 尾插入链表
chat_all_join(head, msg, sfd);
printf("[%s:%d]%s加入聊天室\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), msg.name);
};
break;
case 'C': // 客户端消息
{
chat_all(head, msg, sfd, cin);
printf("[%s:%d]%s:%s\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), msg.name, msg.text);
};
break;
case 'Q': // 客户端退出
{
chat_all_quit(head, msg, sfd);
printf("[%s:%d]%s退出聊天室\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), msg.name);
head = exit_chat(head);
};
break;
default:
printf("type error\ttype=%c\n", msg.type);
return -1;
}
}
// 发送消息
if (FD_ISSET(0, &tempfds))
{
memset(buf, 0, sizeof(buf));
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) - 1] = '\0';
char wbuf[56] = "";
snprintf(wbuf, sizeof(wbuf), "***system***%s\n", buf);
List p = head;
while (p != NULL)
{
sendto(sfd, wbuf, sizeof(wbuf), 0, (struct sockaddr *)&(p->cin), sizeof(p->cin));
p = p->next;
}
}
}
return 0;
}
客户端
#include <myhead.h>
// #define SER_IP "192.168.141.128"
// #define SER_PORT 8888
struct Message
{
char type;
char name[20];
char text[128];
};
int main(int argc, const char *argv[])
{
struct Message msg;
// 创建通信用套接字文件描述符
int cfd = -1;
if ((cfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
perror("socket error");
return -1;
}
if (argc < 3)
{
fprintf(stderr, "请输入ip和端口号\n");
return -1;
}
// 填写服务器的地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(atoi(argv[2]));
sin.sin_addr.s_addr = inet_addr(argv[1]);
char name[20] = "";
// 发送客户端的登录信息
printf("请输入昵称:");
fgets(name, sizeof(name), stdin);
name[strlen(name) - 1] = '\0';
strcpy(msg.name, name);
msg.type = 'L';
if (sendto(cfd, &msg, sizeof(msg), 0, (struct sockaddr *)&sin, sizeof(sin)) == -1)
{
perror("sendto error");
return -1;
}
else
{
printf("加入聊天服务器成功\n");
}
// 准备文件描述符容器
fd_set readfds, tempfds;
FD_ZERO(&readfds);
FD_SET(0, &readfds);
FD_SET(cfd, &readfds);
int maxfd = cfd;
while (1)
{
tempfds = readfds;
int res = select(maxfd + 1, &tempfds, NULL, NULL, NULL);
if (res == -1)
{
perror("select error");
return -1;
}
// 发数据
if (FD_ISSET(0, &tempfds))
{
memset(msg.text, 0, sizeof(msg.text));
read(0, msg.text, sizeof(msg.text));
msg.text[strlen(msg.text) - 1] = '\0';
// 客户端退出
if (strcmp(msg.text, "quit") == 0)
{
msg.type = 'Q';
sendto(cfd, &msg, sizeof(msg), 0, (struct sockaddr *)&sin, sizeof(sin));
printf("本机已下线\n");
close(cfd);
return 0;
}
// 与其他客户端通信
else
{
msg.type = 'C';
sendto(cfd, &msg, sizeof(msg), 0, (struct sockaddr *)&sin, sizeof(sin));
}
}
// 收数据
if (FD_ISSET(cfd, &tempfds))
{
char buf[128] = "";
// 不接收服务器的地址信息结构体
recvfrom(cfd, buf, sizeof(buf), 0, NULL, NULL);
printf("%s", buf);
fflush(stdout);
}
}
return 0;
}