7月份的时候还在学校那个时候想要学习嵌入式Linux,但是还没有买开发板来玩,再学linux系统编程,网络编程,Linux系统的文件IO,于是学完之后想做一个模仿qq的通信程序于是就有了这个“ailun.exe”,因为暑假去打工了,没时间写代码,后面开学才接着写完,有bug还没解决,不过我也不想解决了,拖着到现在才想着写一篇总结
1.客户端
客户端用的是qt开发,其实也没写很多复杂的功能,就有用到qt的Tcp编程,还有界面的设计,字符串处理,如何文件io,还有一个至关重要的也是用到非常多的就是信号与槽,不得不说这个信号与槽真的是很好用。
2.服务端
服务器端的话,是租了一个阿里云的轻量级应用服务器,一年好像才80,服务器端用到的技术就会多一些,像进程间通信的共享内存,Linux的网络编程,文件io,多线程,多进程,还用到了文件系统事件通知机制,算法方面的也是基本上就用到了字符串匹配(用作登录,注册,一系列的检测判断),没用到什么厉害的算法。
服务端没有用到makefile,基本上有点像伪cpp的代码
来仔细分析和回顾一下都用到哪些技术吧
(1)文件io
#include <fstream>
写入文件用的是 ofstream f(路径,ios::app);
这里的ios::app表示追加写入
f<<"字符串";即可写入文件
从文件中读出 ifstream f(路径);
同理string s="";
while(getline(f,s))
{
//处理逐行读出的文件
}
像其他的一些函数如is_open(),close();
(2)网络编程
主要包含头文件
<sys/socket.h>
<netinet/in.h>
<arpa/inet.h>
<unistd.h>
具体看我往期文章有详细介绍
(3)多线程
#include <pthread>void thread_one()
{//....
}int main()
{thread t1(thread_one);if (t1.joinable()) std::cout << "Thread 1 created successfully." << std::endl;else std::cerr << "Failed 1 to create thread." << std::endl;t1.join();//将线程分离return 0;
}
补充一点,为什么要分离线程?
- 当一个线程被分离后,它的资源会在其执行完成后自动被系统回收,无需其他线程显式地等待并清理其资源。
- 如果不分离线程,并且主线程在子线程还未完成时就结束了,可能会导致子线程所占用的资源(如内存、文件描述符等)无法被正确释放,从而造成资源泄漏。
(4)多进程
使用<thread>
和<mutex>
等标准库头文件
在服务器代码中关于进程的并没有使用很多函数,只用了一个fork()
在 Linux 和类 Unix 系统中,fork()
是一个系统调用,用于创建一个新的进程
- 当一个程序调用
fork()
时,操作系统会创建一个新的进程,这个新进程几乎是调用fork()
的进程的一个副本。 fork()
函数会返回两次:一次在父进程中,一次在子进程中。- 在父进程中,
fork()
返回新创建子进程的进程 ID。 - 在子进程中,
fork()
返回 0。
- 在父进程中,
(5)文件系统事件通知机制
inotify
:
inotify
是 Linux 内核提供的文件系统事件监控机制。- 它允许应用程序监控文件和目录的各种事件,如创建、修改、删除、移动等。
- 使用方法:
- 通过系统调用
inotify_init
创建一个inotify
实例。 - 使用
inotify_add_watch
将需要监控的文件或目录添加到inotify
实例中,并指定关注的事件类型。 - 应用程序可以通过读取
inotify
实例的文件描述符来获取发生的事件。
- 通过系统调用
#include <stdio.h>#include <sys/inotify.h>int main() {int fd = inotify_init();if (fd == -1) {perror("inotify_init");return 1;}int wd = inotify_add_watch(fd, "./test_directory", IN_CREATE | IN_MODIFY | IN_DELETE);if (wd == -1) {perror("inotify_add_watch");return 1;}char buffer[4096];while (1) {ssize_t len = read(fd, buffer, sizeof(buffer));if (len == -1) {perror("read");break;}for (ssize_t i = 0; i < len; ) {struct inotify_event *event = (struct inotify_event *)&buffer[i];if (event->len) {if (event->mask & IN_CREATE) {printf("Created: %s\n", event->name);} else if (event->mask & IN_MODIFY) {printf("Modified: %s\n", event->name);} else if (event->mask & IN_DELETE) {printf("Deleted: %s\n", event->name);}}i += sizeof(struct inotify_event) + event->len;}}inotify_rm_watch(fd, wd);close(fd);return 0;}
(6)共享内存
这个以前写过了,就不重复写了
struct my_map
{char name[20];int this_socket;
};//共享内存的指针
struct my_map *ptr;
int *ptriter;//创建共享内
//将内存分离出来
int shmid=shmget((key_t)0x5005,sizeof(struct my_map)*20,0640|IPC_CREAT);int shmiter=shmget((key_t)0x1001,4,0640|IPC_CREAT);//给每个进程创建指针指向这个共享内存
ptr=(struct my_map*)shmat(shmid,NULL,0);
ptriter=(int*)shmat(shmiter,NULL,0);
以下是完整代码
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <unistd.h>
#include <stdio.h>
#include <string>
#include <cerrno>
#include <pthread.h>
#include <vector>
#include <unistd.h>
#include <fstream>
#include <vector>
#include <unordered_map>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <thread>
#include <sys/inotify.h>using namespace std;class server
{
public:int socket_fd;int client_fd;vector<int> vec_client;string ip;unsigned short port;server():socket_fd(-1){}bool connect(string in_ip,unsigned short port){if(socket_fd!=-1)return false;socket_fd=socket(AF_INET,SOCK_STREAM,0);if(socket_fd==-1){return false;} struct sockaddr_in s_addr;s_addr.sin_family=AF_INET;s_addr.sin_port=htons(port);inet_aton(in_ip.c_str(),&s_addr.sin_addr);bind(socket_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));listen(socket_fd,10);return true;}bool send(string msg){if(socket_fd==-1)return false;int x=::send(client_fd,msg.data(),msg.size(),0);if(x<0)return false;return true; }bool recv(string &msg,int maxsize){ if(socket_fd==-1)return false;msg.clear();msg.resize(maxsize);int x=::recv(client_fd,&msg[0],maxsize,0);if(x<0){msg.clear();std::cerr << "接收失败!错误代码: " << errno << ", 错误信息: " << strerror(errno) << std::endl;return false;}msg.resize(x);return true;}bool accept(){if(socket_fd==-1)return false;struct sockaddr_in c_addr;socklen_t c_size=sizeof(struct sockaddr_in);int c_fd=::accept(socket_fd,(struct sockaddr *)&c_addr,&c_size);if(c_fd==-1){perror("accept failed!");::close(socket_fd);socket_fd=-1;return false;}cout<<"客服端ip:"<<inet_ntoa(c_addr.sin_addr)<<endl;client_fd=c_fd;vec_client.push_back(c_fd);return true;}bool close_socket(){if(socket_fd==-1)return false;::close(socket_fd);socket_fd=-1;return true;}bool close_client(){if(client_fd==-1)return false;::close(client_fd);client_fd=-1;return true;}~server(){close_socket();close_client();}
};struct my_map
{char name[20];int this_socket;
};//共享内存的指针
struct my_map *ptr;
int *ptriter;
server *server_socket;string this_client_name;vector<string> idvec,passwordvec;int inotify_fd;
int watch_descriptor;bool is_exit=false;void thread_one()
{string buffer;server *c=server_socket;bool flag=true;//发送用户清单,用于添加好友string send_user_list="all_user#";ifstream user("all_user");while(getline(user,buffer)){send_user_list+=buffer;send_user_list+='#';}if(c->send(send_user_list))cout<<"用户清单发送成功:!"<<send_user_list<<"\n";else cout<<"用户清单发送失败!\n";user.close();while(1){int err=c->recv(buffer,128);if(!err){cout<<"发生错误!\n";//当用户关闭客户端时结束进程is_exit=true;for(int i=0;i<*ptriter;i++){if(ptr[*ptriter].name==this_client_name){string ba="?????";strncpy(ptr[*ptriter].name,ba.c_str(),sizeof(ptr[*ptriter].name) - 1); break;}}cout<<this_client_name<<" 用户关闭客户端!\n\n";break;}else cout<<"【来自客户端的消息】 "<<buffer<<endl;//f<<buffer<<"\n";//发送信息的前面加了@@@@@//std::cout<<"buffer[0]="<<buffer[0]<<" "<<"buffer[1]="<<buffer[1]<<"\n\n";//添加好友,写入好友关系到文件if(buffer[0]=='*'){ifstream read_friend("friend.txt");if(read_friend.is_open())cout<<"好友关系文件已打开!\n";else cout<<"好友关系文件打开失败!\n";unordered_map<string,vector<string>> friend_list;string friend_item;string this_friend="";while(getline(read_friend,friend_item)){if(friend_item=="#####"){getline(read_friend,friend_item);this_friend=friend_item;continue;}friend_list[this_friend].push_back(friend_item);}read_friend.close();for(auto iter=friend_list.begin();iter!=friend_list.end();iter++){auto a=iter->first;auto b=iter->second;cout<<a<<"的好友列表有:";for(int i=0;i<b.size();i++){cout<<b[i]<<" ";}cout<<endl;}string add_who="";for(int i=1;i<buffer.size();i++){add_who+=buffer[i];}friend_list[this_client_name].push_back(add_who);friend_list[add_who].push_back(this_client_name);ofstream write_friend_list("friend.txt",ios::trunc);if(write_friend_list.is_open())cout<<"写入好友关系文件已打开!\n";else cout<<"写入好友关系文件打开失败!\n";write_friend_list<<"#####"<<endl;for(auto iter=friend_list.begin();iter!=friend_list.end();iter++){auto a=iter->first;auto b=iter->second;write_friend_list<<a<<endl;for(int i=0;i<b.size();i++){write_friend_list<<b[i]<<endl;}write_friend_list<<"#####"<<endl;}if(c->send("ok"))cout<<"添加成功\n";write_friend_list.close();continue;}if(buffer[0]=='@'&&buffer[1]=='@'){//cout<<"成功执行了这里的代码"<<endl;string goal="";int start=0;int iter;string this_msg="";for(int i=5;i<buffer.size();i++){if(buffer[i]=='#'){start++;continue;}if(start==1)continue;if(start==2)goal+=buffer[i];if(start==3)this_msg+=buffer[i];}cout<<"发送目标"<<goal<<"\n\n";bool myflag=false;cout<<"当前登录的账号有:\n";for(int i=0;i<=*ptriter;i++){cout<<ptr[i].name<<"\n";if(goal==ptr[i].name)myflag=true;}if(!myflag)cout<<"对方不在线!\n\n";string end_msg="$$发送消息给:$"+this_client_name+"$"+this_msg;//if(!c->send_this(end_msg,ptr[i].this_socket))cout<<"发送失败!\n";string file_name="msg/"+goal;ofstream file_sendmsg(file_name,ios::app);if(file_sendmsg.is_open()){file_sendmsg<<end_msg<<endl;file_sendmsg.close();cout<<goal<<"文件打开成功!\n\n";}else cout<<goal<<"文件打开失败!\n\n";file_sendmsg.close();this_thread::sleep_for(std::chrono::milliseconds(500));//暂停线程500毫秒}} }bool is_f=false;void thread_two()
{server *c=server_socket;int count=0;char buffer[1024];while (true) {int length = read(inotify_fd, buffer, sizeof(buffer));if (length < 0) {perror("read");break;}int i = 0;while (i < length) {struct inotify_event* event = reinterpret_cast<struct inotify_event*>(&buffer[i]);if (event->mask & IN_MODIFY){inotify_rm_watch(inotify_fd,watch_descriptor);std::ifstream inputFile("msg/"+this_client_name);std::string line;cout<<"打开了"<<this_client_name<<"的文件准备读取"<<endl;string last_send="";while(std::getline(inputFile, line)){count++;last_send+=(line+" ");cout<<"发送了:"<<line<<" "<<"当前已经发送"<<count<<"条数据"<<endl;}cout<<"总信息汇总:"<<last_send<<"\n\n";if(!c->send(last_send))cout<<"信息发送失败!"<<endl;inputFile.close();watch_descriptor=inotify_add_watch(inotify_fd,("msg/"+this_client_name).c_str(),IN_MODIFY);if (watch_descriptor < 0)perror("1inotify_add_watch failed");}i += sizeof(struct inotify_event) + event->len;inotify_rm_watch(inotify_fd,watch_descriptor);ofstream delete_content("msg/"+this_client_name,ios::trunc);delete_content.close();watch_descriptor=inotify_add_watch(inotify_fd,("msg/"+this_client_name).c_str(),IN_MODIFY);if (watch_descriptor < 0)perror("2inotify_add_watch failed");}}}int main(int argc,char *argv[])
{//创建共享内存//将内存分离出来int shmid=shmget((key_t)0x5005,sizeof(struct my_map)*20,0640|IPC_CREAT);int shmiter=shmget((key_t)0x1001,4,0640|IPC_CREAT); if(shmid==-1){cout<<"结构体共享内存创建失败"<<"\n\n";return 0;}if(shmiter==-1){cout<<"共享内存iter创建失败\n\n";}if(argc<3){cout<<"参数不够!";return 0;}cout<<"开始你的操作咯!\n"<<"进程id:"<<getpid()<<endl;server_socket=new server();if(server_socket->connect(argv[1],atoi(argv[2]))==false){cout<<"连接失败!\n";return 0;}while(1){//给每个进程创建指针指向这个共享内存ptr=(struct my_map*)shmat(shmid,NULL,0);ptriter=(int*)shmat(shmiter,NULL,0);if(!server_socket->accept())return 0;int pid=fork();if(pid>0)continue;else if(pid<0)return 0;//匹配密码,若成功开两个线程bool is_login=false;string buffer;while(1){server *c=server_socket;int err=c->recv(buffer,128);if(!err){cout<<"\n发生错误!尚未登陆,进程已结束!\n";return 0;break;}else cout<<"【来自客户端的消息】 "<<buffer<<endl;//注册功能if(buffer[0]=='$'){bool is_password=false;string p="",n="";cout<<"正在注册账号\n\n";ofstream r("passwordAndid.txt",ios::app);if(r.is_open())cout<<"注册文件打开成功!\n";else cout<<"注册文件打开失败!\n";for(int i=1;i<buffer.size();i++){if(buffer[i]=='#'){is_password=true;continue;}if(is_password)p+=buffer[i];else n+=buffer[i];}string ll="#"+p+"#"+n;r<<ll<<endl;ofstream ma("msg/"+n);if(ma.is_open()){cout<<"新注册用户的信息文本生成成功!\n";}//加入用户名单ofstream add_myself("all_user",ios::app);if(add_myself.is_open())cout<<"all_user 打开成功\n";else cout<<"all_user 打开失败\n";add_myself<<n<<endl;add_myself.close();cout<<"注册成功!\n\n";r.close();continue;}bool flag;if(buffer[0]=='#'){ifstream readfile("passwordAndid.txt");if(!readfile)cout<<"文件读取失败!";string line;cout<<"从文本中查找账号和密码如下:\n";while(getline(readfile,line)){string password,id;password="";id="";flag=true;for(int i=1;i<line.size();i++){if(line[i]=='#'){flag=false;continue;}if(flag)password+=line[i];if(!flag)id+=line[i];}cout<<id<<" "<<password<<"\n";idvec.push_back(id);passwordvec.push_back(password);}cout<<"\n";string curid,curpassword;flag=true;for(int i=1;i<buffer.size();i++){if(buffer[i]=='#'){flag=false;continue;}if(flag)curpassword+=buffer[i];if(!flag)curid+=buffer[i];}cout<<"curid="<<curid<<" "<<"curpassword="<<curpassword<<"\n";cout<<"正在匹配...........\n";for(int i=0;i<idvec.size();i++){if(curid==idvec[i]&&curpassword==passwordvec[i]){//if(!c->send("密码匹配正确!\n"))cout<<"发送失败!";//登录成功的套接字需要在共享内存中保存下来,以便后面的通信cout<<"*ptriter="<<*ptriter<<endl;//ptr[*ptriter].name=curid;strncpy(ptr[*ptriter].name, curid.c_str(), sizeof(ptr[*ptriter].name) - 1); ptr[*ptriter].name[sizeof(ptr[*ptriter].name) - 1] = '\0'; // 确保字符串以 null 结尾ptr[*ptriter].this_socket=c->client_fd;cout<<"ptr[*ptriter].name="<<ptr[*ptriter].name<<" "<<"ptr[*ptriter].this_socket="<<ptr[*ptriter].this_socket<<"\n";(*ptriter)++;cout<<"ptriter增加后="<<*ptriter<<"\n\n";//ss[curid]=c;cout<<"密码匹配正确!登录成功!\n";//标记当前进程对应的客户端this_client_name=curid;//找出该账号的好友列表ifstream friendfile("friend.txt");if(!friendfile)cout<<"friendfile open errer!\n";string this_ss,last_str;vector<string> arr;bool judge=false;getline(friendfile,last_str);while(getline(friendfile,this_ss)){if(this_ss==curid&&!judge&&last_str=="#####")judge=true;if(judge&&this_ss=="#####")break;if(judge){cout<<"添加的好友:"<<this_ss<<"\n";arr.push_back(this_ss);}last_str=this_ss;}string sendtotal="密码匹配正确!#";is_login=true;for(int i=0;i<arr.size();i++){//c->send(arr[i]);sendtotal+=arr[i];sendtotal+='#';cout<<arr[i]<<"\n";}c->send(sendtotal);break;}else cout<<idvec[i]<<" "<<passwordvec[i]<<"\n";}if(is_login)break;}}cout<<"当前账号"<<this_client_name<<"已经登录"<<endl;inotify_fd = inotify_init();if (inotify_fd < 0){perror("inotify_init");return 1;}//需要监视的文件描述符watch_descriptor = inotify_add_watch(inotify_fd, ("msg/"+this_client_name).c_str(), IN_MODIFY);if (watch_descriptor < 0){perror("inotify_add_watch");close(inotify_fd);return 1;}thread t1(thread_one);if (t1.joinable()) std::cout << "Thread 1 created successfully." << std::endl;else std::cerr << "Failed 1 to create thread." << std::endl;thread t2(thread_two);if (t2.joinable()) std::cout << "Thread 2 created successfully." << std::endl;else std::cerr << "Failed 2 to create thread." << std::endl;while(1){if(is_exit){cout<<"用户成功退出!\n\n";break;}}t1.join();t2.join();}return 0;
}