编程实战:自己编写HTTP服务器(系列2:请求)

news2025/1/17 3:55:21

系列入口:编程实战:自己编写HTTP服务器(系列1:概述和应答)-CSDN博客

        本文介绍如何处理请求。

目录

一、概述

二、成员变量

三、接收并分解请求

四、完整代码

五、HTTP处理框架


一、概述

        请求和应答结构其实差不多,但是代码就多了很多分析过程(应答只需要拼接,简单多了)。

        应答的代码:

        整个类从RecvRequest()开始,然后就是各种Get。显然嘛,服务器收到的请求对服务器来说是个只读信息,修改也没啥用啊。

二、成员变量

	private:
		bool m_isCgi;//是否是CGI
		string m_fullrequest;//完整请求,包含了状态行、头标、数据
		long m_contentlength;//内容长度
		string m_method;//请求方法,GET、POST等
		string m_resource;//请求资源,/dir/file,不包括QueryString
		string m_querystring;//查询字符串
		PARAMLIST_T m_params;//参数表,QueryString,Form Data
		map<string, string > m_cookies;
		string m_content_type;//内容类型
		string m_content;//内容

        与CGI有关的可以无视,CGI没什么用,因为没人再写CGI了,所以支持CGI也没什么用。

        分解出的各种元素就在这些变量里,有一些内容是不同的分解程度。比如查询字符串同时被分解为参数,form数据也被分解为参数。虽然一般查询字符串和form data是不同的处理方式,但是其实我们都很希望在代码里能同样对待。

        从性能角度来说,这样预分解不是一个好策略,最佳方式是用到什么去找什么,因为大部分内容其实是用不到的。

三、接收并分解请求

        代码如下:

		//接收请求,此调用成功后才能调用GetXXXX
		//pFullRequest不为空直接从pFullRequest中分析而不需要从s接收
		bool RecvRequest(CSocket & s, char const * pFullRequest = NULL, bool(*pfNeedBrek)() = NULL)
		{
			Clear();
			m_isCgi = false;
			long timeout = 300;//接收超时

			string::size_type headlen = 0;

			char buf[1024];
			long count;
			string::size_type pos;

			if (NULL != pFullRequest)m_fullrequest = pFullRequest;

			//先接收请求头
			while (m_fullrequest.npos == (headlen = m_fullrequest.find("\r\n\r\n")))
			{
				if (!_Recv(s, timeout, buf, 1023, &count, pfNeedBrek))
				{
					//LOG<<"recv error"<<ENDE;
					s.Close();
					return false;
				}
				if (0 == count)
				{
					//LOG<<"client closed"<<ENDE;
					s.Close();
					return false;
				}
				buf[count] = '\0';
				m_fullrequest += buf;
			}
			//获得第一行
			pos = m_fullrequest.find("\r\n");
			string str;
			str = m_fullrequest.substr(0, pos);
			//获得method
			pos = str.find(" ");
			if (str.npos == pos)
			{
				return false;
			}
			this->m_method = str.substr(0, pos);
			str.erase(0, pos + 1);
			//去掉http版本
			pos = str.find_last_of(" ");
			if (str.npos == pos)
			{
				return false;
			}
			str.erase(pos);
			//剩下的是URL,问号之后的是GET参数
			pos = str.find_first_of("?");
			if (str.npos != pos)
			{
				this->m_resource = str.substr(0, pos);
				str.erase(0, pos + 1);
				m_querystring = str;
			}
			else
			{
				this->m_resource = str;
			}

			//分析Cookie
			AnalyzeCookie(m_fullrequest);

			//如果存在请求内容则接收请求内容
			pos = m_fullrequest.find("Content-Length:");
			if (m_fullrequest.npos == pos || pos >= headlen)
			{
				m_contentlength = 0;
				m_content = "";
			}
			else
			{
				m_contentlength = atol(m_fullrequest.c_str() + pos + strlen("Content-Length:"));
				while (m_fullrequest.size() < headlen + strlen("\r\n\r\n") + m_contentlength)
				{
					if (!_Recv(s, timeout, buf, 1023, &count, pfNeedBrek))
					{
						LOG << "recv error" << ENDE;
						s.Close();
						return false;
					}
					if (0 == count)
					{
						LOG << "client closed" << ENDE;
						s.Close();
						return false;
					}
					buf[count] = '\0';
					m_fullrequest += buf;
				}
				m_content = m_fullrequest.substr(headlen + strlen("\r\n\r\n"), m_contentlength);
			}

			//根据内容类型处理数据
			m_content_type = GetHeader("Content-Type");
			Trim(m_content_type);
			if ("application/x-www-form-urlencoded" == m_content_type)
			{
				if (m_querystring.size() != 0)m_querystring += "&";
				m_querystring += m_content;
			}
			else if (isContentTypeXml(m_content_type))
			{
				DEBUG_LOG << "content is xml" << ENDI;
			}
			else
			{
			}
			this->AnalyzeParam(m_querystring);
			return true;
		}

        _Recv()从socket接收数据:

		//出错或需要退出返回false,seconds为0不超时
		bool _Recv(CSocket & s, long seconds, char * buf, int buflen, long * pReadCount, bool(*pfNeedBrek)() = NULL)
		{
			bool isReady = false;
			if (!s.IsSocketReadReady2(seconds, isReady, pfNeedBrek))
			{
				LOG << "CSocket error" << ENDE;
				return false;
			}
			if (!isReady)
			{
				DEBUG_LOG << "CSocket timeout" << ENDI;
				return false;
			}
			else
			{
				DEBUG_LOG << "CSocket ready" << ENDI;
			}

			return s.Recv(buf, buflen, pReadCount);
		}

        用的是一个socket包装类,从我的另一篇文章里可以获得源代码。

        接受请求的代码虽然比较长,但是也没什么难点。

