我始终觉得,天生的出身很重要,但后天的努力更加重要,所以如今的很多“科班”往往不如后天努力的“非科班”。所以,我们需要重新给“专业”和“专家”下一个定义:所谓专业,就是别人搞你不搞,这就是你的“专业”;你和别人同时搞,你比别人搞的好,就是“专家”。

说到http协议和http请求,很多人都知道,但是他们真的“知道”吗?我面试过很多求职者,一说到http协议,他们能滔滔不绝,然后我问他http协议的具体格式是啥样子的?很多人不清楚,不清楚就不清楚吧,他甚至能将http协议的头扯到html文档头部<head>。当我问http GET和POST请求的时候,GET请求是什么形式一般人都可以答出来,但是POST请求的数据放在哪里,服务器如何识别和解析这些POST数据,很多人又说不清道不明了。当说到http服务器时,很多人离开了apache、Nginx这样现成的http server之外,自己实现一个http服务器无从下手,如果实际应用场景有需要使用到一些简单http请求时,使用apache、Nginx这样重量级的http服务器程序实在劳师动众,你可以尝试自己实现一个简单的。

上面提到的问题,如果您不能清晰地回答出来,可以阅读一下这篇文章,这篇文章在不仅介绍http的格式,同时带领大家从零实现一个简单的http服务器程序。

一、项目背景

最近很多朋友希望我的flamingo服务器支持http协议,我自己也想做一个微信小程序,小程序通过http协议连接通过我的flamingo服务器进行聊天。flamingo是一个开源的即时通讯软件,目前除了服务器端,还有pc端、android端,后面会支持更多的终端。关于flamingo的介绍您可以参考这里:https://blog.csdn.net/analogous_love/article/details/69481542,这是我不断维护一个项目,其最新代码下载地址是:https://github.com/baloonwj/flamingo,更新日志:https://github.com/baloonwj/flamingo/issues/1。下面是flamingo的部分截图: 二、http协议介绍

  1. http协议是应用层协议,一般建立在tcp协议的基础之上(当然你的实现非要基于udp也是可以的),也就是说http协议的数据收发是通过tcp协议的。

  2. http协议也分为head和body两部分,但是我们一般说的html中的<head>和<body>标记不是http协议的头和身体,它们都是http协议的body部分。 

那么http协议的头到底长啥样子呢?我们来介绍一下http协议吧。

http协议的格式如下:

  1. GETPOST 请求的url路径(一般是去掉域名的路径) HTTP协议版本号
  2. 字段1名: 字段1\r\n
  3. 字段2名: 字段2\r\n
  4. ...
  5. 字段n : 字段n\r\n
  6. \r\n
  7. http协议包体内容

也就是说http协议由两部分组成:包头和包体,包头与包体之间使用一个\r\n分割,由于http协议包头的每一行都是以\r\n结束,所以http协议包头一般以\r\n\r\n结束。

举个例子,比如我们在浏览器中请求http://www.hootina.org/index_2013.php这个网址,这是一个典型的GET方法,浏览器组装的http数据包格式如下:

  1. GET /index_2013.php HTTP/1.1\r\n
  2. Host: www.hootina.org\r\n
  3. Connection: keep-alive\r\n
  4. Upgrade-Insecure-Requests: \r\n
  5. User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36\r\n
  6. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\n
  7. Accept-Encoding: gzip, deflate\r\n
  8. Accept-Language: zh-CN,zh;q=0.9,en;q=0.8\r\n
  9. \r\n

上面这个请求只有包头没有包体,http协议的包体不是必须的,也就是说GET请求一般没有包体。

如果GET请求带参数,那么一般是附加在请求的url后面,参数与参数之间使用&分割,例如请求http://www.hootina.org/index_2013.php?param1=value1&param2=value2&param3=value3,我们看下这个请求组装的的http协议包格式:

  1. GET /index_2013.php?param1=value1&param2=value2&param3=value3 HTTP/1.1\r\n
  2. Host: www.hootina.org\r\n
  3. Connection: keep-alive\r\n
  4. Upgrade-Insecure-Requests: \r\n
  5. User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36\r\n
  6. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\n
  7. Accept-Encoding: gzip, deflate\r\n
  8. Accept-Language: zh-CN,zh;q=0.9,en;q=0.8\r\n
  9. \r\n

