HTTPcookie与session实现

1.HTTP Cookie

定义

HTTP Cookie (也称为 Web Cookie 、浏览器 Cookie 或简称 Cookie )是服务器发送到
用户浏览器并保存在浏览器上的一小块数据,它会在浏览器之后向同一服务器再次发
起请求时被携带并发送到服务器上。通常,它用于告知服务端两个请求是否来自同一
浏览器,如保持用户的登录状态、记录用户偏好等。
工作原理
用户第一次访问网站时,服务器会在响应的HTTP头中设置Set-Cookie字段,发送Cookie到用户的浏览器上,浏览器接收到cookie后,会保存到本地,之后的请求中,浏览器会自动在HTTP请求头中携带Cookie字段,将之前保存的Cookie信息发送到服务器。
分类
会话Cookie(Session Cookie):在浏览器关闭时失效
持久Cookie(Persistent Cookie):带有明确的过期日期或者持续时间,可以跨多个浏览器会话存在。
如果是一个持久的cookie,其实就是浏览器相关的,特定目录下的一个文件,但是无法直接查看文件,因为cookie文件一般时以二进制或者sqlite格式存储,查看就需要到浏览器对应的cookie中查看。

认识cookie

HTTP存在一个报头选项:Set-cookie,可以用来进行给浏览器设置cookie值。

在HTTP响应头中添加,客服端获取并自行设置并保存。

完整的Set-Cookie实例

C++
Set-Cookie: username=peter; expires=Thu, 18 Dec 2024 12:00:00
UTC; path=/; domain=.example.com; secure; HttpOnly
时间格式必须遵守 RFC 1123 标准,具体格式样例: Tue, 01 Jan 2030 12:34:56
GMT 或者 UTC( 推荐 )
关于时间解释
Tue : 星期二(星期几的缩写)
, : 逗号分隔符
01 : 日期(两位数表示)
Jan : 一月(月份的缩写)
2030 : 年份(四位数)
12:34:56 : 时间(小时、分钟、秒)
GMT : 格林威治标准时间(时区缩写)
区别:
计算方式: GMT 基于地球的自转和公转,而 UTC 基于原子钟。
准确度:由于 UTC 基于原子钟,它比基于地球自转的 GMT 更加精确。
在实际使用中, GMT UTC 之间的差别通常很小,大多数情况下可以互换使用。但
在需要高精度时间计量的场合,如科学研究、网络通信等, UTC 是更为准确的选择。
关于其他可选属性的解释
expires=<date> [ 要验证 ] :设置 Cookie 的过期日期 / 时间。如果未指定此属
性,则 Cookie 默认为会话 Cookie ,即当浏览器关闭时过期。
path=<some_path> [ 要验证 ] :限制 Cookie 发送到服务器的哪些路径。默认
为设置它的路径。
domain=<domain_name> [ 了解即可 ] :指定哪些主机可以接受该 Cookie 。默
认为设置它的主机。
secure [ 了解即可 ] :仅当使用 HTTPS 协议时才发送 Cookie 。这有助于防止
Cookie 在不安全的 HTTP 连接中被截获。
HttpOnly [ 了解即可 ] :标记 Cookie HttpOnly ,意味着该 Cookie 不能被
客户端脚本(如 JavaScript )访问。这有助于防止跨站脚本攻击( XSS )。

2.HTTP Session

定义
HTTP Session 是服务器用来跟踪用户与服务器交互期间用户状态的机制。由于 HTTP
协议是无状态的(每个请求都是独立的),因此服务器需要通过 Session 来记住用户
的信息。(无状态是无法记录历史访问)
工作原理
用户首次访问网站时,服务器会为用户创建一个唯一的Session ID,通过Cookie将其发送到客服端。客服端在之后的请求都会携带这个Session ID,服务区通过Session ID来标识用户,从而获取用户的会话信息。服务器通常会将Session信息存储在内存,数据库或缓存中。
安全性:
Cookie 相似,由于 Session ID 是在客户端和服务器之间传递的,因此也存 在被窃取的风险。
但是一般虽然 Cookie 被盗取了,但是用户只泄漏了一个 Session ID ,私密信息暂时没有被泄露的风险。Session ID 便于服务端进行客户端有效性的管理,比如异地登录。 可以通过 HTTPS 和设置合适的 Cookie 属性(如 HttpOnly Secure )来增强安全性。
没有session的风险

 没有Session时的隐私风险