四、完整代码

        完整代码如下:

	//HTTP请求
	class CHttpRequest
	{
	public:
		typedef vector<pair<string, string > > PARAMLIST_T;
		static bool AnalyzeParam(string const & params, PARAMLIST_T & ret)
		{
			ret.clear();
			if (0 == params.size())return true;

			string tmpparams = params + "&";
			string::size_type pos;
			while (tmpparams.npos != (pos = tmpparams.find("&")))
			{
				string str = tmpparams.substr(0, pos);
				string::size_type pos2;
				if (str.npos != (pos2 = str.find("=")))
				{
					ret.push_back(pair<string, string >(CHtmlDoc::URLDecode(str.substr(0, pos2)), CHtmlDoc::URLDecode(str.substr(pos2 + 1))));
				}
				tmpparams.erase(0, pos + 1);
			}
			return true;
		}
	private:
		bool m_isCgi;//是否是CGI
		string m_fullrequest;//完整请求,包含了状态行、头标、数据
		long m_contentlength;//内容长度
		string m_method;//请求方法,GET、POST等
		string m_resource;//请求资源,/dir/file,不包括QueryString
		string m_querystring;//查询字符串
		PARAMLIST_T m_params;//参数表,QueryString,Form Data
		map<string, string > m_cookies;
		string m_content_type;//内容类型
		string m_content;//内容

		//出错或需要退出返回false,seconds为0不超时
		bool _Recv(CSocket & s, long seconds, char * buf, int buflen, long * pReadCount, bool(*pfNeedBrek)() = NULL)
		{
			bool isReady = false;
			if (!s.IsSocketReadReady2(seconds, isReady, pfNeedBrek))
			{
				LOG << "CSocket error" << ENDE;
				return false;
			}
			if (!isReady)
			{
				DEBUG_LOG << "CSocket timeout" << ENDI;
				return false;
			}
			else
			{
				DEBUG_LOG << "CSocket ready" << ENDI;
			}

			return s.Recv(buf, buflen, pReadCount);
		}
		bool AnalyzeParam(string const & params)
		{
			return AnalyzeParam(params, m_params);
		}
		void AnalyzeCookie(string const & request)
		{
			string COOKIE = "Cookie: ";
			string::size_type pos_start;
			string::size_type pos_end;

			pos_start = request.find(COOKIE);
			if (request.npos == pos_start)return;
			pos_end = request.find("\r\n", pos_start);
			if (request.npos == pos_end)return;
			string str = request.substr(pos_start + COOKIE.size(), pos_end - (pos_start + COOKIE.size()));
			CStringSplit st(str.c_str(), ";");
			for (CStringSplit::iterator it = st.begin(); it != st.end(); ++it)
			{
				CStringSplit st2(it->c_str(), "=");
				if (st2.size() != 2)continue;
				m_cookies[st2[0]] = st2[1];
			}
		}
	public:
		void Clear()
		{
			m_isCgi = false;
			m_fullrequest = "";
			m_contentlength = 0;
			m_method = "";
			m_resource = "";
			m_querystring = "";
			m_params.clear();
			m_cookies.clear();
			m_content_type = "";
			m_content = "";
		}
		map<string, string > const & GetCookies()const { return m_cookies; }
		string const & GetResource()const { return m_resource; }//获得请求资源,/dir/file,不包括QueryString
		string const & GetQueryString()const { return m_querystring; }//获得QueryString
		string const & GetMethod()const { return m_method; }//获得请求方法
		string const & GetContentType()const { return m_content_type; }//获得内容类型
		string const & GetContent()const { return m_content; }//获得内容
		//获得请求资源的后缀名
		string GetResourceType()const
		{
			long pos = m_resource.size() - 1;
			while (pos >= 0 && m_resource[pos] != '.' && m_resource[pos] != '/')--pos;
			if (pos < 0 || m_resource[pos] == '/')return "";
			return m_resource.substr(pos + 1);
		}
		//获得请求资源的文件名
		string GetResourceFileTitle()const
		{
			size_t start = m_resource.find_last_of('/');
			size_t end = m_resource.find_last_of('.');
			if (start != m_resource.npos && end != m_resource.npos)
			{
				if (end > start + 1)return m_resource.substr(start + 1, end - start - 1);
			}
			return "";
		}
		string const & GetFullRequest()const { return m_fullrequest; }//获得完整请求
		long GetContentLength()const { return m_contentlength; }//获得contentlength
		string const & GetParam(string const & param)const//获得参数,如果参数有重复则只能取得第一个
		{
			STATIC_C string const static_str;
			string const * p = GetParam(param, m_params);
			if (NULL != p)return *p;
			else return static_str;
		}
		static string const * GetParam(string const & param, PARAMLIST_T const & params)//获得参数,如果参数有重复则只能取得第一个
		{
			string _param = param;
			for (string::size_type i = 0; i < params.size(); ++i)
			{
				string _first = params[i].first;
				if (0 == stricmp(_param.c_str(), _first.c_str()))return &params[i].second;
			}
			return NULL;
		}
		string GetCookie(string const & cookie)const
		{
			map<string, string >::const_iterator it = this->m_cookies.find(cookie);
			if (it != this->m_cookies.end())return it->second;
			else return "";
		}
		string GetHeader(char const * _header)const
		{
			if (m_isCgi)
			{
				//获得HTTP头标,实际上是从环境变量中直接获取
				char const * penv = getenv(_header);
				if (NULL != penv)return penv;
				else return "";
			}
			else
			{
				string header = _header;
				header += ": ";
				string::size_type pos_start;
				string::size_type pos_end;

				if (m_fullrequest.npos == (pos_start = m_fullrequest.find(header)))return "";
				if (m_fullrequest.npos == (pos_end = m_fullrequest.find("\r\n", pos_start)))return "";
				string ret = m_fullrequest.substr(pos_start + header.size(), pos_end - pos_start - header.size());
				return Trim(ret);
			}
		}
		//替换Header
		static string & ReplaceHeader(string const & oldrequest, char const * _header, long _value, string & newrequest)
		{
			char buf[256];
			sprintf(buf, "%ld", _value);
			return ReplaceHeader(oldrequest, _header, buf, newrequest);
		}
		//替换Header
		static string & ReplaceHeader(string const & oldrequest, char const * _header, char const * _value, string & newrequest)
		{
			newrequest = oldrequest;
			string header = _header;
			header += ": ";
			string::size_type pos_start;
			string::size_type pos_end;

			if (newrequest.npos != (pos_start = newrequest.find(header)))
			{
				if (newrequest.npos != (pos_end = newrequest.find("\r\n", pos_start)))
				{
					newrequest.replace(pos_start + header.size(), pos_end - pos_start - header.size(), _value);
					return newrequest;
				}
				else
				{
					LOG << "格式错误,没有行结束符" << ENDE;
					return newrequest = "";
				}
			}
			else
			{
				if (newrequest.npos != (pos_start = newrequest.find("\r\n")))
				{
					newrequest.insert(pos_start + strlen("\r\n"), header + _value + "\r\n");
					return newrequest;
				}
				else
				{
					LOG << "格式错误,没有行结束符" << ENDE;
					return newrequest = "";
				}
			}
		}
		bool GetAuthorization(string & user, string & password)const
		{
			string AUTHORIZATION = "Authorization: Basic ";
			string::size_type pos_start;
			string::size_type pos_end;
			string base64;

			if (m_fullrequest.npos == (pos_start = m_fullrequest.find(AUTHORIZATION)))return false;
			if (m_fullrequest.npos == (pos_end = m_fullrequest.find("\r\n", pos_start)))return false;
			base64 = m_fullrequest.substr(pos_start + AUTHORIZATION.size(), pos_end - pos_start - AUTHORIZATION.size());

			char buf[2048];
			int len;
			if (0 > (len = CBase64::Base64Dec(buf, base64.c_str(), (int)base64.size())))
			{
				LOG << "base64解码错误" << ENDE;
			}
			buf[len] = '\0';
			DEBUG_LOG << buf << ENDI;
			CStringSplit st(buf, ":");
			if (st.size() != 2)return false;
			user = st[0];
			password = st[1];
			return true;
		}
		PARAMLIST_T GetParamList(PARAMLIST_T & params)const { return params = m_params; }//获得参数表,<参数名,参数值>数组
		//接收请求,此调用成功后才能调用GetXXXX
		//pFullRequest不为空直接从pFullRequest中分析而不需要从s接收
		bool RecvRequest(CSocket & s, char const * pFullRequest = NULL, bool(*pfNeedBrek)() = NULL)
		{
			Clear();
			m_isCgi = false;
			long timeout = 300;//接收超时

			string::size_type headlen = 0;

			char buf[1024];
			long count;
			string::size_type pos;

			if (NULL != pFullRequest)m_fullrequest = pFullRequest;

			//先接收请求头
			while (m_fullrequest.npos == (headlen = m_fullrequest.find("\r\n\r\n")))
			{
				if (!_Recv(s, timeout, buf, 1023, &count, pfNeedBrek))
				{
					//LOG<<"recv error"<<ENDE;
					s.Close();
					return false;
				}
				if (0 == count)
				{
					//LOG<<"client closed"<<ENDE;
					s.Close();
					return false;
				}
				buf[count] = '\0';
				m_fullrequest += buf;
			}
			//获得第一行
			pos = m_fullrequest.find("\r\n");
			string str;
			str = m_fullrequest.substr(0, pos);
			//获得method
			pos = str.find(" ");
			if (str.npos == pos)
			{
				return false;
			}
			this->m_method = str.substr(0, pos);
			str.erase(0, pos + 1);
			//去掉http版本
			pos = str.find_last_of(" ");
			if (str.npos == pos)
			{
				return false;
			}
			str.erase(pos);
			//剩下的是URL,问号之后的是GET参数
			pos = str.find_first_of("?");
			if (str.npos != pos)
			{
				this->m_resource = str.substr(0, pos);
				str.erase(0, pos + 1);
				m_querystring = str;
			}
			else
			{
				this->m_resource = str;
			}

			//分析Cookie
			AnalyzeCookie(m_fullrequest);

			//如果存在请求内容则接收请求内容
			pos = m_fullrequest.find("Content-Length:");
			if (m_fullrequest.npos == pos || pos >= headlen)
			{
				m_contentlength = 0;
				m_content = "";
			}
			else
			{
				m_contentlength = atol(m_fullrequest.c_str() + pos + strlen("Content-Length:"));
				while (m_fullrequest.size() < headlen + strlen("\r\n\r\n") + m_contentlength)
				{
					if (!_Recv(s, timeout, buf, 1023, &count, pfNeedBrek))
					{
						LOG << "recv error" << ENDE;
						s.Close();
						return false;
					}
					if (0 == count)
					{
						LOG << "client closed" << ENDE;
						s.Close();
						return false;
					}
					buf[count] = '\0';
					m_fullrequest += buf;
				}
				m_content = m_fullrequest.substr(headlen + strlen("\r\n\r\n"), m_contentlength);
			}

			//根据内容类型处理数据
			m_content_type = GetHeader("Content-Type");
			Trim(m_content_type);
			if ("application/x-www-form-urlencoded" == m_content_type)
			{
				if (m_querystring.size() != 0)m_querystring += "&";
				m_querystring += m_content;
			}
			else if (isContentTypeXml(m_content_type))
			{
				DEBUG_LOG << "content is xml" << ENDI;
			}
			else
			{
			}
			this->AnalyzeParam(m_querystring);
			return true;
		}
		static bool isContentTypeXml(string const & content_type)
		{
			return "Text/xml" == content_type;
		}
		bool InitCgiRequest(int argc, char ** argv)
		{
			Clear();
			m_isCgi = true;
			string tmp;

			thelog << "argc " << argc << endi;
			if (argc >= 2)
			{
				thelog << m_resource << endi;
				m_resource = argv[1];
			}
			if (argc >= 3)
			{
				tmp = argv[2];
				thelog << tmp << endi;
				m_querystring = tmp;
				AnalyzeParam(m_querystring);
			}

			char const * penv;
			if (NULL != (penv = getenv("REQUEST_METHOD")))
			{
				m_method = penv;
			}
			if (NULL != (penv = getenv("QUERY_STRING")))
			{
				tmp = penv;
				AnalyzeParam(tmp);
			}
			//如果是POST,内容从标准输入获取,因为没有文件结束符,长度必须从头标获取
			if (NULL != (penv = getenv("CONTENT_LENGTH")))
			{
				char * p = NULL;
				int content_length = atol(penv);
				p = new char[content_length + 1];
				if (NULL == p)return false;
				if (content_length != read(STDIN_FILENO, p, content_length))return false;
				p[content_length] = '\0';
				tmp = p;
				AnalyzeParam(tmp);
				delete[] p;
			}
			if (NULL != (penv = getenv("REQUEST_URI")))
			{
				tmp = penv;
				string::size_type pos = tmp.find_first_of("?");
				if (tmp.npos != pos)
				{
					m_resource = tmp.substr(0, pos);
				}
				else
				{
					m_resource = tmp;
				}
			}
			return true;
		}
		string RequestHtmlReport()const//报告请求信息,HTML格式
		{
			string ret = "HttpRequest:<HR/>";
			ret += m_method;
			ret += "&nbsp;";
			ret += m_resource;
			ret += "<BR>\n";
			for (PARAMLIST_T::size_type i = 0; i < m_params.size(); ++i)
			{
				ret += CHtmlDoc::HTMLEncode(m_params[i].first);
				ret += " = ";
				ret += CHtmlDoc::HTMLEncode(m_params[i].second);
				ret += "<BR>\n";
			}
			map<string, string >::const_iterator it;
			for (it = m_cookies.begin(); it != m_cookies.end(); ++it)
			{
				ret += "Cookie: ";
				ret += it->first + "=" + it->second + "<BR>\n";
			}
			ret += "<HR/>";
			time_t t1 = time(NULL);
			ret += asctime(localtime(&t1));
			return ret;
		}
	};