对比一下,你现在知道http协议的GET参数放在协议包的什么位置了吧。

那么POST的数据放在什么位置呢?我们再12306网站(https://kyfw.12306.cn/otn/login/init)中登陆输入用户名和密码:

然后发现浏览器以POST方式组装了http协议包发送了我们的用户名、密码和其他一些信息,组装的包格式如下:

  1. POST /passport/web/login HTTP/1.1\r\n
  2. Host: kyfw..cn\r\n
  3. Connection: keep-alive\r\n
  4. Content-Length: \r\n
  5. Accept: application/json, text/javascript, */*; q=0.01\r\n
  6. Origin: https://kyfw.12306.cn\r\n
  7. X-Requested-With: XMLHttpRequest\r\n
  8. User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36\r\n
  9. Content-Type: application/x-www-form-urlencoded; charset=UTF-8\r\n
  10. Referer: https://kyfw.12306.cn/otn/login/init\r\n
  11. Accept-Encoding: gzip, deflate, br\r\n
  12. Accept-Language: zh-CN,zh;q=0.9,en;q=0.8\r\n
  13. Cookie: _passport_session=0b2cc5b86eb74bcc976bfa9dfef3e8a20712; _passport_ct=18d19b0930954d76b8057c732ce4cdcat8137; route=6f50b51faa11b987e576cdb301e545c4; RAIL_EXPIRATION=1526718782244; RAIL_DEVICEID=QuRAhOyIWv9lwWEhkq03x5Yl_livKZxx7gW6_-52oTZQda1c4zmVWxdw5Zk79xSDFHe9LJ57F8luYOFp_yahxDXQAOmEV8U1VgXavacuM2UPCFy3knfn42yTsJM3EYOy-hwpsP-jTb2OXevJj5acf40XsvsPDcM7; BIGipServerpool_passport=300745226.50215.0000; BIGipServerotn=1257243146.38945.0000; BIGipServerpassport=1005060362.50215.0000\r\n
  14. \r\n
  15. username=balloonwj%40qq.com&password=iloveyou&appid=otn

其中username=balloonwj%40qq.com&password=iloveyou&appid=otn就是我们的POST数据,但是大家需要注意的以下几种,不要搞错:

1.我的用户名是balloonwj@qq.com,到POST里面变成balloonwj%40qq.com,其中%40是@符号的16进制转码形式。这个码表可以参考这里:http://www.w3school.com.cn/tags/html_ref_urlencode.html

2.这里有三个变量,分别是username、password和appid,他们之间使用&符号分割,但是请注意的是,这不意味着传递多个POST变量时必须使用&符号分割,只不过这里是浏览器html表单(输入用户名和密码的文本框是html表单的一种)分割多个变量采用的默认方式而已。你可以根据你的需求,来自由定制,只要让服务器知道你的解析方式即可。比如可以这么分割:

方法一: username=balloonwj%40qq.com|password=iloveyou|appid=otn

方法二: username:balloonwj%40qq.com\r\n password:iloveyou\r\n appid:otn\r\n

方法三 :username,password,appid=balloonwj%40qq.com,iloveyou,otn 不管怎么分割,只要你能自己按一定的规则解析出来就可以了。

不知道你注意到没有,上面的POST数据放在http包体中,服务器如何解析呢?可能你没明白我的意思,看下图:

如上图所示,由于http协议是基于tcp协议的,tcp协议是流式协议,包头部分可以通过多出的\r\n来分界,包体部分如何分界呢?这是协议本身要解决的问题。目前一般有两种方式,第一种方式就是在包头中有个content-Length字段,这个字段的值的大小标识了POST数据的长度,上图中55就是数据username=balloonwj%40qq.com&password=iloveyou&appid=otn的长度,服务器收到一个数据包后,先从包头解析出这个字段的值,再根据这个值去读取相应长度的作为http协议的包体数据。还有一个格式叫做http chunked技术(分块),大致意思是将大包分成小包,具体的详情有兴趣的读者可以自行搜索学习。

二、http客户端实现

如果您能掌握以上说的http协议,你就可以自己通过代码组装http协议发送http请求了(也是各种开源http库的做法)。我们先简单地介绍一下如何模拟发送http。举个例子,我们要请求http://www.hootina.org/index_2013.php,那么我们可以先通过域名得到ip地址,即通过socket API gethostbyname()得到www.hootina.org的ip地址,由于http服务器默认的端口号是80,有了域名和ip地址之后,我们使用socket API connect()去连接服务器,然后根据上面介绍的格式组装成http协议包,利用socket API send()函数发出去,如果服务器有应答,我们可以使用socket API recv()去接受数据,接下来就是解析数据(先解析包头和包体)。

三、http服务器实现

我们这里简化一些问题,假设客户端发送的请求都是GET请求,当客户端发来http请求之后,我们拿到http包后就做相应的处理。我们以为我们的flamingo服务器实现一个支持http格式的注册请求为例。假设用户在浏览器里面输入以下网址,就可以实现一个注册功能:

http://120.55.94.78:12345/register.do?p={"username": "13917043329", "nickname": "balloon", "password": "123"}

这里我们的http协议使用的是12345端口号而不是默认的80端口。如何侦听12345端口,这个是非常基础的知识了,这里就不介绍了。当我们收到数据以后:

  1. void HttpSession::OnRead(const std::shared_ptr<TcpConnection>& conn, Buffer* pBuffer, Timestamp receivTime)
  2. {
  3. //LOG_INFO << "Recv a http request from " << conn->peerAddress().toIpPort();
  4.  
  5. string inbuf;
  6. //先把所有数据都取出来
  7. inbuf.append(pBuffer->peek(), pBuffer->readableBytes());
  8. //因为一个http包头的数据至少\r\n\r\n,所以大于4个字符
  9. //小于等于4个字符,说明数据未收完,退出,等待网络底层接着收取
  10. if (inbuf.length() <= )
  11. return;
  12.  
  13. //我们收到的GET请求数据包一般格式如下:
  14. /* GET /register.do?p={%22username%22:%20%2213917043329%22,%20%22nickname%22:%20%22balloon%22,%20%22password%22:%20%22123%22} HTTP/1.1\r\n Host: 120.55.94.78:12345\r\n Connection: keep-alive\r\n Upgrade-Insecure-Requests: 1\r\n User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36\r\n Accept-Encoding: gzip, deflate\r\n Accept-Language: zh-CN, zh; q=0.9, en; q=0.8\r\n \r\n */
  15. //检查是否以\r\n\r\n结束,如果不是说明包头不完整,退出
  16. string end = inbuf.substr(inbuf.length() - );
  17. if (end != "\r\n\r\n")
  18. return;
  19.  
  20. //以\r\n分割每一行
  21. std::vector<string> lines;
  22. StringUtil::Split(inbuf, lines, "\r\n");
  23. if (lines.size() < || lines[].empty())
  24. {
  25. conn->forceClose();
  26. return;
  27. }
  28.  
  29. std::vector<string> chunk;
  30. StringUtil::Split(lines[], chunk, " ");
  31. //chunk中至少有三个字符串:GET+url+HTTP版本号
  32. if (chunk.size() < )
  33. {
  34. conn->forceClose();
  35. return;
  36. }
  37.  
  38. LOG_INFO << "url: " << chunk[] << " from " << conn->peerAddress().toIpPort();
  39. //inbuf = /register.do?p={%22username%22:%20%2213917043329%22,%20%22nickname%22:%20%22balloon%22,%20%22password%22:%20%22123%22}
  40. std::vector<string> part;
  41. //通过?分割成前后两端,前面是url,后面是参数
  42. StringUtil::Split(chunk[], part, "?");
  43. //chunk中至少有三个字符串:GET+url+HTTP版本号
  44. if (part.size() < )
  45. {
  46. conn->forceClose();
  47. return;
  48. }
  49.  
  50. string url = part[];
  51. string param = part[].substr();
  52.  
  53. if (!Process(conn, url, param))
  54. {
  55. LOG_ERROR << "handle http request error, from:" << conn->peerAddress().toIpPort() << ", request: " << pBuffer->retrieveAllAsString();
  56. }
  57.  
  58. //短连接,处理完关闭连接
  59. conn->forceClose();
  60. }