如果没有Session机制,Web应用可能需要依赖其他方式(如Cookie)来跟踪用户状态。Cookie存储在客户端,容易被恶意程序获取和篡改,从而导致隐私泄露。此外,没有Session的加密通信工具可能无法提供端到端加密或去中心化存储,从而增加数据被窃取或监控的风险。

有session好处
  • 数据存储位置:Session数据存储在服务器端,而不是客户端。即使客户端的Session ID被窃取,攻击者也无法直接获取到存储在服务器上的会话数据。

  • 安全性:服务器可以更好地控制Session的生命周期,例如定期更新Session ID、限制Session的使用范围等,从而降低被攻击的风险。

补充
favicon.ico是一个网站图标,通常显示浏览器的标签页上,地址栏旁边或者收藏夹中。

3.cookie实现

HttpProtocol.hpp文件

第一个类是实现网络请求的,GetLine函数用来截取一行,以HttpSep(\r\n)来作为分隔符,则0到pos位置就是一行的内容了,把读取到的内容在删除掉,读取下一行内容。Deserialize函数是序列化,request是网络请求,用getline函数获取一行出来,第一行是请求行,单独存储起来,然后就是一直死循环读取剩下的报头数据,直到读到了空行,如果是空行的话ok为true且empty也会true则就会把剩下的内容全都放到req_content里,这里就是文本内容了,也是要单独存储的,empty不为空就说明还是在读取报头信息,就把报头信息放到vector里面存储,这里是把键和值一起放到string里面的。

#pragma once#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <memory>
#include <ctime>
#include "TcpServer.hpp"const std::string HttpSep = "\r\n";
// 可以配置的
const std::string homepage = "index.html";
const std::string wwwroot = "./wwwroot";class HttpRequest
{
public:HttpRequest():_req_blank(HttpSep),_path(wwwroot){}bool GetLine(std::string& str,std::string* line){auto pos=str.find(HttpSep);if(pos==std::string::npos)return false;*line=str.substr(0,pos);str.erase(0,pos+HttpSep.size());return true;}bool Deserialize(std::string& request){std::string line;bool ok=GetLine(request,&line);if(!ok)return false;_req_line=line;while(true){bool ok=GetLine(request,&line);if(ok&&line.empty()){_req_content=request;break;}else if(ok&&!line.empty()){_req_header.push_back(line);}elsebreak;}return true;}void DebugHttp(){std::cout<<"_req_line:"<<_req_line<<std::endl;for(auto& line:_req_header){std::cout<<"--->"<<line<<std::endl;}}~HttpRequest(){}
private:std::string _req_line;std::vector<std::string> _req_header;std::string _req_blank;std::string _req_content;std::string _method;std::string _url;std::string _http_version;std::string _path;std::string _suffix;
};

 第二个代码是网络响应的,构造函数设置网络版本和状态码和状态信息。SetCode函数设置状态码的值,SetDesc函数设置状态信息,MakeStatusLine函数构造状态行,网络版本+空格号+状态码+空格号+状态信息+换行拼接在一起。AddHeader函数把报头信息+换行符插入到vector中。AddContent函数设置文内容。Serialize函数先构造出状态行,然后把报头信息一一取出拼接在状态行的后面,for结束后标识状态行和报头信息已经填好了,接下来就是空行和文本内容的填充,把填充完整的响应返回。

const std::string BlankSep=" ";
const std::string LineSep="\r\n";class HttpResponse
{
public:HttpResponse():_http_version("HTTP/1.0"),_status_code(200),_status_code_desc("OK"),_resp_blank(LineSep){}void SetCode(int code){_status_code=code;}void SetDesc(const std::string& desc){_status_code_desc=desc;}void MakeStatusLine(){_status_line=_http_version+BlankSep+std::to_string(_status_code)+BlankSep+_status_code_desc+LineSep;}void AddHeader(const std::string& header){_resp_header.push_back(header+LineSep);}void AddContent(const std::string& content){_resp_content=content;}std::string Serialize(){MakeStatusLine();std::string response_str=_status_line;for(auto& header:_resp_header){response_str+=header;}response_str+=_resp_blank;response_str+=_resp_content;return response_str;}~HttpResponse(){}private:std::string _status_line;std::vector<std::string> _resp_header;std::string _resp_blank;std::string _resp_content;std::string _http_version;int _status_code;std::string _status_code_desc;};

