搭建公共聊天室
一、聊天室介绍
本聊天室主要运用了udp协议,应用于局域网范围之内,可以支持多个处于同一个局域网的主机在局域网内相互传递消息。本聊天室由一个服务器端和若干个客户端组成,由一台主机打开服务器端,其他主机通过udp协议连接服务器端的主机,并通过服务器端的主机将数据发送给其他主机。本聊天室可以完成检测用户的登录、聊天信息、退出与连接断开等四个主要功能。下面将介绍整个聊天室各项功能的搭建过程和网络搭建过程。
二、聊天室服务器端和客户端网络连接搭建过程
服务器端:
1)首先创建套接字;
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
2)申请网络信息结构体;
struct sockaddr_in serveraddr;memset(&serveraddr, 0, sizeof(serveraddr));serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(atoi(argv[2]));//从终端获取服务器端口号serveraddr.sin_addr.s_addr = inet_addr(argv[1]);//从终端获取服务器ip地址socklen_t serveraddr_len = sizeof(serveraddr);
3)将套接字与网络信息结构体进行绑定;
bind(sockfd, (struct sockaddr *)&serveraddr, serveraddr_len);
4)接收客户端发来的信息,向客户端反馈信息;
recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&clientaddr, &clientaddr_len);//接收消息
sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, serveraddr_len);//向客户端发送消息
5)关闭套接字;
close(sockfd);
客户端:1)首先创建套接字;
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
2)申请网络信息结构体;
struct sockaddr_in serveraddr;memset(&serveraddr, 0, sizeof(serveraddr));serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(atoi(argv[2]));//从终端获取要连接的服务器端端口号serveraddr.sin_addr.s_addr = inet_addr(argv[1]);//从终端获取要连接的服务器端ip地址socklen_t serveraddr_len = sizeof(serveraddr);
3)将套接字与网络信息结构体进行绑定(这一步可以删去);
4)向服务器端发送的信息,接收服务器端反馈信息;
sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, serveraddr_len);//向服务器端发送信息
recvfrom(sockfd, &msg, sizeof(msg), 0, NULL, NULL);//接收服务器端发来的消息
5)关闭套接字
close(sockfd);
三、服务器端和客户端各项功能的设计与实现
1.聊天室功能设计:
聊天室需要实现的功能:
有新用户登录,其他在线的用户可以收到登录信息;
有用户群聊,其他在线的用户可以收到群聊信息;
有用户退出,其他在线的用户可以收到退出信息;
服务器可以发送系统信息。
整个聊天室大概思路:
1)客户端登录之后,为了实现一边发送数据一边接收数据,可以使用多进程或者多线程;
2)服务器既可以发送系统信息,又可以接收客户端信息并处理,可以使用多进程或者多线程;
3) 服务器需要给多个用户发送数据,所以需要保存每一个用户的信息,使用链表来保存;
注:数据传输的时候要定义结构体,结构体中包含操作码、用户名以及数据;
2.聊天室功能实现:
服务器端:
//将接收信息的任务交给子线程
while(1){memset(&msg, 0, sizeof(msg));memset(&clientaddr, 0, sizeof(clientaddr));if(-1 == recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&clientaddr, &clientaddr_len)){ERRLOG("recvfrom error");}printf("%8s : [%s]\n", msg.name, msg.txt);switch(msg.code){case 'L':do_login(clientaddr, phead, sockfd, msg);break;case 'C':do_chat(clientaddr, phead, sockfd, msg);break;case 'Q':do_quit(clientaddr, phead, sockfd, msg);break;}
用户登录功能:
//登录操作的函数
int do_login(struct sockaddr_in clientaddr, node_t *phead, int sockfd, msg_t msg){//先遍历链表 将登陆的消息发给所有在线用户node_t *ptemp = phead;while(ptemp->next != NULL){ptemp = ptemp->next;if(-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&(ptemp->c_addr), sizeof(ptemp->c_addr))){ERRLOG("sendto error");}}//将新登录的用户采用头插法插入到链表中node_t *pnew = NULL;create_node(&pnew);pnew->c_addr = clientaddr;pnew->next = phead->next;phead->next = pnew;return 0;
}
用户发送消息功能:
//群聊的函数
int do_chat(struct sockaddr_in clientaddr, node_t *phead, int sockfd, msg_t msg){//遍历链表,将群聊的数据发送给除了自己之外的所有人node_t *ptemp = phead;while(ptemp->next != NULL){ptemp = ptemp->next;if(memcmp(&clientaddr, &(ptemp->c_addr), sizeof(clientaddr))){if(-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&(ptemp->c_addr), sizeof(ptemp->c_addr))){ERRLOG("sendto error");}}}
}
用户退出功能:
int do_quit(struct sockaddr_in clientaddr, node_t *phead, int sockfd, msg_t msg){//将退出群聊的消息发给除了自己之外的所有在线用户//并且将自己在链表中删除node_t *ptemp = phead;while(ptemp->next != NULL){if(memcmp(&clientaddr, &(ptemp->next->c_addr), sizeof(clientaddr))){//不是自己,就发送数据ptemp = ptemp->next;if(-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&(ptemp->c_addr), sizeof(ptemp->c_addr))){ERRLOG("sendto error");}}else{//如果是自己,就将自己在链表中删除node_t *pdel = ptemp->next;ptemp->next = pdel->next;free(pdel);pdel = NULL;}}
}
客户端:
1.创建的用户先登录进入群聊并发送登录信息
msg.code = 'L';strcpy(msg.txt, "加入群聊");if(-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, serveraddr_len)){ERRLOG("sendto error");}
2.创建的用户在登录后发送信息并接收服务器端的信息
子进程负责接收客户端发来的消息
while(1){memset(&msg, 0, sizeof(msg));if(-1 == recvfrom(sockfd, &msg, sizeof(msg), 0, NULL, NULL)){ERRLOG("recvfrom error");}printf("%8s : [%s]\n", msg.name, msg.txt);}
父进程负责发送信息,并检测客户端是否退出了聊天室,并将子进程终结在这里关闭客户端
while(1){msg.code = 'C';fgets(msg.txt, 128, stdin);msg.txt[strlen(msg.txt) - 1] = '\0';//判断是不是要退出if(!strcmp(msg.txt, "quit")){msg.code = 'Q';strcpy(msg.txt, "退出群聊");}if(-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, serveraddr_len)){ERRLOG("sendto error");}if(!strcmp(msg.txt, "退出群聊")){break;}}//先让子进程自杀kill(pid, SIGKILL);wait(NULL);close(sockfd);