代码注释都写的很清楚,我们先利用\r\n分割得到每一行,其中第一行的数据是:

  1. GET /register.do?p={%22username%:%%%,%%22nickname%:%%22balloon%,%%22password%:%%%} HTTP/1.1

其中%22是双引号的url转码形式,%20是空格的url转码形式,然后我们根据空格分成三段,其中第二段就是我们的网址和参数:

  1. /register.do?p={%22username%:%%%,%%22nickname%:%%22balloon%,%%22password%:%%%}

然后我们根据网址与参数之间的问号将这个分成两段:第一段是网址,第二段是参数:

  1. bool HttpSession::Process(const std::shared_ptr<TcpConnection>& conn, const std::string& url, const std::string& param)
  2. {
  3. if (url.empty())
  4. return false;
  5.  
  6. if (url == "/register.do")
  7. {
  8. OnRegisterResponse(param, conn);
  9. }
  10. else if (url == "/login.do")
  11. {
  12. OnLoginResponse(param, conn);
  13. }
  14. else if (url == "/getfriendlist.do")
  15. {
  16.  
  17. }
  18. else if (url == "/getgroupmembers.do")
  19. {
  20.  
  21. }
  22. else
  23. return false;
  24.  
  25. return true;
  26. }

然后我们根据url匹配网址,如果是注册请求,会走注册处理逻辑:

  1. void HttpSession::OnRegisterResponse(const std::string& data, const std::shared_ptr<TcpConnection>& conn)
  2. {
  3. string retData;
  4. string decodeData;
  5. URLEncodeUtil::Decode(data, decodeData);
  6. BussinessLogic::RegisterUser(decodeData, conn, false, retData);
  7. if (!retData.empty())
  8. {
  9. std::string response;
  10. URLEncodeUtil::Encode(retData, response);
  11. MakeupResponse(retData, response);
  12. conn->send(response);
  13.  
  14. LOG_INFO << "Response to client: cmd=msg_type_register" << ", data=" << retData << conn->peerAddress().toIpPort();;
  15. }
  16. }