五、HTTP处理框架

        编程实战:自己编写HTTP服务器(系列3:处理框架)-CSDN博客

(这里是结束,但是并不是整个系列的结束)

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

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

相关文章

Axure原型图表组件库,数据可视化元件(Axure9大屏组件)

针对Axure制作的大屏图表元件库&#xff0c;帮助产品经理更高效地制作高保真图表原型&#xff0c;是产品经理必备元件工具。现分享完整的组件库&#xff0c;大家一起学习。 本组件库的图表模块&#xff0c;已包含所有常用的图表&#xff0c;以下为部分组件截图示意。文末可下载…

Axure原型组件库,数据可视化动态元件库(超详细Axure9可视化素材)

专门针对Axure制作的动态图表元件库&#xff0c;帮助产品经理更高效地制作高保真图表原型&#xff0c;是产品经理必备元件工具。现分享完整 Axure 9 的组件库&#xff0c;大家一起学习。&#xff08;如需 Axure 8组件请详见文末&#xff09; 每一个动态组件在原型文件中都配有…

NLP自然语言处理学习笔记

参考&#xff1a;NLP&#xff08;自然语言处理&#xff09;介绍 - 知乎 (zhihu.com) 一、NLP是什么 自然语言处理( Natural Language Processing, NLP)是计算机科学领域与人工智能领域中的一个重要方向。它研究能实现人与计算机之间用自然语言进行有效通信的各种理论和方法。自…

