[linux] Linux网络编程之HTTP协议详解
Posted 哦哦呵呵
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[linux] Linux网络编程之HTTP协议详解相关的知识,希望对你有一定的参考价值。
目录
1. 自定制协议
在上一篇socket编程中谈到了协议,程序员系的网络程序,全部都部署在应用层。
协议是一种“约定”,socket api的接口,在读写数据的时候,都是按照“字符串”的方式来进行发送和接受。那如果我们要传输的是结构化数据,怎么处理?这时候就需要我们用到自定义协议,可以自己写一套规则,服务器通过固定的方式解析数据,达到自定制协议的目的。
程序示例
// 以下是服务端部分代码
typedef struct request
{
int x;
int y;
char op;
}request_t;
typedef struct response
{
int code;
int result;
}response_t;
void CalResult(int sock)
{
// 短链接 完成对应的计算
request_t rq;
response_t rsp{4, 0};
ssize_t s = recv(sock, &rq, sizeof(rq), 0);
if (s > 0)
{
rsp.code = 0;
switch(rq.op)
{
case '+':
rsp.result = rq.x + rq.y;
break;
case '-':
rsp.result = rq.x - rq.y;
break;
case '*':
rsp.result = rq.x * rq.y;
break;
case '/':
if (rq.y == 0)
{
rsp.code = 1;
}
else
{
rsp.result = rq.x / rq.y;
}
break;
case '%':
if (rq.y == 0)
{
rsp.code = 2;
}
else
{
rsp.result = rq.x % rq.y;
}
break;
default:
rsp.code = 3;
break;
}
}
std::cout << "cal success!" << std::endl;
send(sock, &rsp, sizeof(rsp), 0);
close(sock);
}
// 客户端部分代码
void StartClient()
{
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(port);
server.sin_addr.s_addr = inet_addr(ip.c_str()) ;
if(connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0)
{
std::cerr << "connect err.." << std::endl;
exit(2);
}
request_t rq;
response_t rsp;
std::cout << "data1# ";
std::cin >> rq.x;
std::cout << "data2# ";
std::cin >> rq.y;
std::cout << "op# ";
std::cin >> rq.op;
send(sock, &rq, sizeof(rq), 0);
recv(sock, &rsp, sizeof(rsp), 0);
std::cout << "cod: " << rsp.code << std::endl;
std::cout << "result: " << rsp.result << std::endl;
}
2. HTTP协议
2.1 概述
HTTP协议是建立在TCP/IP协议之上的,是TCP/IP的上层协议,HTTP的数据通过TCP/IP层进行转发。
HTTP协议是无连接、无状态、工作在应用层的协议。
- 无连接
http协议本身是不建立连接的,http直接向对方发送http request即可,但是http在传输层使用的是tcp协议,tcp协议在传输数据的时候是需要建立连接,才可以通信的。- 无状态
http协议本身是对请求和响应不做保存,双方的状态是服务端实现的会话机制进行保存的。
2.2 URL统一资源定位符
- 服务器地址: 使用绝对 URI 必须指定待访问的服务器地址。地址可以是类似hackr.jp 这种 DNS 可解析的名称,或是 192.168.1.1 这类 IPv4 地址
名,还可以是 [0:0:0:0:0:0:0:1] 这样用方括号括起来的 IPv6 地址名。- 服务器端口号: 指定服务器连接的网络端口号。此项也是可选项,若用户省略则自动使用默认端口号。
- 带层次的文件路径(http://域名/路径): 代表的是向服务器后台请求资源的路径,路径中第一个’/'代表web根目录,服务端可以指定任意一个路径作为http服务端的根目录的起始路径
- 查询字符串(key=value): 指定的是客户端向服务器提交的数据,多组时间之间用&连接。
URL中的特殊字符的转义
- urlencode:
在URL有一些特殊字符需要转义,对具有通俗意义的字符进行转码,采用16进制进行显示。例如下方字符,需要使用’%’ + urlencode,其中%是告诉服务器后面的内容是经过转义的。
- urldecode: 将特殊字符编码转化回来。
2.3 HTTP协议格式
2.3.1 HTTP请求报文格式
上述就是一个请求报文的格式:
每一行都是用 \\r\\n
作为改行的结束标记
- 首行: 称为请求行,用空格分为三部分
请求方法:GET… 其它方法下文介绍
url: …想要请求资源所在的路径
http版本: HTTP/1.1- 请求报头:
每一行都是一个键值对key: value\\r\\n
其中Content-Length:
表示请求正文的长度,从请求报头后的多少个字节内容是请求正文。- 空行
\\r\\n
用于分离请求报头与有效载荷- 请求正文(有效载荷): GET方法不需要携带正文,POST必须携带正文
请求方法
- GET: 向服务端请求某些资源,也可以给服务端提交少量的数据(url),url的长度是有限制的,提交的请求会在地址栏中显示出来。
- POST: 向服务器提交某些数据,提交的数据在请求报文中存储,提交时地址栏看不到提交内容,相对GET方法较私密。
- HEAD: 获取响应报文头部信息,并没有获取响应正文,是为了测试资源是否有效。
- DELETE:删除文件
- PUT: 传输文件
DELETE和PUT两种方法,http都没有校验,一般情况后台的服务端不支持PUT和DELETE方法- OPTIONS:询问服务端支持的方法
2.3.1 HTTP响应报文格式
每一行都是用 \\r\\n
作为改行的结束标记
- 首行: 响应行,三部分
协议版本: http/1.1
状态码: 200
状态码描述- 响应报头
由很多key/value
构成- 空行
- 响应正文: 是发送请求后,服务器向客户端发送的数据
2.4 状态码及解释
- 2XX 成功, 2XX 的响应结果表明请求被正常处理了。
200 OK表示从客户端发来的请求在服务器端被正常处理了。
204 No Content
206 Partial Content- 3XX 重定向
301 Moved Permanently 永久性重定向。
302 Found 临时性重定向
303 See Other
307 Temporary Redirect 临时重定向。- 4XX 客户端错误
400 Bad Request
401 Unauthorized 认证失败
403 Forbidden
404 Not Found- 5XX 服务器错误
500 Internal Server Error 该状态码表明服务器端在执⾏请求时发⽣了错误。也有可能是 Web应⽤存在的 bug 或某些临时的故障。
503 Service Unavailabl
2.5 HTTP的响应首部字段
这里只列举最常见的几种
- Content-Type: 正文类型 text、html、css、js等
- Content-Length: 正文长度
- Host: 客户端告知服务器,所请求的资源是在哪个主机的哪个端口上
- User-Agent: 声明用户的操作系统和浏览器版本信息
- Referer: 当前页面是从哪个页面跳转过来的
- Location: 搭配3xx状态码使用,告诉客户端接下来去哪访问
- Conection: keep-alive 保持长连接
- Cookie:下方详解
Cookie
1.概念
- 用于在客户端浏览器存储少量的信息,通常用于实现会话的功能,本质是浏览器中的一个文件
- cookie是服务器返回给浏览器,由浏览器进行保存cookie
- 在访问服务器的其它页面时,由浏览器自动在请求体当中加上cookie
2.作用
服务端通过cookie当中的value值,可以得到服务端生成session,通过会话id,可以在服务端查询出来是哪一个用户的session。浏览器通过请求中的cookie信息提交到服务器,服务端就可以通过cookie保存的会话信息,进行会话校验。
3. HTTP和HTTPS
3.1 HTTP
我们已了解到 HTTP 具有相当优秀和方便的一面,然而HTTP 并非只有好的一面,事物皆具两面性,它也是有不足之处的。
HTTP 主要有这些不足,例举如下。
- 通信使用明文(不加密),内容可能会被窃听
- 不验证通信方的身份,因此有可能遭遇伪装
- 无法证明报文的完整性,所以有可能已遭篡改
这些问题不仅在 HTTP 上出现,其他未加密的协议中也会存在这类问题。除此之外,HTTP 本身还有很多缺点。而且,还有像某些特定的 Web服务器和特定的 Web 浏览器在实际应用中存在的不足(也可以说成是脆弱性或安全漏洞),另外,用 Java 和 php 等编程语言开发的
Web 应用也可能存在安全漏洞。
3.2 HTTPS
HTTP 协议中没有加密机制,但可以通过和 SSL(Secure Socket Layer,安全套接层)或TLS(Transport Layer Security,安全层传输协议)的组合使用,加密 HTTP 的通信内容。
用 SSL建立安全通信线路之后,就可以在这条线路上进行 HTTP通信了。与 SSL组合使用的 HTTP 被称为 HTTPS(HTTPSecure,超文本传输安全协议)或 HTTP over SSL。
HTTP+ 加密 + 认证 + 完整性保护 = HTTPS
HTTPS中的认证方式
SSL采用一种叫做公开密钥加密(Public-key cryptography)的加密处理方式。近代的加密方法中加密算法是公开的,而密钥却是保密的。通过这种方式得以保持加密方法的安全性。
加密和解密都会用到密钥。没有密钥就无法对密码解密,反过来说,任何人只要持有密钥就能解密了。如果密钥被攻击者获得,那加密也就失去了意义。
加密和解密同用一个密钥的方式称为共享密钥加密(Common keycrypto system),也被叫做对称密钥加密。
以共享密钥方式加密时必须将密钥也发给对方。可究竟怎样才能安全地转交?在互联网上转发密钥时,如果通信被监听那么密钥就可会落入攻击者之手,同时也就失去了加密的意义。另外还得设法安全地保管接收到的密钥。
使用两把密钥的公开密钥加密
公开密钥加密方式很好地解决了共享密钥加密的困难。公开密钥加密使用一对非对称的密钥。一把叫做私有密钥(private key),另一把叫做公开密钥(public key)。顾名思义,私有密钥不能让其他任何人知道,而公开密钥则可以随意发布,任何人都可以获得。
使用公开密钥加密方式,发送密文的一方使用对方的公开密钥进行加密处理,对方收到被加密的信息后,再使用自己的私有密钥进行解密。利用这种方式,不需要发送用来解密的私有密钥,也不必担心密钥被攻击者窃听而盗走。
4. HTTP代码示例
http 服务端
#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
#define READFILESIZE 4096
class HttpServer
{
private:
int port;
int lsock;
public:
HttpServer(int _port)
: port(_port)
, lsock(-1)
{}
void InitServer()
{
signal(SIGCHLD, SIG_IGN);
lsock = socket(AF_INET, SOCK_STREAM, 0);
if (lsock < 0)
{
std::cerr << "socket err..." << std::endl;
exit(2);
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = INADDR_ANY;
if (bind(lsock, (struct sockaddr*)&local, sizeof(local)) < 0)
{
std::cerr << "bind err..." << std::endl;
exit(3);
}
if (listen(lsock, 5) < 0)
{
std::cerr << "listen err..." << std::endl;
exit(4);
}
// 设置端口复用,断开连接可以立即使用该端口,跳过TIME_WAIT
int opt = 1;
setsockopt(lsock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
}
// 读取所请求的资源,将资源内容添加到响应报文中
std::string ReadHTMLfile(std::string file)
{
std::string path = "./WEB";
path += file;
int fd = open(path.c_str(), O_RDONLY);
if (fd < 0)
{
std::cerr << "open err..." << std::endl;
exit(6);
}
char buf[READFILESIZE];
std::string res;
size_t rlen;
while (true)
{
rlen = read(fd, buf, sizeof(buf) - 1);
res += buf;
// 清空buf缓冲区 很重要
memset(buf, '\\0', sizeof(buf));
if (rlen < READFILESIZE - 1)
{
break;
}
}
return res;
}
// 提取请求中的资源的后缀
std::string ExtractSuffix(std::string filepath)
{
std::string suffix;
size_t pos = filepath.find_last_of(".");
if (pos != std::string::npos)
{
suffix = filepath.substr(pos + 1, filepath.size() - (filepath.size() - pos));
}
else
{
suffix = "/";
}
return suffix;
}
std::string ResponseHead(std::string suffix)
{
// 添加响应报头
std::string response;
response += "HTTP/1.1 200 OK\\r\\n";
if (suffix == "/" || suffix == "html")
{
response += "Content-type: text/html\\r\\n";
}
else if (suffix == "css")
{
response += "Content-type: text/css\\r\\n";
}
else if (suffix == "js")
{
response += "Content-type: text/javascript\\r\\n";
}
// 图片不能正常的读取
else if (suffix == "jpg")
{
response += "Accept-Ranges: bytes\\r\\n";
response += "Content-Type: image/jpeg\\r\\n";
}
else if (suffix == "png")
{
response += "Accept-Ranges: bytes\\r\\n";
response += "Content-Type: image/png\\r\\n";
}
return response;
}
// 组装响应报文
std::string ResponseContent(std::string filepath)
{
// 查找文件 条件响应信息
std::string responseContent;
responseContent.clear();
// 从文件中读取数据,添加至response报头
if (filepath == "/")
{
// 读取login.js 末尾会出现问题
responseContent += ReadHTMLfile("/login.html");
}
else
{
responseContent += ReadHTMLfile(filepath);
}
// 查找后缀
std::string suffix = ExtractSuffix(filepath);
std::string response;
response += ResponseHead(suffix);
//response += "Content-Length: " + std::to_string(responseContent.size());
response += "\\r\\n";
response += responseContent;
return response;
}
// 资源提取
std::string ExtractPath(char* request)
{
// 提取报头信息中请求文件路径
int posl = strcspn(request, " ");
int posr = strcspn(request + posl + 1, " ");
char buf[64];
memset(buf, '\\0', sizeof(buf));
strncpy(buf, request + posl + 1, posr);
std::string filepath = buf;
return filepath;
}
void EchoHttp(int sock)
{
char request[2048];
size_t s = recv(sock, request, sizeof(request), 0);
if (s > 0)
{
request[s] = 0;
std::cout << request << std::endl;
std::string filepath = ExtractPath(request);
// 向客户端相应
std::string response = ResponseContent(filepath);
send(sock, response.c_str(), response.size(), 0);
}
close(sock);
}
void StartServer()
{
struct sockaddr_in peer;
while (true)
{
socklen_t len = sizeof(peer);
int sock = accept(lsock, (struct sockaddr*)&peer, &len);
if (sock < 0)
{
std::cerr << "accept err..." << std::endl;
continue;
}
std::cout << "get a new connect ... done" << std::endl;
if (fork() == 0)
{
// child
close(lsock);
EchoHttp(sock);
exit(0);
}
close(sock);
}
}
~HttpServer()
{
if (lsock != -1)
{
close(lsock);
}
}
};
以上是关于[linux] Linux网络编程之HTTP协议详解的主要内容,如果未能解决你的问题,请参考以下文章