注册结果放在retData中,为了发给客户端,我们将结果中的特殊字符如双引号转码,如返回结果是:

{"code":0, "msg":"ok"} 会被转码成:

{%22code%22:0,%20%22msg%22:%22ok%22} 然后,将数据组装成http协议发给客户端,给客户端的应答协议与http请求协议有一点点差别,就是将请求的url路径换成所谓的http响应码,如200表示应答正常返回、404页面不存在。应答协议格式如下:

  1. GETPOST 响应码 HTTP协议版本号
  2. 字段1名: 字段1\r\n
  3. 字段2名: 字段2\r\n
  4. ...
  5. 字段n : 字段n\r\n
  6. \r\n
  7. http协议包体内容

举个例子如:

  1. HTTP/1.1 OK\r\n
  2. Content-Type: text/html\r\n
  3. Content-Length:\r\n
  4. \r\n
  5. {%22code%:%,%%22msg%:%%22ok%}

注意,包头中的Content-Length长度必须正好是包体{%22code%22:%200,%20%22msg%22:%20%22ok%22}的长度,这里是42。这也符合我们浏览器的返回结果:

当然,需要注意的是,我们一般说http连接一般是短连接,这里我们也实现了这个功能(看上面的代码:conn->forceClose();),不管一个http请求是否成功,服务器处理后立马就关闭连接。