小航助学2023年6月GESP_Scratch二级真题(含题库答题软件账号)

需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号 单选题3.00分 删除编辑附件图文 答案:D 第1题高级语言编写的程序需要经过以下&#xff08; &#xff09;操作&#xff0c;可以生成在计算机上运行的可执行代码。 A、编辑B、…

Liunx系统使用超详细(四)~文件/文本相关命令2

承接文章Liunx系统使用超详细(四)~文件/文本相关命令1http://t.csdnimg.cn/f7G6S 目录 一、awk命令(三剑客之一) 1.1工作原理 1.2工作流程 1.3语法格式 1.3.1格式注释&#xff1a; 1.3.2模式&#xff08;pattern&#xff09;的类型&#xff1a; 1.3.3动作&#xff08;ac…

Java期末复习题之继承

点击返回标题->23年Java期末复习-CSDN博客 第1题. 设计一个学生类Student&#xff0c;其数据成员有name(姓名)、age(年龄)和degree(学位)。由Student类派生出本科生类Undergraduate和研究生类Graduate&#xff0c;本科生类Undergraduate增加成员specialty(专业)&#xff0c;…

vue-tree-color 组件实现组织架构图遇到的坑和解决方案以及实现

**1、前期工作可以先看看大佬的文章 **https://blog.csdn.net/Try_your_best_l/article/details/120173192?spm1001.2101.3001.6650.5&utm_mediumdistribute.pc_relevant.none-task-blog-2defaultCTRLISTRate-5-120173192-blog-128109597.235%5Ev38%5Epc_relevant_default…