这个类就是网络处理部分。构造函数要接收一个端口号,make_unique构造一个TcpServer对象出来,参数是端口号和一个bind对象,再调用Init函数。ProveCookieWrite函数和ProveCookieTimeOut函数都是添加报头信息,一个是用户名一个是用户名和过期时间(这里设置一分钟),ProvePath函数添加了路径,ProveOtherCookie添加了密码。HandlerHttp函数就是处理部分了,创建请求对象,调用请求的反序列化方法,显示报头信息,创建响应对象,设置状态码,状态信息,添加报头内容,添加文本内容helloworld(前端格式写的,最后网页访问会看到helloworld),最后返回完整的且序列化后的信息。

class Http
{std::string GetMonthName(int month){std::vector<std::string> months={"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};return months[month];}std::string GetWeekDayName(int day){std::vector<std::string> weekdays={"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};return weekdays[day];}std::string ExpireTimeUseRfc1123(int t) // 秒级别的未来UTC时间{time_t timeout = time(nullptr) + t;struct tm *tm = gmtime(&timeout); // 这里不能用localtime,因为localtime是默认带了时区的. gmtime获取的就是UTC统一时间char timebuffer[1024];//时间格式如: expires=Thu, 18 Dec 2024 12:00:00 UTCsnprintf(timebuffer, sizeof(timebuffer), "%s, %02d %s %d %02d:%02d:%02d UTC", GetWeekDayName(tm->tm_wday).c_str(),tm->tm_mday,GetMonthName(tm->tm_mon).c_str(),tm->tm_year+1900,tm->tm_hour,tm->tm_min,tm->tm_sec);return timebuffer;}
public:Http(uint16_t port){_tsvr=std::make_unique<TcpServer>(port,std::bind(&Http::HandlerHttp,this,std::placeholders::_1));_tsvr->Init();}std::string ProveCookieWrite(){return "Set-Cookie: username=zhangsan;";}std::string ProveCookieTimeOut(){return "Set-Cookie: username=zhangsan;expires="+ExpireTimeUseRfc1123(60)+";";}std::string ProvePath(){return "Set-Cookie: username=zhangsan; path=/a/b";}std::string ProveOtherCookie(){return "Set-Cookie: passwd=1234567890; path=/a/b;";}std::string HandlerHttp(std::string request){HttpRequest req;req.Deserialize(request);req.DebugHttp();lg.LogMessage(Debug,"%s\n",ExpireTimeUseRfc1123(60).c_str());HttpResponse resp;resp.SetCode(200);resp.SetCode(200);resp.SetDesc("OK");resp.AddHeader("Content-Type: text/html");resp.AddHeader(ProvePath());resp.AddHeader(ProveOtherCookie());resp.AddContent("<html><h1>helloworld</h1></html>");return resp.Serialize();}void Run(){_tsvr->Start();}~Http(){}
private:std::unique_ptr<TcpServer> _tsvr;
};

TcpServer.hpp文件

构造函数接收端口号和回调函数,以及套接字的值初始化等。ProcessConnection函数参数为Socket对象和InetAddr对象,调用sock方法recv在创建的套接字中读取内容,调用handler处理读取到的信息,把响应信息发送回去,关闭套接字(关闭后就不能传入命令了 telnet指令)。Start函数则会调用AcceptConnection函数进行连接(Tcp协议需要双方连接才能通信),创建task_t对象赋值为bind对象,bind是第一个是函数地址,但是类的话不是这样,类函数需要&来获取函数地址,后面的都是参数,而类函数还需要传递this指针,因为类函数隐式参数this。接着再创建实例并调用插入函数把task插入进去。