当然,这里还有一些没处理好的地方,如果你仔细观察上面的代码就会发现这个问题,就是不满足一个http包头时的处理,如果某个客户端(不是使用浏览器)通过程序模拟了一个连接请求,但是迟迟不发含有\r\n\r\n的数据,这路连接将会一直占用。我们可以判断收到的数据长度,防止别有用心的客户端给我们的服务器乱发数据。我们假定,我们能处理的最大url长度是2048,如果用户发送的数据累积不含\r\n\r\n,且超过2048个,我们认为连接非法,将连接断开。代码修改成如下形式:

  1. void HttpSession::OnRead(const std::shared_ptr<TcpConnection>& conn, Buffer* pBuffer, Timestamp receivTime)
  2. {
  3. //LOG_INFO << "Recv a http request from " << conn->peerAddress().toIpPort();
  4.  
  5. string inbuf;
  6. //先把所有数据都取出来
  7. inbuf.append(pBuffer->peek(), pBuffer->readableBytes());
  8. //因为一个http包头的数据至少\r\n\r\n,所以大于4个字符
  9. //小于等于4个字符,说明数据未收完,退出,等待网络底层接着收取
  10. if (inbuf.length() <= )
  11. return;
  12.  
  13. //我们收到的GET请求数据包一般格式如下:
  14. /* GET /register.do?p={%22username%22:%20%2213917043329%22,%20%22nickname%22:%20%22balloon%22,%20%22password%22:%20%22123%22} HTTP/1.1\r\n Host: 120.55.94.78:12345\r\n Connection: keep-alive\r\n Upgrade-Insecure-Requests: 1\r\n User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36\r\n Accept-Encoding: gzip, deflate\r\n Accept-Language: zh-CN, zh; q=0.9, en; q=0.8\r\n \r\n */
  15. //检查是否以\r\n\r\n结束,如果不是说明包头不完整,退出
  16. string end = inbuf.substr(inbuf.length() - );
  17. if (end != "\r\n\r\n")
  18. return;
  19. //超过2048个字符,且不含\r\n\r\n,我们认为是非法请求
  20. else if (inbuf.length() >= MAX_URL_LENGTH)
  21. {
  22. conn->forceClose();
  23. return;
  24. }
  25.  
  26. //以\r\n分割每一行
  27. std::vector<string> lines;
  28. StringUtil::Split(inbuf, lines, "\r\n");
  29. if (lines.size() < || lines[].empty())
  30. {
  31. conn->forceClose();
  32. return;
  33. }
  34.  
  35. std::vector<string> chunk;
  36. StringUtil::Split(lines[], chunk, " ");
  37. //chunk中至少有三个字符串:GET+url+HTTP版本号
  38. if (chunk.size() < )
  39. {
  40. conn->forceClose();
  41. return;
  42. }
  43.  
  44. LOG_INFO << "url: " << chunk[] << " from " << conn->peerAddress().toIpPort();
  45. //inbuf = /register.do?p={%22username%22:%20%2213917043329%22,%20%22nickname%22:%20%22balloon%22,%20%22password%22:%20%22123%22}
  46. std::vector<string> part;
  47. //通过?分割成前后两端,前面是url,后面是参数
  48. StringUtil::Split(chunk[], part, "?");
  49. //chunk中至少有三个字符串:GET+url+HTTP版本号
  50. if (part.size() < )
  51. {
  52. conn->forceClose();
  53. return;
  54. }
  55.  
  56. string url = part[];
  57. string param = part[].substr();
  58.  
  59. if (!Process(conn, url, param))
  60. {
  61. LOG_ERROR << "handle http request error, from:" << conn->peerAddress().toIpPort() << ", request: " << pBuffer->retrieveAllAsString();
  62. }
  63.  
  64. //短连接,处理完关闭连接
  65. conn->forceClose();
  66. }

但这只能解决发送非法数据的情况,如果一个客户端连上来不给我们发任何数据,这段逻辑就无能为力了。如果不断有客户端这么做,会浪费我们大量的连接资源,所以我们还需要一个定时器去定时检测哪些http连接超过一定时间内没给我们发数据,找到后将连接断开。这又涉及到服务器定时器如何设计了,关于这部分请参考我写的其他文章。

限于作者经验水平有限,文中难免有错乱之处,欢迎拍砖。另外,关于上面的代码,可以去github上下载,地址是:

https://github.com/baloonwj/flamingo

全文完。

欢迎关注公众号『easyserverdev』。如果有任何技术或者职业方面的问题需要我提供帮助,可通过这个公众号与我取得联系,此公众号不仅分享高性能服务器开发经验和故事,同时也免费为广大技术朋友提供技术答疑和职业解惑,您有任何问题都可以在微信公众号直接留言,我会尽快回复您。 

