聊天室功能:
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