#pragma once#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <functional>
#include <memory>
#include "ThreadPool.hpp"
#include "Log.hpp"
#include "Comm.hpp"
#include "Socket.hpp"
#include "InetAddr.hpp"using namespace Net_Work;
const static int default_backlog = 6;
using task_t = std::function<void()>;
using handler_t = std::function<std::string(std::string)>;class TcpServer
{
public:TcpServer(uint16_t port,handler_t handler):_port(port),_isrunning(false),_listensock(new TcpSocket()),_handler(handler){}void Init(){_listensock->BuildListenSocketMethod(_port,default_backlog);}void ProcessConnection(std::shared_ptr<Socket> sock,InetAddr addr){std::string request_str;if(sock->Recv(&request_str,4096)){std::string response=_handler(request_str);sock->Send(response);sock->CloseSocket();}}void Start(){_isrunning=true;while(_isrunning){std::string clientip;uint16_t clientport;std::shared_ptr<Socket> sock=_listensock->AcceptConnection(&clientip,&clientport);if(sock==nullptr){continue;}InetAddr addr(clientip,clientport);task_t task=std::bind(&TcpServer::ProcessConnection,this,sock,addr);ThreadPool<task_t>::GetInstance()->Push(task);}_isrunning=false;}~TcpServer(){}
private:uint16_t _port;std::unique_ptr<Socket> _listensock;bool _isrunning;handler_t _handler;
};

成员函数与非成员函数的区别

  • 非成员函数 :对于非成员函数(即全局函数或命名空间内的函数),函数名本身可以直接作为函数的指针。例如,对于一个非成员函数 void foo(),可以将 foo 作为函数指针使用。

  • 成员函数 :成员函数属于类的一部分,它们的调用需要一个对象上下文(即通过对象来调用)。因此,要获取成员函数的地址,必须使用 & 操作符。例如,对于类 A 中的成员函数 void bar(),要获取其地址,必须写成 &A::bar

Main.cc文件

#include "HttpProtocol.hpp"
#include <memory>using namespace std;void Usage(std::string proc)
{std::cout<<"Usage: \n\t"<<proc<<" local_port\n"<<std::endl;
}int main(int argc,char* argv[])
{if(argc!=2){Usage(argv[0]);return Usage_Err;}uint16_t port=stoi(argv[1]);std::unique_ptr<Http> http=make_unique<Http>(port);http->Run();return 0;
}

ThreadPool.hpp文件

#pragma once#include <iostream>
#include <queue>
#include <vector>
#include <pthread.h>
#include <functional>
#include "Log.hpp"
#include "Thread.hpp"
#include "LockGuard.hpp"static const int defaultnum=5;class ThreadData
{
public:ThreadData(const std::string& name):threadname(name){}~ThreadData(){}
public:std::string threadname;
};template<class T>
class ThreadPool
{
private:ThreadPool(int thread_num=defaultnum):_thread_num(thread_num){pthread_mutex_init(&_mutex,nullptr);pthread_cond_init(&_cond,nullptr);for(int i=0;i<_thread_num;i++){std::string threadname="thread-";threadname+=std::to_string(i+1);ThreadData td(threadname);_threads.emplace_back(threadname,std::bind(&ThreadPool<T>::ThreadRun,this,std::placeholders::_1),td);lg.LogMessage(Info,"%s is created...\n", threadname.c_str());}}ThreadPool(const ThreadPool<T>& tp)=delete;const ThreadPool<T>& operator=(const ThreadPool<T>)=delete;public:static ThreadPool<T>* GetInstance(){if(instance==nullptr){LockGuard lockguard(&sig_lock);if(instance==nullptr){lg.LogMessage(Info, "创建单例成功...\n");instance =new ThreadPool<T>();instance->Start();}}return instance;}bool Start(){for(auto& thread:_threads){thread.Start();lg.LogMessage(Info, "%s is running ...\n", thread.ThreadName().c_str());}return true;}void ThreadWait(const ThreadData& td){lg.LogMessage(Debug, "no task, %s is sleeping...\n", td.threadname.c_str());pthread_cond_wait(&_cond,&_mutex);}void ThreadWakeup(){pthread_cond_signal(&_cond);}void checkSelf(){}void ThreadRun(ThreadData &td){while(true){T t;{LockGuard lockguard(&_mutex);while(_q.empty()){ThreadWait(td);lg.LogMessage(Debug, "thread %s is wakeup\n", td.threadname.c_str());}t=_q.front();_q.pop();}t();}}void Push(T& in){LockGuard Lockguard(&_mutex);_q.push(in);ThreadWakeup();}~ThreadPool(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);}void Wait(){for(auto& thread: _threads){thread.Join();}}
private:std::queue<T> _q;std::vector<Thread<ThreadData>> _threads;int _thread_num;pthread_mutex_t _mutex;pthread_cond_t _cond;static ThreadPool<T>* instance;static pthread_mutex_t sig_lock;};
template <class T>
ThreadPool<T> *ThreadPool<T>::instance = nullptr;
template <class T>
pthread_mutex_t ThreadPool<T>::sig_lock = PTHREAD_MUTEX_INITIALIZER;