案例精选|聚铭网络助力莱阳市人民医院打造合规性网络安全保障体系

莱阳市人民医院是一所集医疗、教学、科研、急救、康复、医养结合于一体的大型二级甲等综合性公立医院&#xff0c;占地总面积约3万平方米&#xff0c;建筑面积约7万平方米&#xff0c;设置科室48个&#xff0c;开放床位500张。医院先后获得山东省首批医养结合典型、山东省卒中防…

案例060:基于微信小程序考试系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

学习设计模式的一个好网址

常用设计模式有哪些&#xff1f; (refactoringguru.cn)https://refactoringguru.cn/design-patterns

【Proteus仿真】【STM32单片机】蓝牙遥控小车

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真STM32单片机控制器&#xff0c;使LCD1602液晶&#xff0c;L298电机&#xff0c;直流电机&#xff0c;HC05/06蓝牙模块等。 主要功能&#xff1a; 系统运行后&#xff0c;LCD1602显…

Matlab 点云对称性检测

文章目录 一、简介二、实现代码三、实现效果参考文献一、简介 这是一个很有趣的功能,它的思路其实与ICP算法的思路有些相似: 首先,它会初始化两个旋转角度,即绕x轴旋转与绕y轴旋转,初始的过程是将点对称(镜像)过去,计算与匹配点之间的距离误差,误差最小者为最优初始值…

