多人聊天室
搭建一个可以实现多个用户同时在线聊天的多人聊天室
客户端
1.注册用户信息
bool Register()
89 {
90 if(!connect_server())
91 {
92 return false;
93 }
94
95 //1.发送注册标识
96 char type = REGISTER;
97 ssize_t send_size = send(tcp_sock,&type,1,0);
98
99 if(send_size < 0)
100 {
101 LOG(ERROR,"send Register type ")<<std::endl;
102 return false;
103 }
104 //2.发送注册内容
105 struct reg_info ri;
106 std::cout<<"please enter your name:";
107 std::cin>>ri.name_;
108
109 std::cout<<"please enter your from:";
110 std::cin>>ri.from_;
111
112 while(1)
113 {
114
115 std::string p_a1;//第一次输入的密码
116 std::cout<<"please enter your passwd:";
117 std::cin>>p_a1;
118 std::cout<<"please enter your passwd again :";
119 std::string p_a2;//第二次输入的密码
120 std::cin>>p_a2;
121 if(p_a1 == p_a2)
122 {
123 strcpy(ri.passwd_,p_a1.c_str());
124 break;
125 }
126 else
127 {
128 printf("The password is not the same\n");
129 }
130 }
131
132 send_size = send(tcp_sock,&ri,sizeof(ri),0);
133
134 if(send_size < 0)
135 {
136 LOG(ERROR,"send Register type ")<<std::endl;
137 return false;
138 }
139 //3.解析应答状态和获取用户id
140 struct reply_info re_i;
141 ssize_t re_i_size = recv(tcp_sock,&re_i,sizeof(re_i),0);
142 if(re_i_size < 0)
143 {
144 LOG(ERROR,"re_i_size ")<<std::endl;
145 return false;
146 }
147 else if(re_i_size == 0)
148 {
149 LOG(ERROR,"peer shutdown connect ")<<std::endl;
150 return false;
151 }
152
153 if(re_i.status != REGISTER_T)
154 {
155 LOG(ERROR,"Register failed")<<std::endl;
156 printf("注册失败\n");
157 return false;
158 }
159 LOG(INFO,"Register success user_id = ")<<re_i.user_id<<std::endl;
160 //printf("注册成功,user_id = %lu\n",re_i.user_id);
161 //4.注册成功之后保存用户信息
162 me.Name_ = ri.name_;
163 me.From_ = ri.from_;
164 me.Passwd_ = ri.passwd_;
165 me.Uer_ID = re_i.user_id;
166 close(tcp_sock);
167 return true;
168 }
2.登录用户
bool Login()
170 {
171 if(!connect_server())
172 {
173 return false;
174 }
175
176 //1.发送登录标识
177 char type = LOGIN;
178 ssize_t send_size = send(tcp_sock,&type,1,0);
179
180 if(send_size < 0)
181 {
182 LOG(ERROR,"send login type ")<<std::endl;
183 return false;
184 }
185 //2.发送登录数据
186 struct login_info li;
187 li.user_id = me.Uer_ID;
188 strcpy(li.passwd_,me.Passwd_.c_str());
189 send_size = send(tcp_sock,&li,sizeof(li),0);
190
191 if(send_size < 0)
192 {
193 LOG(ERROR,"send login type ")<<std::endl;
194 return false;
195 }
196 //3.解析登录状态
197 struct reply_info re_i;
198 ssize_t re_i_size = recv(tcp_sock,&re_i,sizeof(re_i),0);
199
200 if(re_i_size < 0)
201 {
202 LOG(ERROR,"re_i_size ")<<std::endl;
203 return false;
204 }
205 else if(re_i_size == 0)
206 {
207 LOG(ERROR,"peer shutdown connect ")<<std::endl;
208 return false;
209 }
210 if(re_i.status != LOGIN_T)
211 {
212 LOG(ERROR,"login failed status = ")<<re_i.status<<std::endl;
213 //rintf("登录失败\n");
214 //printf("login status = %d\n",re_i.status);
215 return false;
216 }
217 //printf("登录成功\n");
218 //printf("login status = %d\n",re_i.status);
219 LOG(INFO,"login success status = ")<<re_i.status<<std::endl;
220 return true;
221 }
3.发送消息至服务端
//udp数据收发
bool send_msg(const std::string& msg)
226 {
227 struct sockaddr_in p_addr;
228 p_addr.sin_family = AF_INET;
229 p_addr.sin_port = htons(udp_port);
230 p_addr.sin_addr.s_addr = inet_addr(server_ip.c_str());
231 ssize_t send_size = sendto(udp_sock,msg.c_str(),msg.size(),0,(struct sockaddr*)&p_addr,sizeof(p_addr));
232
233 if(send_size < 0)
234 {
235 // LOG(ERROR,"send msg to server failed\n");
236 return false;
237 }
238
239 //LOG(INFO,"send_msg to server success\n");
240 return true;
241 }
4.接收服务端消息
bool recv_msg(std::string* msg)
244 {
245 //LOG(INFO,"--------------\n");
246 char buf[MES_MAX_SIZE];
247 memset(buf,'\0',sizeof(buf));
248 struct sockaddr_in svr_addr;
249 //struct sockaddr svr_addr;
250 socklen_t svr_addr_len = sizeof(svr_addr);
251 // LOG(INFO,"**************\n");
252 //LOG(INFO,"22222222222222\n");
253 //ssize_t recv_size = recvfrom(udp_sock,buf,sizeof(buf)-1,0,(struct sockaddr*)&svr_addr,&svr_addr_len);
254 ssize_t recv_size = recvfrom(udp_sock,buf,sizeof(buf)-1,0,(struct sockaddr*)&svr_addr,&svr_addr_len);
255
256
257 //LOG(INFO,"000000000000\n");
258 //LOG(INFO,"ssssssssssss\n");
259 if(recv_size < 0)
260 {
261 // LOG(ERROR,"recv msg from sevser failed\n");
262 return false;
263 }
264 //LOG(INFO,"11111111111111\n");
265 (*msg).assign(buf,recv_size);
266 //LOG(INFO,"recv_msg from server success\n");
267 return true;
268 }
5.维护客户端窗口
调用了ncurses库,主要参考ncurses库常见用法
static void* Draw_window(void* arg)
269 {
270 pthread_num* pn = (pthread_num*) arg;
271 chat_window* cw = pn->win_p;
272 int num = pn->p_n;
273 switch (num)
274 {
275 case 0:
276 //即可以绘制窗口,也可以打出欢迎语
277 run_Draw_head(cw);
278 break;
279 case 1:
280 run_out_put(cw,pn->c_c);//聊天显示区域
281 break;
282 case 2:
283 run_user_list(cw,pn->c_c);//显示在线的用户
284 break;
285 case 3:
286 run_in_put(cw,pn->c_c);//显示输入区域
287 break;
288 default:
289 break;
290 }
291 //printf("i am %d pthread\n",pn->p_n);
292 delete pn;
294 }
服务端
1.处理客户端的注册、登录请求
static void* Login_Reg_Start(void* arg)
198 {
199 pthread_detach(pthread_self());
200 login_connect* lc = (login_connect*)arg;
201 chat_server* cs = (chat_server*)lc->get_server();
202 //注册,请求登录
203 // 请求从客户端来,recv(sock,buf,size,0)
204 char rues_type;
205 ssize_t recv_size = recv(lc->get_tcp_sock(),&rues_type,1,0);
206 if(recv_size < 0)
207 {
208 LOG(ERROR,"recv rues_type failed")<<std::endl;
209 return NULL;
210 }
211 else if(recv_size == 0)
212 {
213 LOG(ERROR,"client shutdowan connect")<<std::endl;
214 return NULL;
215 }
216
217 uint32_t userId = -1;
218 int userStatus = -1;
219 //正常接收到一个请求标识
220 switch(rues_type)
221 {
case REGISTER:
223 //用户管理模块--注册
224 userStatus = cs->deal_register(lc->get_tcp_sock(),&userId);
225 break;
226
227 case LOGIN:
228 //用户管理模块--登录
229 userStatus = cs->deal_login(lc->get_tcp_sock());
230 break;
231
232 case LOGINOUT:
233 //用户管理模块--退出登录
234 userStatus = cs->deal_login_out();
235 break;
236
237 default:
238 LOG(ERROR,"recv request typt default")<<std::endl;
239 break;
240 }
241
242 // 响应send(sock,buf,size,0)
243 reply_info ri;
244 ri.status = userStatus;
245 ri.user_id = userId;
246 ssize_t send_size = send(lc->get_tcp_sock(),&ri,sizeof(ri),0);
247 if(send_size < 0)
248 {
249 //如果发送数据失败,是否考虑应用层重新发送
250 LOG(ERROR,"send F")<<std::endl;
251 }
252 LOG(INFO,"send success")<<std::endl;
253
254 //将tcp连接释放掉
255 close(lc->get_tcp_sock());
256 delete lc;
257 return NULL;
258 }
2.创建生产线程,接收客户端的消息,并放入消息池
pthread_t tid;
121 for(int i = 0;i < PRODUCER_COUNT;i++)
122 {
123 int ret = pthread_create(&tid,NULL,product_msg_start,(void*)this);
124 if(ret < 0)
125 {
126 LOG(FATAL,"pthread_create product_msg_start")<<std::endl;
127 exit(4);
128 }
129 //printf("product_msg_start success\n");
130 ret = pthread_create(&tid,NULL,consume_msg_start,(void*)this);
131 if(ret < 0)
132 {
133 LOG(FATAL,"pthread_create consume_msg_start")<<std::endl;
134 exit(4);
135 }
136 //printf("consume_msg_start success\n");
137 }
static void* product_msg_start(void* arg)
171 {
172 pthread_detach(pthread_self());
173 chat_server* cs = (chat_server*)arg;
174 while(1)
175 {
176 //将接收到的消息缓存到数据池中
177 cs->recv_msg();
178 //printf("recv_msg\n");
179 }
180 return NULL;
181 }
3.创建消费线程,从消息池获取消息,并群发给所有在线用户
static void* consume_msg_start(void* arg)
184 {
185 pthread_detach(pthread_self());
186
187 chat_server* cs = (chat_server*)arg;
188 while(1)
189 {
190 //从数据池中获取数据
191 // printf("many_send_msg\n");
192 cs->many_send_msg();
193 }
194 return NULL;
195 }
为何会使用生产者-消费者模型以及数据池
可以解决多个用户同时发送消息,通过生产线程将消息放入数据池,消费线程从数据池取出,这样逐步处理,就可以将多个消息一一接收,一一存储,一一发送。
数据池:线程安全队列+2个接口(push pop)
线程安全:同步(条件变量)+互斥(互斥锁)
队列:STL:queue
push_msg_que();
pop_msg_que();
void push_msg_pool(std::string& msg)
27 {
28 pthread_mutex_lock(&msg_queue_lock);
29 while(is_full())
30 {
31 pthread_cond_wait(&product_queue,&msg_queue_lock);
32 }
33 msg_queue.push(msg);
34 pthread_mutex_unlock(&msg_queue_lock);
35 pthread_cond_signal(&consumer_queue);
36 }
37 void pop_msg_pool(std::string* msg)
38 {
39 pthread_mutex_lock(&msg_queue_lock);
40 while(msg_queue.empty())
41 {
42 pthread_cond_wait(&consumer_queue,&msg_queue_lock);
43 }
44 *msg = msg_queue.front();
45 msg_queue.pop();
46 pthread_mutex_unlock(&msg_queue_lock);
47 pthread_cond_signal(&product_queue);
48 }
private:
59 std::queue<std::string> msg_queue;
60 //约束队列大小,防止队列无限扩容,导致内存过大,被操作系统强杀
61 size_t capacity_;
62 //互斥锁
63 pthread_mutex_t msg_queue_lock;
64 //生产者条件变量
65 pthread_cond_t product_queue;
66 //消费者条件变量
67 pthread_cond_t consumer_queue;
为了方便阅读使用json,提高用户体验
主要参考C++json详解、C++中JSON的使用详解【转】
public:
15 //反序列化
16 //将客户端发送来的json数据反序列化
17 void deseruakuze(std::string mes)
18 {
19 Json::Reader r;
20 Json::Value v;
21 r.parse(mes,v,false);
22
23 name_ = v["name_"].asString();
24 from_ = v["from_"].asString();
25 msg_ = v["msg_"].asString();
26 user_id = v["user_id"].asInt();
27 }
28
29 void serialize(std::string* msg)
30 {
31 Json::Value v;
32 v["name_"] = name_;
33 v["from_"] = from_;
34 v["msg_"] = msg_;
35 v["user_id"] = user_id;
36
37 Json::FastWriter w;
38 *msg = w.write(v);
39 }
以上代码都一些简略代码,具体代码详见