thread.hpp文件

#pragma once#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>// 设计方的视角
//typedef std::function<void()> func_t;
template<class T>
using func_t = std::function<void(T&)>;template<class T>
class Thread
{
public:Thread(const std::string &threadname, func_t<T> func, T &data):_tid(0), _threadname(threadname), _isrunning(false), _func(func), _data(data){}static void *ThreadRoutine(void *args) // 类内方法,{// (void)args; // 仅仅是为了防止编译器有告警Thread *ts = static_cast<Thread *>(args);ts->_func(ts->_data);return nullptr;}bool Start(){int n = pthread_create(&_tid, nullptr, ThreadRoutine, this/*?*/);if(n == 0) {_isrunning = true;return true;}else return false;}bool Join(){if(!_isrunning) return true;int n = pthread_join(_tid, nullptr);if(n == 0){_isrunning = false;return true;}return false;}std::string ThreadName(){return _threadname;}bool IsRunning(){return _isrunning;}~Thread(){}
private:pthread_t _tid;std::string _threadname;bool _isrunning;func_t<T> _func;T _data;
};

代码流程

main中unique_ptr创建了Http对象的智能指针,就会调用Http类的构造函数,会给Http的tsvr赋值上一个make出来的TcpServer对象,参数是端口号和bind对象,Tcp类也会调用构造函数确定了端口号和回调函数handler,接着调用init函数,里面会调用BuildListenSocketMethod创建套接字并初始化。这时走完了main的第20行代码,接着21调用http的run函数,里面调用tsvr的Start函数,又到了Tcpserver部分,这里就会开始连接然后调用GetInstacnce函数,此时会跳到threadPool部分,执行threadpool的构造函数,锁和条件变量初始化,然后开始往_threads里面插入线程名字和bind对象(ThreadRun函数),因为_thread<Thread<ThreadData>>,所以就会调用thread构造函数,确定了回调函数是Threadrun函数,然后再继续执行GetInstance函数,开辟空间调用线程池的Start函数,start函数会调用所有线程的start函数(thread.start()),就会跳到thread部分,这里就会创建线程执行routine函数,然后调用回调函数threadrun,参数是线程对象ThreadData型,就会回到线程池部分,run会一直检查任务队列是否为空,为空就把这个线程放到条件变量中,不为空就取出任务队列并执行,这里是全部到条件变量中等待,还没有插入。此时才到Push(task)地方,开始插入任务,然后唤醒线程,唤醒的线程就会执行routine函数走回调函数func(Threadrun函数),run函数while队列不是空,就可以取出任务执行了,而这个任务就是httphandler函数,处理请求,构造响应返回。

4.session实现

HttpProcotol.hpp文件

请求类方法实现,GetLine把套接字读取的内容进行行划分,以间隔号作为基准划分多行。Parse函数把提取的行进行分离,stringstream字符串流,会以空格号为间隔把方法,uri和版本号提取出来存储在成员变量中,接着定义prefix,然后范围for遍历报头信息,如果报头信息中有与prefix一样的字符串就把跟prefix一样的部分之后的地方提取出来存储在cookie中,在插入到vector中管理,接着改变prefix值,在来一次,这次找的就是sessionid=,会提取=后面的值存储到vector中管理。

