改进思路
基于1.0版本的改进:
1.0版对于每个请求连接的客户端都会创建一个线程进行接收发送,如果连接的客户端太多,可能把内存占满,造成资源浪费。于是引入epoll,对每个存入epoll的套接字检测,对有消息传入的套接字处理,效率更高。
2.0版加入一个新线程检测同一文件夹下文件config.txt内容是否为 “close”,如果是,则关闭所有连接。
效果图
服务端源码
main.cpp
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<string>
#include<unistd.h>//linux下头文件
#include<string.h>
#include<netdb.h>//linux特有,与网络有关的结构体、宏、函数
#include<sys/types.h>//linux
#include<sys/socket.h>
#include<thread>
#include<vector>
#include<arpa/inet.h>//一些转换函数
#include"recv_and_send.h"
#include<unordered_map>
#include<sys/epoll.h>
using namespace std;
#define MAX_FD_NUMBER 500
int main(int argc, char *argv[]){
unordered_map<int, string> clientfd_to_name;
check_parameter(argc);
int listenfd;
int epfd;
struct epoll_event events[MAX_FD_NUMBER];
memset(events,0,sizeof(events));
creat_sever_socket(listenfd);
bind_socket(htons(atoi(argv[1])),listenfd);//绑定listenfd
begin_listen(listenfd);//开始监听端口
create_epoll(epfd, listenfd);
epoll_control(epfd, listenfd, events,clientfd_to_name);
cout << "服务端开始关闭......" <<endl;
close(listenfd);
close_socket(clientfd_to_name);
close(epfd);
return 0;
}
recv_and_send.h
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<string>
#include<unistd.h>//linux下头文件
#include<string.h>
#include<netdb.h>//linux特有,与网络有关的结构体、宏、函数
#include<sys/types.h>//linux
#include<sys/socket.h>
#include<thread>
#include<vector>
#include<arpa/inet.h>//一些转换函数
#include<unordered_map>
using namespace std;
#define MAX_FD_NUMBER 500
void check_parameter(const int &argc);
void creat_sever_socket(int & sockfd);
void bind_socket(short port,int &sockfd);
void begin_listen(int &sockfd);
bool recv_message(char *buffer,int buffer_size,int &clientfd);
bool send_message(char *buffer,int buffer_size,const int &clientfd);
void close_socket(unordered_map<int,string> &);
void recv_and_send_message(int clientfd,int &epfd,unordered_map<int,string> &clientfd_to_name,char *buffer,int buffer_size,char *send_buffer,int send_buffer_size);
void create_epoll(int &epfd,int &listenfd);
void accept_new_client(int &epfd, int &listenfd);
void epoll_control(int &epfd,int &listenfd, struct epoll_event *events,unordered_map<int,string> &clientfd_to_name);
void shutdown_server();
void delete_client(int &epfd,int &clientfd,unordered_map<int,string> &clientfd_to_name);
recv_and_send.cpp
#include<iostream>
#include<fstream>
#include<stdio.h>
#include<stdlib.h>
#include<string>
#include<unistd.h>//linux下头文件
#include<string.h>
#include<netdb.h>//linux特有,与网络有关的结构体、宏、函数
#include<sys/types.h>//linux
#include<sys/socket.h>
#include<thread>
#include<vector>
#include<arpa/inet.h>//一些转换函数
#include<unordered_map>
#include<sys/epoll.h>//epoll头文件
#include"recv_and_send.h"
using namespace std;
#define MAX_FD_NUMBER 500
bool if_shutdown = false;
void check_parameter(const int &argc);//检查参数个数
void creat_sever_socket(int & sockfd);//创建服务端socket套接字
void bind_socket(short port,int &sockfd);//绑定端口
void begin_listen(int &sockfd);//服务端开始监听
bool recv_message(char *buffer,int buffer_size,int &clientfd);//发送一条信息
bool send_message(char *buffer,int buffer_size,const int &clientfd);//接收一条信息
void close_socket(const vector<int> &sockfd);//关闭clientfd
void recv_and_send_message(int clientfd,int &epfd,unordered_map<int,string> &clientfd_to_name,char *buffer,int buffer_size,char *send_buffer,int send_buffer_size);//接收和发送
void create_epoll(int &epfd,int &listenfd);//创建epollfd
void accept_new_client(int &epfd, int &listenfd);//发现有新用户连接
void epoll_control(int &epfd,int &listenfd, struct epoll_event *events,unordered_map<int,string> &clientfd_to_name);//epoll主体,实现epoll_ctl和epoll_wait
void shutdown_server();//一直监听同一文件夹下的config.txt,发现键入close后,关闭服务端和所有客户端连接
void delete_client(int &epfd,int &clientfd,unordered_map<int,string> &clientfd_to_name);//出现客户端断开后,在epoll中删除客户端
void check_parameter(const int &argc){
cout << "check_parameter starting..." << endl;
if(argc != 2){
cout << "参数个数错误!!!" << endl;
exit(0);
}
}
void creat_sever_socket(int & sockfd){
cout << "creat_sever_socket starting..." << endl;
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1){
cout <<"创建服务端socket错误" <<endl;
exit(0);
}
}
void bind_socket(short port,int &sockfd){
cout << "bind_socket starting..." << endl;
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = port;
int ans = bind(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
if(ans != 0){
cout << "bind 绑定错误!" << endl;
exit(0);
}
}
void begin_listen(int &sockfd){
cout << "begin_listen starting..." << endl;
int ans = listen(sockfd,5);
if(ans != 0){
cout << "listen 监听错误!"<<endl;
exit(0);
}
}
bool recv_message(char *buffer,int buffer_size,int &clientfd){
memset(buffer,0,buffer_size);
int ans = recv(clientfd,buffer,buffer_size,0);
return ans > 0 ? true : false;
}
bool send_message(char *buffer,int buffer_size,int &clientfd){
int ans = send(clientfd,buffer,buffer_size,0);
return ans > 0 ? true : false;
}
void close_socket(unordered_map<int,string> &client_to_name){
for(unordered_map<int,string> :: iterator iter = client_to_name.begin(); iter != client_to_name.end(); iter++){
close(iter->first);
}
}
void create_epoll(int &epfd,int &listenfd){
cout << "create_epoll starting..." << endl;
epfd = epoll_create(MAX_FD_NUMBER);
struct epoll_event ev;
memset(&ev,0,sizeof(ev));
ev.data.fd = listenfd;
ev.events = EPOLLIN | EPOLLET;
if(epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev) == -1){//把linstenfd存入epoll池
cout << "epoll error" << endl;
exit(0);
}
}
void shutdown_server(){
FILE * fd = freopen("config.txt","w+",stdin);
char s[10];
char c[10] = "close";
while(!if_shutdown){
fseek(fd,0L,SEEK_SET);
fscanf(fd,"%[^\n]",s);
sleep(2);
if(strcmp(s,c) == 0){
if_shutdown = true;
break;
}
}
cout << "关闭程序" <<endl;
}
void epoll_control(int &epfd,int &listenfd, struct epoll_event *events ,unordered_map<int,string> &clientfd_to_name){
cout << "epoll_control starting..." << endl;
thread t(shutdown_server);
t.detach();
char buffer[1024];
char send_buffer[2048];
while(!if_shutdown){
int num = 0;
num = epoll_wait(epfd, events, MAX_FD_NUMBER, 20);
if(num == -1){
//epoll错误
cout << "epoll出现错误!" <<endl;
break;
}
for(int i = 0 ; i < num; i++){
if((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP) || !(events[i].events & EPOLLIN)){
cout << "错误响应" <<endl;
}
else if(events[i].data.fd == listenfd){
accept_new_client(epfd, listenfd);
}
else{
memset(buffer,0,sizeof(buffer));
memset(send_buffer,0,sizeof(send_buffer));
recv_and_send_message(events[i].data.fd,epfd,clientfd_to_name,buffer,sizeof(buffer),send_buffer,sizeof(send_buffer));
}
}
}
}
void accept_new_client(int &epfd, int &listenfd){
cout << "accept_new_client starting..." << endl;
int clientfd;
struct sockaddr_in clientaddr;
int socklen = sizeof(struct sockaddr_in);
memset(&clientaddr, 0, sizeof(clientaddr));
clientfd = accept(listenfd, (struct sockaddr*)&clientaddr, (socklen_t*)&socklen);
struct epoll_event ev;
memset(&ev, 0, sizeof(ev));
ev.data.fd = clientfd;
ev.events = EPOLLIN | EPOLLET;
epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);//加入容器
cout << "****客户端 " << inet_ntoa(clientaddr.sin_addr) << " 已连接****"<<endl;
cout << "****socket :"<< clientfd << " ****" << endl;
}
void recv_and_send_message(int clientfd,int &epfd,unordered_map<int,string> &clientfd_to_name,char *buffer,int buffer_size,char *send_buffer,int send_buffer_size){
if(recv_message(buffer, buffer_size, clientfd) == false){
cout << "接收错误 " << clientfd << " 退出" <<endl;
//接受错误说明此clinet退出,需要在epoll_ctl中去掉它
delete_client(epfd,clientfd,clientfd_to_name);
return ;
}
if(clientfd_to_name.find(clientfd) == clientfd_to_name.end()){
//没有名字,创建
string name = buffer;
clientfd_to_name.insert(make_pair(clientfd,name));
cout << clientfd << "创建姓名成功 : " << clientfd_to_name[clientfd] << endl;
char ok[] = "------昵称创建成功!------";
if(send_message(ok,sizeof(ok),clientfd) == false){
delete_client(epfd,clientfd,clientfd_to_name);
cout << clientfd << " 昵称创建失败, 断开连接" << endl;
}
return;
}
char tmp[] = " : ";
memset(send_buffer, 0, send_buffer_size);
strcat(send_buffer,clientfd_to_name[clientfd].c_str());
strcat(send_buffer,tmp);
strcat(send_buffer, buffer);
for(unordered_map<int,string> :: iterator iter = clientfd_to_name.begin() ; iter != clientfd_to_name.end(); iter++){
int i = iter->first;
if(iter->first != clientfd){
cout << "已向 " <<iter->second<<" (clientfd : " << iter->first << " ) 发送 " <<buffer << endl;
if(send_message(send_buffer,send_buffer_size,i) == false){
cout << "向 " << iter->first << " 发送失败" <<endl;
continue;
}
}
}
}
void delete_client(int &epfd,int &clientfd,unordered_map<int,string> &clientfd_to_name){
struct epoll_event ev;
ev.data.fd = clientfd;
ev.events = EPOLLIN | EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_DEL,clientfd, &ev);
clientfd_to_name.erase(clientfd);
}
需要改进的地方
可以把client写成类,便于操作
linux聊天不方便,如何移植到windows
等等。。。