## http 格式实验
http 请求包含三个部分,分别是:起始行、消息包头、请求正文
```c
Request Line<CRLF>
Header-Name: header-value<CRLF>
Header-Name: header-value<CRLF>
//一个或多个,均以<CRLF>结尾
<CRLF>
body//请求正文
```
![](https://www.writebug.com/myres/static/uploads/2021/12/31/c7f3edc7b29e0c2d710aeb1fb84bb835.writebug)
1、起始行以一个方法符号开头,以空格分开,后面跟着请求的 URI 和协议的版本,格式如下:
```c
Method Request-URL HTTP-Version CRLF
```
```
**请求方法 统一资源标识符 HTTP协议版本 回车换行符**
```
2、请求方法(所有方法全为大写)有多种,各个方法的解释如下:
- GET 请求获取 Request-URI 所标识的资源
- POST 在 Request-URI 所标识的资源后附加新的数据
- HEAD 请求获取由 Request-URI 所标识的资源的响应消息报头
- PUT 请求服务器存储一个资源,并用 Request-URI 作为其标识
- DELETE 请求服务器删除 Request-URI 所标识的资源
- TRACE 请求服务器回送收到的请求信息,主要用于测试或诊断
- CONNECT 保留将来使用
- OPTIONS 请求查询服务器的性能,或者查询与资源相关的选项和需求
应用举例: GET 方法:在浏览器的地址栏中输入网址的方式访问网页时,浏览器采用 GET 方法向服务器获取资源,eg:
```c
GET /form.html HTTP/1.1 (CRLF)
```
POST 方法要求被请求服务器接受附在请求后面的数据,常用于提交表单。eg:
```c
POST /reg.jsp HTTP/ (CRLF)
Accept:image/gif,image/x-xbit,... (CRLF)
...
HOST:www.guet.edu.cn (CRLF)
Content-Length:22 (CRLF)
Connection:Keep-Alive (CRLF)
Cache-Control:no-cache (CRLF)
(CRLF) //该CRLF表示消息报头已经结束,在此之前为消息报头
user=jeffrey&pwd=1234 //此行以下为提交的数据
```
## Tinyhttpd 中包含的主要函数
包含的函数
```c
void accept_request(int);
void bad_request(int);
void cat(int, FILE *);
void cannot_execute(int);
void error_die(const char *);
void execute_cgi(int, const char *, const char *, const char *);
int get_line(int, char *, int);
void headers(int, const char *);
void not_found(int);
void serve_file(int, const char *);
int startup(u_short *);
void unimplemented(int);
```
交互流程
![](https://www.writebug.com/myres/static/uploads/2021/12/31/b7a6e436e35d11a8be214bd05033d134.writebug)
### main 函数
sockaddr_In 结构体: 解决了 sockaddr 的缺陷,将 port 和 addr 分开存储。
```c
struct sockaddr_in {
short int sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
struct in_addr {
unsigned long s_addr;
}
unsigned char sin_zero[8];
}
```
#### 使用 socklen_t 记录 clientname 长度
socklen_t 的定义出现在,与 int 具有相同的长度。
windows 平台下: 头文件:
# include<ws2tcpip.h>
Linux 平台下,下面两个头文件都有定义
1)#include <sys/socket.h>
2)#include <unistd.h>
#### 使用 pthread 记录新线程的 id
#### 调用 startup 函数 开启端口
socket 函数进行调用
```c
httpd = socket(PF_INET, SOCK_STREAM, 0);
```
建立 socket 指定协议时,采用 PF,设置地址时,采用 AF。
##### socket 函数原型
socket()函数的原型如下,这个函数建立一个协议族为 domain、协议类型为 type、协议编号为 protocol 的套接字文件描述符。如果函数调用成功,会返回一个标识这个套接字的文件描述符,失败的时候返回-1。
```c
# include<sys/types.h>
# include<sys/socket.h>
int socket(int domain, int type, int protocol);
```
###### domain
函数 socket()的参数 domain 用于设置网络通信的域,函数 socket()根据这个参数选择通信协议的族。通信协议族在文件 sys/socket.h 中定义。
| 名称 | 含义 | 名称 | 含义 |
| ---------------- | ------------------ | ------------ | ------------------------- |
| PF_UNIX,PF_LOCAL | 本地通信 | PF_X25 | ITU-T X25 / ISO-8208 协议 |
| AF_INET,PF_INET | IPv4 Internet 协议 | PF_AX25 | Amateur radio AX.25 |
| PF_INET6 | IPv6 Internet 协议 | PF_ATMPVC | 原始 ATM PVC 访问 |
| PF_IPX | IPX-Novell 协议 | PF_APPLETALK | Appletalk |
| PF_NETLINK | 内核用户界面设备 | PF_PACKET | 底层包访问 |
此次使用的为 PF_INET,即 IPv4 协议。
###### type
函数 socket()的参数 type 用于设置套接字通信的类型,主要有 SOCKET_STREAM(流式套接字)、SOCK——DGRAM(数据包套接字)等。
| 名称 | 含义 |
| -------------- | -------------------------------------------------------------------------------------------------------------------------- |
| SOCK_STREAM | Tcp 连接,提供序列化的、可靠的、双向连接的字节流。支持带外数据传输 |
| SOCK_DGRAM | 支持 UDP 连接(无连接状态的消息) |
| SOCK_SEQPACKET | 序列化包,提供一个序列化的、可靠的、双向的基本连接的数据传输通道,数据长度定常。每次调用读系统调用时数据需要将全部数据读出 |
| SOCK_RAW | RAW 类型,提供原始网络协议访问 |
| SOCK_RDM | 提供可靠的数据报文,不过可能数据会有乱序 |
| SOCK_PACKET | 这是一个专用类型,不能在通用程序中使用 |
此次使用的为 SOCK_STREAM。
###### protocol
函数 socket()的第 3 个参数 protocol 用于制定某个协议的特定类型,即 type 类型中的某个类型。通常某协议中只有一种特定类型,这样 protocol 参数仅能设置为 0;但是有些协议有多种特定的类型,就需要设置这个参数来选择特定的类型。
- 类型为 SOCK_STREAM 的套接字表示一个双向的字节流,与管道类似。流式的套接字在进行数据收发之前必须已经连接,连接使用 connect()函数进行。一旦连接,可以使用 read()或者 write()函数进行数据的传输。流式通信方式保证数据不会丢失或者重复接收,当数据在一段时间内任然没有接受完毕,可以将这个连接人为已经死掉。
- SOCK_DGRAM 和 SOCK_RAW 这个两种套接字可以使用函数 sendto()来发送数据,使用 recvfrom()函数接受数据,recvfrom()接受来自制定 IP 地址的发送方的数据。
- SOCK_PACKET 是一种专用的数据包,它直接从设备驱动接受数据。
startup 函数中调用方法为建立一个流式套接字。
---
sockaddr_in 在使用前用 0 进行初始化。
并且进一步填充接口。
```c
memset(&name, 0, sizeof(name));
name.sin_family = AF_INET;
name.sin_port = htons(*port);
name.sin_addr.s_addr = htonl(INADDR_ANY);
```
---
##### bind 函数
利用 bind 函数对 socket 套接字进行命名。
```c
if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)
error_die("bind");
```
###### bind 函数介绍
```c
# include <sys/types.h>
# include <sys/socket.h>
int bind(int socket, const struct sockaddr* my_addr, socklen_t addrlen);
```
bind 将 my_addr 所指的 socket 地址分配给未命名的 sockfd 文件描述符,addrlen 参数指出该 socket 地址的长度。 调用成功返回 0, 失败返回-1,并设置 errno。
---
##### getsockname 和 getpeername 函数
getsockname 函数用于获取与某