#pragma once#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <memory>
#include <ctime>
#include <functional>
#include "TcpServer.hpp"
#include "Session.hpp" // 引入sessionconst std::string HttpSep = "\r\n";
// 可以配置的
const std::string homepage = "index.html";
const std::string wwwroot = "./wwwroot";class HttpRequest
{
public:HttpRequest():_req_blank(HttpSep),_path(wwwroot){}bool GetLine(std::string& str,std::string* line){auto pos=str.find(HttpSep);if(pos==std::string::npos){return false;}*line=str.substr(0,pos);str.erase(0,pos+HttpSep.size());return true;}void Parse(){std::stringstream ss(_req_line);ss>>_method>>_url>>_http_version;std::string prefix="Cookie:";for(auto& line:_req_header){std::string cookie;if(strncmp(line.c_str(),prefix.c_str(),prefix.size())==0){cookie=line.substr(prefix.size());_cookies.emplace_back(cookie);break;}}prefix="sessionid=";for(const auto& cookie:_cookies){if(strncmp(cookie.c_str(),prefix.c_str(),prefix.size())==0){_sessionid=cookie.substr(prefix.size());}}}std::string Url(){return _url;}std::string SessionId(){return _sessionid;}bool Deserialize(std::string& request){std::string line;bool ok=GetLine(request,&line);if(!ok)return false;_req_line=line;while(true){bool ok=GetLine(request,&line);if(ok&& line.empty()){_req_content=request;break;}else if(ok&& !line.empty()){_req_header.push_back(line);}else{break;}}return true; }void DebugHttp(){std::cout<<"_req_line:"<<_req_line<<std::endl;for(auto& line:_req_header){std::cout<<"--->"<<line<<std::endl;}}~HttpRequest(){}
private:std::string _req_line;std::vector<std::string> _req_header;std::string _req_blank;std::string _req_content;std::string _method;std::string _url;std::string _http_version;std::string _path;std::string _suffix;std::vector<std::string> _cookies;std::string _sessionid;
};

 处理请求的地方,构建请求和应答类,反序列化请求并提取请求行的信息,定义number变量,走if判断,如果为login就说明要登陆,然后提取sessionid值,如果为空说明没有登陆过,定义用户名为number+1,创建session对象,会走session构造函数,得到名字和状态,调用Sessionmanager类方法Addsession插入创建的s对象,会创建一个sessionid值出来,通过随机数加时间戳,并把创建好的sessionid和s存储到map里面管理,把sessionid信息添加到报头中。如果不是login则会查看sessionid值,不为空就调用Getsession函数,在Session.hpp文件中的SessionManger类中成员变量_sessions中查看是否存在这个sessionid,存在就打印正在活跃,没有就标识过期,后面就是设置状态码状态信息等内容。

 std::string HandlerHttp(std::string request){HttpRequest req;HttpResponse resp;req.Deserialize(request);req.Parse();static int number=0;if(req.Url()=="/login"){std::string sessionid=req.SessionId();if(sessionid.empty()){std::string user="user-"+std::to_string(number++);session_ptr s=std::make_shared<Session>(user,"logined");std::string sessionid=_session_manager->AddSession(s);lg.LogMessage(Debug, "%s 被添加, sessionid是: %s\n", user.c_str(), sessionid.c_str());resp.AddHeader(ProveSession(sessionid));}}else{std::string sessionid=req.SessionId();if(!sessionid.empty()){session_ptr s=_session_manager->GetSession(sessionid);if(s!=nullptr)lg.LogMessage(Debug, "%s 正在活跃.\n", s->_username.c_str());elselg.LogMessage(Debug, "cookie : %s 已经过期, 需要清理\n", sessionid.c_str()); }}resp.SetCode(200);resp.SetDesc("OK");resp.AddHeader("Content-Type:text/html");resp.AddContent("<html><h1>helloworld</h1></html>");return resp.Serialize();}

Session.hpp文件

Session类主要是定义用户名字和访问什么文件,SessionManger则是管理session的类,添加session和检验并返回session。

