前言:
本篇文章需要你要两个前置知识
第一 你需要了解网络编程基础知识 和socket、bind、listen和accept函数
第二 你需要了解什么是http协议
参考
1.网络编程
2.http协议
1.构建连接
- 初始化
startup(u_short *port)
{
int httpd = 0;
struct sockaddr_in name;
httpd = socket(PF_INET, SOCK_STREAM, 0);
if (httpd == -1)
error_die("socket");
memset(&name, 0, sizeof(name));
name.sin_family = AF_INET;
name.sin_port = htons(*port);
name.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)
error_die("bind");
if (*port == 0) /* if dynamically allocating a port */
{
int namelen = sizeof(name);
if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)
error_die("getsockname");
*port = ntohs(name.sin_port);
)
解析 连接三部曲
1.socket初始化
2.地址族初始化
3.bind和listen的初始化
当你学到了高级语言的时候,一般会封装成了一个类.
补充
getsockname
:这是获取一个socket细节的一个函数,假如绑定失败,就会返回-1
- 连接器
client_sock = accept(server_sock,
(struct sockaddr *)&client_name,
&client_name_len);
if (client_sock == -1)
error_die("accept");
accept函数作为一个连接器,可以连接客户端,成功返回连接的连接短的socket_fd;
2.HTTP
服务端主要是处理请求的
- 处理请求行的方法
{
char buf[1024];
int numchars;
char method[255];
char url[255];
char path[512];
size_t i, j;
struct stat st;
int cgi = 0; /* becomes true if server decides this is a CGI
* program */
char *query_string = NULL;
numchars = get_line(client, buf, sizeof(buf));
i = 0; j = 0;
while (!ISspace(buf[j]) && (i < sizeof(method) - 1))
{
method[i] = buf[j];
i++; j++;
}
method[i] = '\0';
if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
{
unimplemented(client);
return;
}
if (strcasecmp(method, "POST") == 0)
cgi = 1;
首先 提起方法 是get还是post,这个服务器简陋,所以只实现了get和post方法
- 处理请求行的url
i = 0;
while (ISspace(buf[j]) && (j < sizeof(buf)))
j++;
while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf)))
{
url[i] = buf[j];
i++; j++;
}
url[i] = '\0';
if (strcasecmp(method, "GET") == 0)
{
query_string = url;
while ((*query_string != '?') && (*query_string != '\0'))
query_string++;
if (*query_string == '?')
{
cgi = 1;
*query_string = '\0';
query_string++;
}
}
这里讲url分割成两个部分 一个是url的路径,一个是url的查询符串
比如说一个url
http://example.com/path?param1=value1¶m2=value2
经过上述代码处理后,
url 就变为 "http://example.com/path"。
query_string 指向 "param1=value1¶m2=value2"。
**补充知识 **
C语言当中数组 的结束是
\0
,url 强行在的位置结束了
query_string++
就继续继承url的查询部分
- 处理url
sprintf(path, "htdocs%s", url);
if (path[strlen(path) - 1] == '/')
strcat(path, "index.html");
将你的输入的url定位到网址首页
(通俗理解)
响应部分
错误处理
void not_found(int client)
{
char buf[1024];
sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, SERVER_STRING);
send(client, buf, strlen(buf), 0);
sprintf(buf, "Content-Type: text/html\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "<BODY><P>The server could not fulfill\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "your request because the resource specified\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "is unavailable or nonexistent.\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "</BODY></HTML>\r\n");
send(client, buf, strlen(buf), 0);
}
这个部分包含响应全部部分
- 响应行
sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n");
send(client, buf, strlen(buf), 0);
- 响应头
sprintf(buf, SERVER_STRING);
send(client, buf, strlen(buf), 0);
sprintf(buf, "Content-Type: text/html\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "\r\n");
send(client, buf, strlen(buf), 0);
-
- SERVER_STRING 一般包含服务器的相关信息,例如服务器的名称和版本,这属于响应头的一部分。
-
- Content-Type: text/html\r\n 明确了响应体的内容类型是 HTML。
-
- 空行 \r\n 标志着响应头的结束。
- 响应体
sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "<BODY><P>The server could not fulfill\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "your request because the resource specified\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "is unavailable or nonexistent.\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "</BODY></HTML>\r\n");
send(client, buf, strlen(buf), 0);
展现了些代码片段组合起来形成了一个 HTML 格式的响应体,用于向客户端展示 404 错误的相关信息。
静态页面的响应
void serve_file(int client, const char *filename)
{
FILE *resource = NULL;
int numchars = 1;
char buf[1024];
buf[0] = 'A'; buf[1] = '\0';
while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */
numchars = get_line(client, buf, sizeof(buf));
resource = fopen(filename, "r");
if (resource == NULL)
not_found(client);
else
{
headers(client, filename);
cat(client, resource);
}
fclose(resource);
}
这个首先是处理请求,丢失了所以的请求,简化了逻辑,(只访问页面,(知道路径就好))
- 响应头和响应行
void headers(int client, const char *filename)
{
char buf[1024];
(void)filename; /* could use filename to determine file type */
strcpy(buf, "HTTP/1.0 200 OK\r\n");
send(client, buf, strlen(buf), 0);
strcpy(buf, SERVER_STRING);
send(client, buf, strlen(buf), 0);
sprintf(buf, "Content-Type: text/html\r\n");
send(client, buf, strlen(buf), 0);
strcpy(buf, "\r\n");
send(client, buf, strlen(buf), 0);
}
和上面错误处理一样 不过多论述了
- 响应体
void cat(int client, FILE *resource)
{
char buf[1024];
fgets(buf, sizeof(buf), resource);
while (!feof(resource))
{
send(client, buf, strlen(buf), 0);
fgets(buf, sizeof(buf), resource);
}
}
发送一个html给客户端
补充知识
浏览器具有内置的渲染引擎,能够识别 HTML 标签、CSS 样式和 JavaScript 脚本等,并根据相关的标准和规范将其转换为用户可见的网页内容。只要发送的 HTML 文件格式正确且完整,浏览器就能对其进行处理并展示出相应的页面结构、文本、图像、链接等元素。
动态页面的响应
void execute_cgi(int client, const char *path,
const char *method, const char *query_string)
{
char buf[1024];
int cgi_output[2];
int cgi_input[2];
pid_t pid;
int status;
int i;
char c;
int numchars = 1;
int content_length = -1;
buf[0] = 'A'; buf[1] = '\0';
if (strcasecmp(method, "GET") == 0)
while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */
numchars = get_line(client, buf, sizeof(buf));
else /* POST */
{
numchars = get_line(client, buf, sizeof(buf));
while ((numchars > 0) && strcmp("\n", buf))
{
buf[15] = '\0';
if (strcasecmp(buf, "Content-Length:") == 0)
content_length = atoi(&(buf[16]));
numchars = get_line(client, buf, sizeof(buf));
}
if (content_length == -1) {
bad_request(client);
return;
对于get方法 也是丢失了请求,
对于POST 方法
if (strcasecmp(buf, “Content-Length:”) == 0):
比较截取后的 buf 是否等于 “Content-Length:”,如果相等,说明当前行是 Content-Length 请求头字段。
content_length = atoi(&(buf[16]));:
如果是 Content-Length 字段,使用 atoi 函数将该字段后面的字符串转换为整数,赋值给 content_length 变量,这个变量表示 POST 请求体的长度。
还有一部分是cgi的响应,无需了解。(它进程开销优点大)
如果你想了解 建议阅读网络知识,先了解进程和pipe函数