【Docker二】docker网络模式、网络通信、数据管理

目录 一、docker网络模式&#xff1a; 1、概述 2、docker网络实现原理&#xff1a; 3、docker的网络模式&#xff1a; 3.1、bridge模式&#xff1a; 3.2、host模式&#xff1a; 3.3、container模式&#xff1a; 3.4、none模式&#xff1a; 3.5、自定义网络模式&#xf…

Spring基于注解存储对象

小王学习录 前言基于注解存储对象Controller (控制器存储)Service (服务存储)Repository (仓库存储)Component (组件存储)Configuration (配置存储)Bean(方法注解) 前言 上一篇文章中已经介绍了在Spring中存储Bean和取Bean的方法. 而在 Spring 中想要更简单的存储和读取对象的…

【面试经典150 | 二叉树】从前序与中序遍历序列构造二叉树

文章目录 写在前面Tag题目来源题目解读解题思路方法一&#xff1a;递归 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法&#xff0c;两到三天更新一篇文章&#xff0c;欢迎催更…… 专栏内容以分析题目为主&#xff0c;并附带一些对于本题涉及到的数据结构等内容…

docker基本管理和概念

1、定义&#xff1a;一个开源的应用容器引擎&#xff0c;基于go语言开发&#xff0c;运行在liunx系统中的开源的、轻量级的“虚拟机” docker的容器技术可以在一台主机上轻松的为任何应用创建一个轻量级的、可移植的、自给自足的容器 docker的宿主机是liunx系统&#xff0c;集…