#pragma once#include <iostream>
#include <string>
#include <memory>
#include <ctime>
#include <unistd.h>
#include <unordered_map>// 用来进行测试说明
class Session
{
public:Session(const std::string &username, const std::string &status):_username(username), _status(status){_create_time = time(nullptr); // 获取时间戳就行了,后面实际需要,就转化就转换一下}~Session(){}
public:std::string _username;std::string _status;uint64_t _create_time;uint64_t _time_out; // 60*5std::string vip; // vipint active; // std::string pos;//当然还可以再加任何其他信息,看你的需求
};using session_ptr = std::shared_ptr<Session>;class SessionManager
{
public:SessionManager(){srand(time(nullptr) ^ getpid());}std::string AddSession(session_ptr s){uint32_t randomid = rand() + time(nullptr); // 随机数+时间戳,实际有形成sessionid的库,比如boost uuid库,或者其他第三方库等std::string sessionid = std::to_string(randomid);_sessions.insert(std::make_pair(sessionid, s));return sessionid;}session_ptr GetSession(const std::string sessionid){if(_sessions.find(sessionid) == _sessions.end()) return nullptr;return _sessions[sessionid];}~SessionManager(){}
private:std::unordered_map<std::string, session_ptr> _sessions;
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/79323.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【算法基础】冒泡排序算法 - JAVA

一、算法基础 1.1 什么是冒泡排序 冒泡排序是一种简单直观的比较排序算法。它重复地走访待排序的数列&#xff0c;依次比较相邻两个元素&#xff0c;如果顺序错误就交换它们&#xff0c;直到没有元素需要交换为止。 1.2 基本思想 比较相邻元素&#xff1a;从头开始&#xf…

0902Redux_状态管理-react-仿低代码平台项目

文章目录 1 Redux 概述1.1 核心概念1.2 基本组成1.3 工作流程1.4 中间件&#xff08;Middleware&#xff09;1.5 适用场景1.6 优缺点1.7 Redux Toolkit&#xff08;现代推荐&#xff09;1.8 与其他工具的对比1.9 总结 2 todoList 待办事项案例3 Redux开发者工具3.1 核心功能3.2…

《ATPL地面培训教材13:飞行原理》——第6章:阻力

翻译&#xff1a;Leweslyh&#xff1b;工具&#xff1a;Cursor & Claude 3.7&#xff1b;过程稿 第6章&#xff1a;阻力 目录 引言寄生阻力诱导阻力减少诱导阻力的方法升力对寄生阻力的影响飞机总阻力飞机总重量对总阻力的影响高度对总阻力的影响构型对总阻力的影响速度稳…

C++总结01-类型相关

一、数据存储 1.程序数据段 • 静态&#xff08;全局&#xff09;数据区&#xff1a;全局变量、静态变量 • 堆内存&#xff1a;程序员手动分配、手动释放 • 栈内存&#xff1a;编译器自动分配、自动释放 • 常量区&#xff1a;编译时大小、值确定不可修改 2.程序代码段 •…

【Hot 100】94. 二叉树的中序遍历

目录 引言二叉树的中序遍历我的解题代码优化更清晰的表述建议&#xff1a; &#x1f64b;‍♂️ 作者&#xff1a;海码007&#x1f4dc; 专栏&#xff1a;算法专栏&#x1f4a5; 标题&#xff1a;【Hot 100】94. 二叉树的中序遍历❣️ 寄语&#xff1a;书到用时方恨少&#xff…

大语言模型(LLMs)微调技术总结

文章目录 全面总结当前大语言模型&#xff08;LLM&#xff09;微调技术1. 引言2. 为什么需要微调&#xff1f;3. 微调技术分类概览4. 各种微调技术详细介绍4.1 基础微调方法4.1.1 有监督微调&#xff08;Supervised Fine-Tuning, SFT&#xff09;4.1.2 全参数微调&#xff08;F…

解决Maven项目中报错“java不支持版本6即更高的版本 7”

错误背景 当Maven项目编译或运行时出现错误提示 Java不支持版本6即更高的版本7&#xff0c;通常是由于项目配置的JDK版本与当前环境或编译器设置不一致导致的。例如&#xff1a; 项目配置的Java版本为6或7&#xff0c;但实际使用的是JDK 17。Maven或IDE的编译器未正确指定目标…

C++笔记-多态(包含虚函数,纯虚函数和虚函数表等)

1.多态的概念 多态(polymorphism)的概念:通俗来说&#xff0c;就是多种形态。多态分为编译时多态(静态多态)和运行时多态(动态多态)&#xff0c;这里我们重点讲运行时多态&#xff0c;编译时多态(静态多态)和运行时多态(动态多态)。编译时多态(静态多态)主要就是我们前面讲的函…

【Unity】MVP框架的使用例子

在提到MVP之前&#xff0c;可以先看看这篇MVC的帖子&#xff1a; 【Unity】MVC的简单分享以及一个在UI中使用的例子 MVC的不足之处&#xff1a; 在MVC的使用中&#xff0c;会发现View层直接调用了Model层的引用&#xff0c;即这两个层之间存在着一定的耦合性&#xff0c;而MV…

前端js学算法-实践

1、两数之和 const twoSum (nums, target) > {const obj {}for (let m 0; m < nums.length; m) {const cur nums[m]const diff target - curif(obj.hasOwnProperty(diff)){ // 查询对象中是否存在目标值-当前值键值对console.log([obj[diff], m]) // 存在则直接获取…

《MATLAB实战训练营:从入门到工业级应用》趣味入门篇-用声音合成玩音乐:MATLAB电子琴制作(超级趣味实践版)

《MATLAB实战训练营&#xff1a;从入门到工业级应用》趣味入门篇-用声音合成玩音乐&#xff1a;MATLAB电子琴制作&#xff08;超级趣味实践版&#xff09; 开篇&#xff1a;当MATLAB遇见音乐 - 一场数字与艺术的浪漫邂逅 想象一下&#xff0c;你正坐在一台古老的钢琴前&#x…

实战探讨:为什么 Redis Zset 选择跳表?

在了解了跳表的原理和实现后&#xff0c;一个常见的问题&#xff08;尤其是在面试中&#xff09;随之而来&#xff1a;为什么像 Redis 的有序集合 (Zset) 这样的高性能组件会选择使用跳表&#xff0c;而不是大家熟知的平衡树&#xff08;如红黑树&#xff09;呢&#xff1f; 对…

数据结构-线性结构(链表、栈、队列)实现

公共头文件common.h #define TRUE 1 #define FALSE 0// 定义节点数据类型 #define DATA_TYPE int单链表C语言实现 SingleList.h #pragma once#include "common.h"typedef struct Node {DATA_TYPE data;struct Node *next; } Node;Node *initList();void headInser…

高中数学联赛模拟试题精选学数学系列第3套几何题

△ A B C \triangle ABC △ABC 的内切圆 ⊙ I \odot I ⊙I 分别与边 B C BC BC, C A CA CA, A B AB AB 相切于点 D D D, E E E, F F F, D D ′ DD DD′ 为 ⊙ I \odot I ⊙I 的直径, 过圆心 I I I 作直线 A D ′ AD AD′ 的垂线 l l l, 直线 l l l 分别与 D E DE…

使用 ossutil 上传文件到阿里云 OSS

在处理文件存储和传输时&#xff0c;阿里云的对象存储服务&#xff08;OSS&#xff09;是一个非常方便的选择。特别是在需要批量上传文件或通过命令行工具进行文件管理时&#xff0c;ossutil提供了强大的功能。本文将详细说明如何使用 ossutil 上传文件到阿里云 OSS&#xff0c…

DeepSeek与MySQL:开启数据智能新时代

目录 一、引言&#xff1a;技术融合的力量二、DeepSeek 与 MySQL&#xff1a;技术基石2.1 DeepSeek 技术探秘2.2 MySQL 数据库深度解析 三、DeepSeek 与 MySQL 集成&#xff1a;从理论到实践3.1 集成原理剖析3.2 集成步骤详解 四、应用案例&#xff1a;实战中的价值体现4.1 电商…

WebAPI项目从Newtonsoft.Json迁移到System.Text.Json踩坑备忘

1.控制器层方法返回类型不能为元组 控制器层方法返回类型为元组时&#xff0c;序列化结果为空。 因为元组没有属性只有field&#xff0c;除非使用IncludeFields参数专门指定&#xff0c;否则使用System.Text.Json进行序列化时不会序列化field var options new JsonSerializ…

202553-sql

目录 一、196. 删除重复的电子邮箱 - 力扣&#xff08;LeetCode&#xff09; 二、602. 好友申请 II &#xff1a;谁有最多的好友 - 力扣&#xff08;LeetCode&#xff09; 三、176. 第二高的薪水 - 力扣&#xff08;LeetCode&#xff09; 一、196. 删除重复的电子邮箱 - 力扣…

Spring Boot的GraalVM支持:构建低资源消耗微服务

文章目录 引言一、GraalVM原生镜像技术概述二、Spring Boot 3.x的GraalVM支持三、适配GraalVM的关键技术点四、构建原生镜像微服务实例五、性能优化与最佳实践总结 引言 微服务架构已成为企业应用开发的主流模式&#xff0c;但随着微服务数量的增加&#xff0c;资源消耗问题日…

pip 常用命令及配置

一、python -m pip install 和 pip install 的区别 在讲解 pip 的命令之前&#xff0c;我们有必要了解一下 python -m pip install 和 pip install 的区别&#xff0c;以便于我们在不同的场景使用不同的方式。 python -m pip install 命令使用 python 可执行文件将 pip 模块作…