聊天室功能:
1.其他用户上线通知。
2.其他用户下线通知。
3.获取在线列表。
4.用户之间点对点聊天。
实现思想:
1.服务器端主要是解析客户端发送过来的各种指令,并作出相应的处理和回应。
2.客户端采用select管理套接口IO和标准输入IO,当有事件发生,做出相应的处理。
3.采用链表存储每个客户端的网络信息,登录对应聊表插入(使用头插法),退出对应链表删除。点对点对应链表的遍历查找。
源代码:
服务器端:
#include "pub.h" #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <signal.h> #include <poll.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <iostream> #include <vector> #include <algorithm> #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ }while(0); //聊天室成员列表 USER_LIST client_list; void chat_server(int server_fd); void do_login(MESSAGE msg,int sock,struct sockaddr_in *clientaddr); void do_logout(MESSAGE msg,int sock,struct sockaddr_in *clientaddr); void do_online_user(int sock,struct sockaddr_in *clientaddr); int main(void) { int sock; struct sockaddr_in servaddr; if ((sock = socket(AF_INET,SOCK_DGRAM,0)) < 0) ERR_EXIT("socket"); memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(9999); servaddr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(sock,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0) ERR_EXIT("bind"); chat_server(sock); return 0; } void chat_server(int server_fd) { struct sockaddr_in clientaddr; socklen_t clientlen; int n; MESSAGE msg; while (1) { memset(&msg,0,sizeof(msg)); clientlen = sizeof(clientaddr); n = recvfrom(server_fd,&msg,sizeof(msg),0,(struct sockaddr*)&clientaddr,&clientlen); if (n < 0) { if (errno == EINTR) continue; ERR_EXIT("recvfrom"); } int cmd = ntohl(msg.cmd); switch (cmd) { case C2S_LOGIN: do_login(msg,server_fd,&clientaddr); break; case C2S_LOGOUT: do_logout(msg,server_fd,&clientaddr); break; case C2S_ONLINE_USER: do_online_user(server_fd,&clientaddr); break; default: break; } } } void do_login(MESSAGE msg,int sock,struct sockaddr_in *clientaddr) { USER_INFO user; strcpy(user.username,msg.body); user.ip = clientaddr->sin_addr.s_addr; user.port = clientaddr->sin_port; /*查找用户*/ USER_LIST::iterator it; for (it=client_list.begin(); it!=client_list.end(); ++it) { if (strcmp(it->username,msg.body) == 0) { break; } } if (it == client_list.end()) //没找到用户 { printf("has a user login:%s <-> %s:%d\n",msg.body,inet_ntoa(clientaddr->sin_addr),ntohs(clientaddr->sin_port)); client_list.push_back(user); //登录成功应答 MESSAGE reply_msg; memset(&reply_msg,0,sizeof(reply_msg)); reply_msg.cmd = htonl(S2C_LOGIN_OK); sendto(sock,&reply_msg,sizeof(reply_msg),0,(struct sockaddr*)clientaddr,sizeof(struct sockaddr_in)); int count = htonl((int)client_list.size()); //发送在线人数 sendto(sock,&count,sizeof(int),0,(struct sockaddr*)clientaddr,sizeof(struct sockaddr_in)); printf("sending user list information to:%s <-> %s:%d\n",msg.body,inet_ntoa(clientaddr->sin_addr),ntohs(clientaddr->sin_port)); //发送在线列表 for (it=client_list.begin(); it != client_list.end(); ++it) { sendto(sock,&*it,sizeof(USER_INFO),0,(struct sockaddr*)clientaddr,sizeof(struct sockaddr_in)); } //向其他用户通知 for (it=client_list.begin(); it != client_list.end(); ++it) { if (strcmp(it->username,msg.body) == 0) continue; struct sockaddr_in peeraddr; memset(&peeraddr,0,sizeof(peeraddr)); peeraddr.sin_family = AF_INET; peeraddr.sin_port = it->port; peeraddr.sin_addr.s_addr = it->ip; msg.cmd = htonl(S2C_SOMEONE_LOGIN); memcpy(msg.body,&user,sizeof(user)); if ( sendto(sock,&msg,sizeof(msg),0,(struct sockaddr*)&peeraddr,sizeof(struct sockaddr_in)) ) { printf("sending user list information to:%s <-> %s:%d\n",it->username,inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port)); } } } else { printf("user %s has already logined\n",msg.body); MESSAGE reply_msg; memset(&reply_msg,0,sizeof(reply_msg)); reply_msg.cmd = htonl(S2C_ALREADY_LOGINED); sendto(sock,&reply_msg,sizeof(reply_msg),0,(struct sockaddr*)clientaddr,sizeof(struct sockaddr_in)); } } void do_logout(MESSAGE msg,int sock,struct sockaddr_in *clientaddr) { USER_INFO user; strcpy(user.username,msg.body); user.ip = clientaddr->sin_addr.s_addr; user.port = clientaddr->sin_port; /*查找用户*/ USER_LIST::iterator it; for (it=client_list.begin(); it!=client_list.end(); ++it) { if (!strcmp(it->username,msg.body)) { printf("client %s logout server.\n",msg.body); client_list.erase(it); break; } } /*向其他用户通知*/ for (it=client_list.begin(); it != client_list.end(); ++it) { struct sockaddr_in peeraddr; memset(&peeraddr,0,sizeof(peeraddr)); peeraddr.sin_family = AF_INET; peeraddr.sin_port = it->port; peeraddr.sin_addr.s_addr = it->ip; msg.cmd = htonl(S2C_SOMEONE_LOGOUT); memcpy(msg.body,&user,sizeof(user)); sendto(sock,&msg,sizeof(msg),0,(struct sockaddr*)&peeraddr,sizeof(struct sockaddr_in)); } } void do_online_user(int sock,struct sockaddr_in *clientaddr) { MESSAGE msg; memset(&msg,0,sizeof(msg)); msg.cmd = htonl(S2C_ONLINE_USER); if ( sendto(sock,(const char*)&msg,sizeof(msg),0,(struct sockaddr*)clientaddr,sizeof(struct sockaddr_in)) ) { printf("sending user list information to: <-> %s:%d\n",inet_ntoa(clientaddr->sin_addr),ntohs(clientaddr->sin_port)); } USER_LIST::iterator it; int count = htonl((int)client_list.size()); //发送在线人数 sendto(sock,&count,sizeof(int),0,(struct sockaddr*)clientaddr,sizeof(struct sockaddr_in)); //发送在线列表 for (it=client_list.begin(); it != client_list.end(); ++it) { sendto(sock,&*it,sizeof(USER_INFO),0,(struct sockaddr*)clientaddr,sizeof(struct sockaddr_in)); } }
客户端:
#include <unistd.h> #include <sys/types.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #include "pub.h" #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } \ while(0); \ //当前用户名 char username[16]; //聊天室成员列表 USER_LIST client_list; void do_someone_login(MESSAGE &msg); void do_someone_logout(MESSAGE &msg); void do_getlist(int sock); void parse_cmd(char *cmdline,int sock,struct sockaddr_in *servaddr); bool sendmsgto(int sock,char *name,char *msg); void do_chat(const MESSAGE &msg); void chat_cli(int sock); int main(void) { int sock; if ((sock = socket(PF_INET,SOCK_DGRAM,0)) < 0) ERR_EXIT("socket"); chat_cli(sock); return 0; } void chat_cli(int sock) { struct sockaddr_in servaddr; memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(9999); servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); struct sockaddr_in peeraddr; socklen_t peerlen; MESSAGE msg; while (1) { memset(username,0,sizeof(username)); printf("please input your name:"); fflush(stdout); scanf("%s",username); memset(&msg,0,sizeof(msg)); msg.cmd = htonl(C2S_LOGIN); strcpy(msg.body,username); sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr *)&servaddr, sizeof(servaddr)); memset(&msg,0,sizeof(msg)); recvfrom(sock,&msg,sizeof(msg),0,NULL,NULL); int cmd = ntohl(msg.cmd); if (cmd == S2C_ALREADY_LOGINED) printf("user %s already logined server please use another username\n",username); else if(cmd == S2C_LOGIN_OK) { printf("user %s already logined server\n",username); break; } } int count; recvfrom(sock,&count,sizeof(int),0,NULL,NULL); //接受登录列表 int n = ntohl(count); printf("has %d users logined server\n",n); //接受登录用户名,ip,端口 for(int i=0; i<n;i++) { USER_INFO user; recvfrom(sock,&user,sizeof(USER_INFO),0,NULL,NULL); client_list.push_back(user); in_addr tmp; tmp.s_addr = user.ip; printf("%d %s <-> %s:%d\n",i,user.username,inet_ntoa(tmp),ntohs(user.port)); } printf("**********************************COMMANDS:*******************************\n"); printf(" send username msg\n"); printf(" list\n"); printf(" exit\n"); fd_set rset; FD_ZERO(&rset); int nready; while(1) { //把标准输入IO添加到select集合中 FD_SET(STDIN_FILENO,&rset); //把套接口IO添加到select集合中 FD_SET(sock,&rset); nready = select(sock+1,&rset,NULL,NULL,NULL); if(nready == -1) ERR_EXIT("select"); if(nready == 0) continue; //检测到套接口事件 if(FD_ISSET(sock,&rset)) { printf("检测到sock事件\n"); peerlen = sizeof(peeraddr); memset(&msg,0,sizeof(msg)); recvfrom(sock,&msg,sizeof(msg),0,(struct sockaddr*)&peeraddr,&peerlen); int cmd = ntohl(msg.cmd); printf("cmd=%d\n",cmd); switch(cmd) { case S2C_SOMEONE_LOGIN: do_someone_login(msg); break; case S2C_SOMEONE_LOGOUT: do_someone_logout(msg); break; case S2C_ONLINE_USER: do_getlist(sock); break; case C2C_CHAT: do_chat(msg); break; default: break; } } //如果是标准输入产生了事件 if(FD_ISSET(STDIN_FILENO,&rset)) { printf("检测到标准输入事件\n"); char cmdline[100] = {0}; if(fgets(cmdline,sizeof(cmdline),stdin) == NULL) break; if(cmdline[0] == '\n') continue; cmdline[strlen(cmdline) - 1] = '\0'; //调用命令解析函数 parse_cmd(cmdline,sock,&servaddr); } } } //其他用户登录通知 void do_someone_login(MESSAGE &msg) { USER_INFO *user = (USER_INFO*)msg.body; in_addr tmp; tmp.s_addr = user->ip; printf("%s <->%s:%d has logined server\n",user->username,inet_ntoa(tmp),ntohs(user->port)); client_list.push_back(*user); } //其他用户登出通知 void do_someone_logout(MESSAGE &msg) { USER_LIST::iterator it; //找到退出的客户端 for(it=client_list.begin(); it != client_list.end(); it++) { //找到终止循环 if(strcmp(it->username,msg.body) == 0) { break; } } if (it != client_list.end()) { client_list.erase(it); } printf("user %s has logout server\n",msg.body); } //得到在线列表 void do_getlist(int sock) { int count; recvfrom(sock,&count,sizeof(int),0,NULL,NULL); //接受登录列表 int n = ntohl(count); printf("has %d users logined server\n",n); client_list.clear(); //接受登录用户名,ip,端口 for(int i=0; i<n;i++) { USER_INFO user; recvfrom(sock,&user,sizeof(USER_INFO),0,NULL,NULL); client_list.push_back(user); in_addr tmp; tmp.s_addr = user.ip; printf("%d %s <-> %s:%d\n",i,user.username,inet_ntoa(tmp),ntohs(user.port)); } } //命令解析 void parse_cmd(char *cmdline,int sock,struct sockaddr_in *servaddr) { char cmd[10] = {0};//命令 char *p = NULL; //检测空格 p = strchr(cmdline,' '); if(p != NULL) *p = '\0'; strcpy(cmd,cmdline); if(strcmp(cmd,"exit") == 0) { MESSAGE msg; memset(&msg,0,sizeof(msg)); msg.cmd = htonl(C2S_LOGOUT); strcpy(msg.body,username); if( (sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr *)servaddr, sizeof(*servaddr))) < 0 ) ERR_EXIT("sendto"); printf("user %s has logout server\n",username); exit(EXIT_SUCCESS); } else if(strcmp(cmd,"list") == 0) { MESSAGE msg; memset(&msg,0,sizeof(msg)); msg.cmd = htonl(C2S_ONLINE_USER); if( (sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr *)servaddr, sizeof(*servaddr))) < 0 ) ERR_EXIT("sendto"); return; } else if(strcmp(cmd,"send") == 0) { char peername[16] = {0};//用户名 char msg[MSG_LEN] = {0};//要发送的消息 if(p == NULL) //用户名为空,重新输入 { printf("bad command\n"); printf("**********************************COMMANDS:*******************************\n"); printf(" send username msg\n"); printf(" list\n"); printf(" exit\n"); return; } /*send user msg*/ /* p p2 */ //命令解析 while(*p++ == ' '); char *p2; p2 = strchr(p,' '); if(p2 == NULL)//消息为空,重新输入 { printf("bad command\n"); printf("**********************************COMMANDS:*******************************\n"); printf(" send username msg\n"); printf(" list\n"); printf(" exit\n"); return; } *p2 = '\0'; strcpy(peername,p); while( *p2++ == ' '); if(p2 == NULL)//消息为空,重新输入 { printf("bad command\n"); printf("**********************************COMMANDS:*******************************\n"); printf(" send username msg\n"); printf(" list\n"); printf(" exit\n"); return; } strcpy(msg,p2); sendmsgto(sock,peername,msg); } else { printf("bad command\n"); printf("**********************************COMMANDS:*******************************\n"); printf(" send username msg\n"); printf(" list\n"); printf(" exit\n"); } } bool sendmsgto(int sock,char *name,char *msg) { if(strcmp(name,username) == 0) { printf("can't send message to self\n"); return false; } USER_LIST::iterator it; //找到退出的客户端 for(it=client_list.begin(); it != client_list.end(); it++) { //找到终止循环 if(strcmp(it->username,name) == 0) { break; } } if(it == client_list.end()) { printf("user %s has not logined server\n",name); return false; } MESSAGE m; memset(&m,0,sizeof(m)); m.cmd = htonl(C2C_CHAT); CHAT_MSG cm; strcpy(cm.username,username); strcpy(cm.msg,msg); memcpy(m.body,&cm,sizeof(cm)); struct sockaddr_in peeraddr; memset(&peeraddr,0,sizeof(peeraddr)); peeraddr.sin_family = AF_INET; peeraddr.sin_addr.s_addr = it->ip; peeraddr.sin_port = it->port; in_addr tmp; tmp.s_addr = it->ip; printf("sending message [%s] to user [%s] <->%s:%d\n",msg,name,inet_ntoa(tmp),ntohs(it->port)); sendto(sock,(const char*)&m,sizeof(m),0,(struct sockaddr *)&peeraddr, sizeof(peeraddr)); return true; } void do_chat(const MESSAGE &msg) { CHAT_MSG *cm = (CHAT_MSG*)msg.body; printf("recv a message haha [%s] form [%s]\n",cm->msg,cm->username); }
头文件:
#ifndef _PUB_H_ #define _PUB_H_ #include <list> #include <algorithm> using namespace std; //客户端向服务器发送 #define C2S_LOGIN 0x01 #define C2S_LOGOUT 0x02 #define C2S_ONLINE_USER 0x03 #define MSG_LEN 512 //服务器向客户端应答 #define S2C_LOGIN_OK 0x01 #define S2C_ALREADY_LOGINED 0x02 #define S2C_SOMEONE_LOGIN 0x03 #define S2C_SOMEONE_LOGOUT 0x04 #define S2C_ONLINE_USER 0x05 //客户端向客户端发送 #define C2C_CHAT 0x10 //定义消息结构 typedef struct message { int cmd; char body[MSG_LEN]; }MESSAGE; //用户信息 typedef struct user_info { char username[16]; unsigned int ip; unsigned short port; }USER_INFO; //客户端和客户端传递的消息结构 typedef struct chat_msg { char username[16]; char msg[100]; }CHAT_MSG; typedef list<USER_INFO> USER_LIST; #endif