Halcon threshold_sub_pix (Operator)

read_image(Image,fabrik) threshold_sub_pix(Image,Border,35) dev_display(Border)Image是输入的原始图像&#xff0c;Threshold是设定的阈值&#xff0c;Width和Height是像素值计算区域的大小&#xff0c;ThresholdedRegion是经过分割后得到的二值化结果。 在对图像进行二值…

Stable Diffusion AI绘画系列【20】:美丽动人的雀羽婚纱风,你心动了吗?

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

springboot基础(80):redis geo的应用

文章目录 前言redis GEO如何从地图上获取经纬度springboot 的相关方法调用准备redis服务器引用的依赖预设位置的keyGEOADD 添加位置GEORADIUS 获取指定经纬度附件的停车场&#xff08;deprecated&#xff09;GEORADIUS 获取指定成员附件的停车场&#xff08;deprecated&#xf…

百面嵌入式专栏(岗位分析)大疆嵌入式工程师【通信/流媒体】

文章目录 一、岗位简介二、解析2.1、网络协议2.2、音视频传输算法2.3、大规模音视频会议或直播系统 三、简历 沉淀、分享、成长&#xff0c;让自己和他人都能有所收获&#xff01;&#x1f604; &#x1f4e2;本篇我们将对大疆嵌入式工程师【通信/流媒体】岗位进行分析 。 一、…