从零实现一个http服务器的更多相关文章

  1. Muduo 多线程模型:一个 Sudoku 服务器演变

    陈硕 (giantchen AT gmail) blog.csdn.net/Solstice Muduo 全系列文章列表: http://blog.csdn.net/Solstice/category ...

  2. 适合新手:从零开发一个IM服务端(基于Netty,有完整源码)

    本文由“yuanrw”分享,博客:juejin.im/user/5cefab8451882510eb758606,收录时内容有改动和修订. 0.引言 站长提示:本文适合IM新手阅读,但最好有一定的网络 ...

  3. 如何编写一个 SendFile 服务器

    如何编写一个 SendFile 服务器 前言 之前讨论零拷贝的时候,我们知道,两台机器之间传输文件,最快的方式就是 send file,众所周知,在 Java 中,该技术对应的则是 FileChann ...

  4. 从零搭建一个IdentityServer——聊聊Asp.net core中的身份验证与授权

    OpenIDConnect是一个身份验证服务,而Oauth2.0是一个授权框架,在前面几篇文章里通过IdentityServer4实现了基于Oauth2.0的客户端证书(Client_Credenti ...

  5. 从零搭建一个IdentityServer——会话管理与登出

    在上一篇文章中我们介绍了单页应用是如何使用IdentityServer完成身份验证的,并且在讲到静默登录以及会话监听的时候都提到会话(Session)这一概念,会话指的是用户与系统之间交互过程,反过来 ...

  6. 从零搭建一个IdentityServer——资源与访问控制

    IdentityServer作为授权服务器它的最终目的是用于对资源进行管控,这里所说的资源有两种,其一是API资源,实际上也就是OIDC协议中客户端(RP)所需要访问的一系列受保护的资源(API),授 ...

  7. CSharpGL(34)以从零编写一个KleinBottle渲染器为例学习如何使用CSharpGL

    CSharpGL(34)以从零编写一个KleinBottle渲染器为例学习如何使用CSharpGL +BIT祝威+悄悄在此留下版了个权的信息说: 开始 本文用step by step的方式,讲述如何使 ...

  8. Linux下配置一个VNC服务器

    在Linux下配置一个VNC服务器,并设置2个用户,要求其中一个用户登录时不需要输入密码. 然后在客户端使用ssh+vncview的方式访问. 1确认vnc安装 2配置vncserver 3测试vnc ...

  9. 在 Linux 上配置一个 syslog 服务器

    syslog服务器可以用作一个网络中的日志监控中心,所有能够通过网络来发送日志的设施(包含了Linux或Windows服务器,路由器,交换机以及其他主机)都可以把日志发送给它. 通过设置一个syslo ...

随机推荐

  1. hihoCoder扩展欧几里得

    #include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h&g ...

  2. Mr.Jin系统发布报告——WIN7 WIN8双系统下的学习模式系统

    Mr.Jin系统发布报告           本文由Mr.Jin原创发表于博客园.CSDN论坛 http://www.cnblogs.com/CHPowerljp-IT/p/3463850.html ...

  3. 第三篇 .NET高级技术之深拷贝和浅拷贝

    深拷贝.浅拷贝 如果拷贝的时候共享被引用的对象就是浅拷贝,如果被引用的对象也拷贝一份出来就是深拷贝.(深拷贝就是说重新new一个对象,然后把之前的那个对象的属性值在重新赋值给这个用户) using S ...

  4. C#程序结构与基本语法

    C# 程序结构 Hello World 实例 一个 C# 程序主要包括以下部分: 命名空间声明(Namespace declaration) 一个 class Class 方法 Class 属性 一个 ...

  5. C语言归并排序(合并排序)算法及代码

    归并排序也称合并排序,其算法思想是将待排序序列分为两部分,依次对分得的两个部分再次使用归并排序,之后再对其进行合并.仅从算法思想上了解归并排序会觉得很抽象,接下来就以对序列A[0], A[l]…, A ...

  6. 跟我一起玩Win32开发(18):使用对话框的两个技巧

    相信大家知道对话框怎么用了,就是先用“资源编辑器”设计一个对话框,然后在代码中加载处理.今天,我向大家分享两个使用对话框的技巧,还是比较实用的.不用担心,先喝杯茶,很简单的,一点也不复杂,总之,看俺写 ...

  7. 跟我一起玩Win32开发(9):绘图(B)

    我们今天继续涂鸦,实践证明,涂鸦是人生一大乐趣. 首先,我们写一个程序骨架子,以便做实验. #include <Windows.h> LRESULT CALLBACK MainWinPro ...

  8. JS获取屏幕的大小

    <html><script>function a(){document.write("屏幕分辨率为:"+screen.width+"*" ...

  9. Codeforces Round #261 (Div. 2) E

    Description Pashmak's homework is a problem about graphs. Although he always tries to do his homewor ...

  10. Contextual Action bar(3) 两个示例

    一.通过activity启动Context Action Bar 1.主java public class ActivityActionModeFrgmt extends Fragment imple ...