一、浏览器与服务器通信过程浏览器与 web 服务器在应用层通信使用的是 HTTP 协议超文本传输协议而HTTP协议在传输层使用的是 TCP 协议。那么浏览器需要和 web 服务器三次握手建立连接后才可以发送 HTTP 请求报文服务器收到请求报文后向浏览器回复 HTTP 应答报文。浏览器向服务器发起连接前需要得到服务器的 IP 及端口。用户在浏览器中通常只输入网址网站域名 ,浏览器会通过DNS 服务查询获取到服务器的 IP 地址。 对于端口来讲使用 HTTP 协议的程序一般默认使用 80 端口。浏览器服务器建立连接后如果两次以上的请求复用同一个 TCP 连接则称之为长连接。如果浏览器发送一次请求报文服务器回复一次应答就断开连接下次交互再重新进行三次握手建立连接那么就被称作短连接。使用长连接显然是更好一些可以减少网络中的同步报文也使得服务器的响应速度变快。http属于应用层,它在传输层使用的是tcp协议;传输层协议:tcp 协议和udp协议。常见的 web 服务器有◼ Apache 简单、速度快、性能稳定并可做代理服务器使用◼ IISInternet Information Server安全性、强大、灵活◼ Nginx小巧而高效可以做高效的负载均衡反向代理◼ Tomcat技术先进、性能稳定、免费浏览器与服务器通信过程(1)浏览器从URL中解析出服务器的主机名;(2)浏览器通过DNS协议将服务器的主机名转换成服务器的IP地址;(3)浏览器将端口号(如果有的话)从URL中解析出来.(4)浏览器建立一条与Web服务器的TCP连接;(5)浏览器向服务器发送一条HTTP请求报文;(6)服务器向浏览器回送一条HTTP响应报文;(7)关闭连接,浏览器显示文档;DNS协议域名系统英文DomainNameSystem缩写DNS是互联网的一项服务。它作为将域名和IP地址相互映射的一个分布式数据库能够使人更方便地访问互联网。DNS使用UDP端口53。浏览器要将URL解析为IP地址,解析域名就要用到DNS协议,首先主机会查询DNS的缓存,如果没有就给本地DNS发送查询请求。DNS查询分为两种方式一种是递归查询一种是迭代查询。如果是迭代查询本地的DNS服务器向根域名服务器发送查询请求,根域名服务器告知该域名的一级域名服务器,然后本地服务器给该一级域名 服务器发送查询请求,然后依次类推直到查询到该域名的IP地址。而递归查询请求方只发一次请求等待最终结果中间查询全由接收方完成。DNS服务器是基于UDP的,因此会用到UDP协议。DNS 查询的迭代过程是从根域名服务器 → 顶级域名服务器 → 二级域名服务器 → 权威域名服务器也就是从右往左从顶级域名往前逐级查询的。URLURL:统一资源定位符,URL是资源标识符最常见的形式URL描述了一台特定服务器上某资源的特定位置.它们可以明确说明如何从一个精确,固定的位置获取资源。1.客户端发起请求浏览器解析这个 URL向www.joes-hardware.com服务器发送 HTTP 请求要求获取/specials/saw-blade.gif这个图片文件。2. 服务器定位资源服务器收到请求后在自己的存储中找到对应的saw-blade.gif文件。3.服务器返回响应服务器把文件数据打包成 HTTP 响应发给客户端响应头里会带上关键信息Content-type: image/gif告诉客户端这是一个 GIF 格式的图片Content-length: 8572告诉客户端这个文件的大小是 8572 字节4.客户端解析并展示浏览器收到数据后根据Content-type知道这是图片直接把它渲染到页面上。HTTP 协议格式HTTP 的请求报头结构HTTP 请求报文段示例/index.html指定资源文件的名称,这里指的是服务器根目录(站点的根目录,而不是服务器的文件系统根目录 /)中的索引文件。注意:Keep-Alive首部只是请求将连接保持在活跃状态。发出keep-alive请求之后,客户端和服务器端并不一定会统一进行keep-alive会话。它们可以在任意时刻关闭空闲的keep-alive连接,并可随意限制keep-alive连接所处理事务的数量。HTTP 的请求方法HTTP 的应答报头结构HTTP 的应答报头结构HTTP 的应答状态二、搭建WEB服务器首先先写出短连接的服务器过程在一步一步修改。我们通过封装CreatSocket函数用于创建sockfd套接字在通过一个死循环去处理不同客户端的消息我们先实现出没有多线程和多进程的处理逻辑。#include stdio.h #include stdlib.h #include string.h #include arpa/inet.h //转换IP地址 #include netinet/in.h//转换字节序 #include sys/types.h #include sys/socket.h #include unistd.h //close函数 #include assert.h #include stdbool.h //创建socket绑定地址监听端口 int CreateSocket() { int sockfd socket(AF_INET, SOCK_STREAM, 0); assert(sockfd ! -1); struct sockaddr_in server_addr; memset(server_addr, 0, sizeof(server_addr)); server_addr.sin_family AF_INET; server_addr.sin_port htons(80); server_addr.sin_addr.s_addr inet_addr(192.168.199.128); int ret bind(sockfd, (struct sockaddr*)server_addr, sizeof(server_addr)); assert(ret ! -1); ret listen(sockfd, 5); assert(ret ! -1); return sockfd; } //先接收报头并打印出来 void DealHttpRequest(int c) { char requestHead[1024]; ssize_t n recv(c, requestHead, sizeof(requestHead)-1, 0); if(n 0){ perror(recv); return; } requestHead[n] \0; printf(Request:\n%s\n, requestHead); } //接收客户端连接处理请求 void AcceptClient(int sockfd) { struct sockaddr_in client_addr; socklen_t client_len sizeof(client_addr); int c accept(sockfd, (struct sockaddr*)client_addr, client_len); if(c -1){ perror(accept); return; } printf(Accept client %s:%d\n, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); //针对客户端的处理 DealHttpRequest(c); close(c); } int main() { int sockfd CreateSocket(); //处理多个客户端连接 while(true){ AcceptClient(sockfd); } close(sockfd); return 0; }上述代码就是先接受客户端发送的http请求报头先进行打印之后在去处理相应逻辑但是在这里我们需要注意80端口不能被普通用户使用一般情况下1024一下的端口号都不能被普通用户使用。这时候我们需要切换到root用户或者将80端口修改为HTTP 协议的 “通用替代端口”-8080端口即可正常启动。这里我演示的是切换root用户。我通过linux系统下的Firefox浏览器去访问该网址的index.html但是我的路径下是没有这个文件的所以他是会连续重传好几次的但是这时候我们已经可以确定是能够接收到请求报头的接下来就是对报头内容进行解析并返回相应的应答报头。多线程版本多线程版简易 Web 服务器采用一连接一线程的并发模型。服务器首先创建套接字、绑定本机 IP 与端口、完成监听初始化。程序通过死循环持续调用accept()阻塞等待客户端连接。每当有新客户端连接成功后不直接阻塞处理业务而是创建新线程单独处理本次 HTTP 请求主线程立刻回归监听状态实现支持多客户端同时访问的并发能力。子线程负责具体的 HTTP 请求解析与资源响应工作首先读取浏览器发来的 HTTP 请求报头通过字符串解析出用户请求的资源路径如果用户访问根路径/则默认访问index.html主页。通过stat()函数判断目标文件是否存在文件存在读取文件大小拼接并发送200 OK响应报头再读取本地文件内容发送给浏览器完成正常网页访问。文件不存在跳转读取本地404.html页面发送404 Not Found响应报头与错误页面内容实现标准 404 错误提示。所有请求处理完毕后线程统一释放堆内存、关闭客户端文件描述符避免内存泄漏与文件资源泄露子线程任务执行完毕自动退出。整体基于 TCP 流式套接字手动实现简易 HTTP 应答协议利用多线程解决单进程服务器无法并发访问的问题完成了基础 Web 服务器的资源访问与错误处理功能。//多线程版本 #include stdio.h #include stdlib.h #include string.h #include arpa/inet.h //转换IP地址 #include netinet/in.h//转换字节序 #include sys/types.h #include sys/socket.h #include unistd.h //close函数 #include assert.h #include stdbool.h #include sys/stat.h #include fcntl.h #include pthread.h //创建socket绑定地址监听端口 int CreateSocket() { int sockfd socket(AF_INET, SOCK_STREAM, 0); assert(sockfd ! -1); struct sockaddr_in server_addr; memset(server_addr, 0, sizeof(server_addr)); server_addr.sin_family AF_INET; server_addr.sin_port htons(80); server_addr.sin_addr.s_addr inet_addr(192.168.199.128); int ret bind(sockfd, (struct sockaddr*)server_addr, sizeof(server_addr)); assert(ret ! -1); ret listen(sockfd, 5); assert(ret ! -1); return sockfd; } //发送应答报头 void SendResponseHead(int c, int contentLength, bool flag) { char responseHead[256] {0}; if(flag){ strcat(responseHead,HTTP/1.1 200 OK\r\n); }else{ strcat(responseHead,HTTP/1.1 404 Not Found\r\n); } strcat(responseHead,Server:MYWEB/1.1\r\n); sprintf(responseHead strlen(responseHead) ,Content-Length:%d\r\n,contentLength); strcat(responseHead,Content-Type:text/html;charsetutf-8\r\n); strcat(responseHead,\r\n); send(c,responseHead,strlen(responseHead),0); } //发送文件内容 void SendResponseBody(int c, const char *filePath) { int fd open(filePath, O_RDONLY); if(fd -1){ perror(open); return; } char buff[1024]; while(true){ ssize_t n read(fd, buff, sizeof(buff)); if(n 0){ break; } send(c, buff, n, 0); } close(fd); } //先接收报头并打印出来 void* DealHttpRequest(void *arg) { int c *(int *)arg; char requestHead[1024]; ssize_t n recv(c, requestHead, sizeof(requestHead)-1, 0); if(n 0){ perror(recv); free(arg); close(c); return NULL; } requestHead[n] \0; //printf(Request:\n%s\n, requestHead); char *requestWay strtok(requestHead, ); char *filePath strtok(NULL, ); if(strlen(filePath) 1 filePath[0] /){ filePath /index.html; } char path[256] .; strcat(path, filePath); struct stat st; //利用stat结构体成员st_size获取文件大小 //undo,判断文件是否存在不存在则返回404 bool flag true; int ret stat(path, st); //assert(ret ! -1); if(ret -1){ flag false; memset(path, 0, sizeof(path)); strcpy(path, ./404.html); //先修改路径为404.html看看是否存在 struct stat st404; if(stat(path, st404) -1){ // 连404.html都找不到只能返回最基础的404响应 SendResponseHead(c, 0, flag); free(arg); close(c); return NULL; } SendResponseHead(c, st404.st_size, flag); //发送404头部 SendResponseBody(c, path); //发送404页面内容 free(arg); close(c); return NULL; } SendResponseHead(c, st.st_size, flag); //发送头部 SendResponseBody(c, path); //发送文件内容 free(arg); close(c); return NULL; } //接收客户端连接处理请求 void AcceptClient(int sockfd) { while(true){ struct sockaddr_in client_addr; socklen_t client_len sizeof(client_addr); int c accept(sockfd, (struct sockaddr*)client_addr, client_len); if(c -1){ perror(accept); return; } printf(Accept client %s:%d\n, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); int *pc (int *)malloc(sizeof(int)); *pc c; //针对客户端的处理 //多线程处理 pthread_t id; pthread_create(id,NULL,DealHttpRequest,(void *)pc); pthread_detach(id); // 分离线程避免资源泄漏 } } int main() { int sockfd CreateSocket(); //处理多个客户端连接 while(true){ AcceptClient(sockfd); } close(sockfd); return 0; }HTTPS版本HTTPS版本我们需要先生成证书但是这个证书只是适用于测试使用并不能直接进行商用。1.通过这个命令生成证书 openssl req -x509 -newkey rsa:2048 -nodes -keyout server.key -out server.crt -days 365 2.填写相关信息 Country Name (2 letter code) [AU]:CN //中国的国家代码 State or Province Name (full name) [Some-State]: //省份 Locality Name (eg, city) []: //城市 Organization Name (eg, company) [Internet Widgits Pty Ltd]: //组织随便填 Organizational Unit Name (eg, section) []: //部门随便填 Common Name (e.g. server FQDN or YOUR name) []: //最重要填你的服务器 IP否则浏览器会报证书域名不匹配 Email Address []: //测试证书可以不填 3.填写完成后会得到两个文件 server.crt //证书 server.key //私钥 将两个文件放在web_ser.c下的路径 4.每个客户端连接都做 SSL 握手 5.用 SSL_read / SSL_write 代替 recv /send HTTPS 不改变 HTTP 协议本身只负责传输加密。//https版本 #include stdio.h #include stdlib.h #include string.h #include arpa/inet.h #include netinet/in.h #include sys/types.h #include sys/socket.h #include unistd.h #include assert.h #include stdbool.h #include sys/stat.h #include fcntl.h #include pthread.h // OpenSSL 头文件 #include openssl/ssl.h #include openssl/err.h #define PORT 443 #define IP 192.168.199.128 SSL_CTX *ctx NULL; // 全局 SSL 上下文 // 1. 初始化 OpenSSL 加载证书私钥 bool InitSSL() { // OpenSSL 初始化 SSL_library_init(); SSL_load_error_strings(); OpenSSL_add_all_algorithms(); // 创建 TLS 服务器上下文 ctx SSL_CTX_new(TLS_server_method()); if (!ctx) { ERR_print_errors_fp(stderr); return false; } // 加载证书 if (SSL_CTX_use_certificate_file(ctx, server.crt, SSL_FILETYPE_PEM) 0) { ERR_print_errors_fp(stderr); return false; } // 加载私钥 if (SSL_CTX_use_PrivateKey_file(ctx, server.key, SSL_FILETYPE_PEM) 0) { ERR_print_errors_fp(stderr); return false; } // 检查私钥是否匹配证书 if (!SSL_CTX_check_private_key(ctx)) { fprintf(stderr, Private key does not match certificate\n); return false; } return true; } // 2. 创建 socket、绑定、监听端口 443 int CreateSocket() { int sockfd socket(AF_INET, SOCK_STREAM, 0); assert(sockfd ! -1); int opt 1; setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, opt, sizeof(opt)); struct sockaddr_in server_addr; memset(server_addr, 0, sizeof(server_addr)); server_addr.sin_family AF_INET; server_addr.sin_port htons(PORT); server_addr.sin_addr.s_addr inet_addr(IP); int ret bind(sockfd, (struct sockaddr*)server_addr, sizeof(server_addr)); assert(ret ! -1); ret listen(sockfd, 5); assert(ret ! -1); printf(HTTPS server listening on %s:%d\n, IP, PORT); return sockfd; } // 3. 发送 HTTP 响应头不变 void SendResponseHead(SSL *ssl, int contentLength, bool flag) { char responseHead[256] {0}; if (flag) strcat(responseHead, HTTP/1.1 200 OK\r\n); else strcat(responseHead, HTTP/1.1 404 Not Found\r\n); strcat(responseHead, Server:MYWEB/HTTPS\r\n); sprintf(responseHead strlen(responseHead), Content-Length:%d\r\n, contentLength); strcat(responseHead, Content-Type:text/html;charsetutf-8\r\n); strcat(responseHead, \r\n); SSL_write(ssl, responseHead, strlen(responseHead)); } // 4. 发送文件内容用 SSL_write void SendResponseBody(SSL *ssl, const char *filePath) { int fd open(filePath, O_RDONLY); if (fd -1) { perror(open); return; } char buff[1024]; while (true) { ssize_t n read(fd, buff, sizeof(buff)); if (n 0) break; SSL_write(ssl, buff, n); } close(fd); } // 5. 处理请求线程SSL 版 void* DealHttpRequest(void *arg) { SSL *ssl (SSL*)arg; char requestHead[1024]; ssize_t n SSL_read(ssl, requestHead, sizeof(requestHead)-1); if (n 0) { ERR_print_errors_fp(stderr); SSL_shutdown(ssl); SSL_free(ssl); return NULL; } requestHead[n] \0; char *requestWay strtok(requestHead, ); char *filePath strtok(NULL, ); if (strlen(filePath) 1 filePath[0] /) filePath /index.html; char path[256] .; strcat(path, filePath); struct stat st; bool flag true; int ret stat(path, st); if (ret -1) { flag false; strcpy(path, ./404.html); struct stat st404; if (stat(path, st404) -1) { SendResponseHead(ssl, 0, flag); SSL_shutdown(ssl); SSL_free(ssl); return NULL; } SendResponseHead(ssl, st404.st_size, flag); SendResponseBody(ssl, path); SSL_shutdown(ssl); SSL_free(ssl); return NULL; } SendResponseHead(ssl, st.st_size, flag); SendResponseBody(ssl, path); SSL_shutdown(ssl); SSL_free(ssl); return NULL; } // 6. 接受连接 SSL 握手 建线程 void AcceptClient(int sockfd) { while (true) { struct sockaddr_in client_addr; socklen_t client_len sizeof(client_addr); int c accept(sockfd, (struct sockaddr*)client_addr, client_len); if (c -1) { perror(accept); continue; } printf(Accept client %s:%d\n, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); // 新建 SSL 对象 SSL *ssl SSL_new(ctx); SSL_set_fd(ssl, c); // SSL 握手 if (SSL_accept(ssl) 0) { ERR_print_errors_fp(stderr); close(c); SSL_free(ssl); continue; } // 多线程处理 pthread_t id; pthread_create(id, NULL, DealHttpRequest, (void*)ssl); pthread_detach(id); // 分离线程避免资源泄漏 } } int main() { if (!InitSSL()) { fprintf(stderr, SSL init failed\n); return 1; } int sockfd CreateSocket(); AcceptClient(sockfd); close(sockfd); SSL_CTX_free(ctx); return 0; }三、问题1.HTTP 和 HTTPS 协议区别http超文本传输协议https :安全的超文本传输协议 在 HTTP 协议基础上加入了 SSL 协议保证安全传输。HTTP 和 HTTPS 主要应用于 Web 浏览器和网站服务器之间传递数据 HTTP 协议以明文方式发送内容不提供任何方式的数据加密如果攻击者截取了 Web 浏览器和网站服务器之间的传输报文就可以直接读懂其中的信息因此 HTTP 协议不适合传输一些敏感信息比如信用卡号、密码等。而 HTTPS 为了数据传输的安全在 HTTP 协议的基础上加入了 SSL 协议 SSL 协议依靠证书来验证服务器的身份并为浏览器和服务器之间的通信加密。区别(1)HTTPS 协议需要申请CA 证书一般免费的证书很少需要交费.(2)HTTP 是超文本传输协议信息是明文传输 HTTPS 是具有安全性的 SSL 加密传输协议。(3)HTTP 和 HTTPS 使用的是完全不同的连接方式用的端口号也不一样HTTP 80 HTTPS 443。(4)HTTP 协议的TCP 直连明文通信; HTTPS 协议是由SSLHTTP协议构成的可进行加密传输身份认证的网络协议比 HTTP 协议安全,TCP 先连再做 TLS 加密握手最后加密跑 HTTP。2.Cookie是什么应用到会话技术,Cookie;(小饼干,计算机里面的意思就是:不经使用者的认可就由服务器电脑直接写入使用者硬盘中的小型文字档案).也就是说是服务器产生的一小串的数据,在响应的过程中传给客户端,客户端及时保存下来,等到下次访问服务器的时候,就会把这个cookie给携带上。Cookie:有时也用其复数形式 Cookies。类型为“小型文本文件”是某些网站为了辨别用户身份进行Session跟踪而储存在用户本地终端上的数据通常经过加密由用户客户端计算机暂时或永久保存的信息。所谓“cookie”数据是指某些网站为了辨别用户身份储存在用户本地终端上的数据通常经过加密由用户客户端计算机暂时或永久保存的信息。通俗来讲就是指缓存数据包括用户名、密码、注册账户、手机号等公民个人信息。3.SSL握手SSL是Secure Sockets Layer安全套接层协议的缩写可以在Internet上提供秘密性传输。Netscape公司在推出第一个Web浏览器的同时提出了SSL协议标准。其目标是保证两个应用间通信的保密性和可靠性,可在服务器端和用户端同时实现支持。已经成为Internet上保密通讯的工业标准。SSL能使用户/服务器应用之间的通信不被攻击者窃听并且始终对服务器进行认证还可选择对用户进行认证。SSL协议要求建立在应用层协议(HTTP)和传输层协议(TCP)之间。SSL协议的优势在于它是与应用层协议独立无关的高层的应用层协议(例如HTTPFTPTELNET等)能透明地建立于SSL协议之上。SSL协议在应用层协议通信之前就已经完成加密算法、通信密钥的协商及服务器认证工作。在此之后应用层协议所传送的数据都会被加密从而保证通信的私密性。
计算机网络(4) -- http协议
一、浏览器与服务器通信过程浏览器与 web 服务器在应用层通信使用的是 HTTP 协议超文本传输协议而HTTP协议在传输层使用的是 TCP 协议。那么浏览器需要和 web 服务器三次握手建立连接后才可以发送 HTTP 请求报文服务器收到请求报文后向浏览器回复 HTTP 应答报文。浏览器向服务器发起连接前需要得到服务器的 IP 及端口。用户在浏览器中通常只输入网址网站域名 ,浏览器会通过DNS 服务查询获取到服务器的 IP 地址。 对于端口来讲使用 HTTP 协议的程序一般默认使用 80 端口。浏览器服务器建立连接后如果两次以上的请求复用同一个 TCP 连接则称之为长连接。如果浏览器发送一次请求报文服务器回复一次应答就断开连接下次交互再重新进行三次握手建立连接那么就被称作短连接。使用长连接显然是更好一些可以减少网络中的同步报文也使得服务器的响应速度变快。http属于应用层,它在传输层使用的是tcp协议;传输层协议:tcp 协议和udp协议。常见的 web 服务器有◼ Apache 简单、速度快、性能稳定并可做代理服务器使用◼ IISInternet Information Server安全性、强大、灵活◼ Nginx小巧而高效可以做高效的负载均衡反向代理◼ Tomcat技术先进、性能稳定、免费浏览器与服务器通信过程(1)浏览器从URL中解析出服务器的主机名;(2)浏览器通过DNS协议将服务器的主机名转换成服务器的IP地址;(3)浏览器将端口号(如果有的话)从URL中解析出来.(4)浏览器建立一条与Web服务器的TCP连接;(5)浏览器向服务器发送一条HTTP请求报文;(6)服务器向浏览器回送一条HTTP响应报文;(7)关闭连接,浏览器显示文档;DNS协议域名系统英文DomainNameSystem缩写DNS是互联网的一项服务。它作为将域名和IP地址相互映射的一个分布式数据库能够使人更方便地访问互联网。DNS使用UDP端口53。浏览器要将URL解析为IP地址,解析域名就要用到DNS协议,首先主机会查询DNS的缓存,如果没有就给本地DNS发送查询请求。DNS查询分为两种方式一种是递归查询一种是迭代查询。如果是迭代查询本地的DNS服务器向根域名服务器发送查询请求,根域名服务器告知该域名的一级域名服务器,然后本地服务器给该一级域名 服务器发送查询请求,然后依次类推直到查询到该域名的IP地址。而递归查询请求方只发一次请求等待最终结果中间查询全由接收方完成。DNS服务器是基于UDP的,因此会用到UDP协议。DNS 查询的迭代过程是从根域名服务器 → 顶级域名服务器 → 二级域名服务器 → 权威域名服务器也就是从右往左从顶级域名往前逐级查询的。URLURL:统一资源定位符,URL是资源标识符最常见的形式URL描述了一台特定服务器上某资源的特定位置.它们可以明确说明如何从一个精确,固定的位置获取资源。1.客户端发起请求浏览器解析这个 URL向www.joes-hardware.com服务器发送 HTTP 请求要求获取/specials/saw-blade.gif这个图片文件。2. 服务器定位资源服务器收到请求后在自己的存储中找到对应的saw-blade.gif文件。3.服务器返回响应服务器把文件数据打包成 HTTP 响应发给客户端响应头里会带上关键信息Content-type: image/gif告诉客户端这是一个 GIF 格式的图片Content-length: 8572告诉客户端这个文件的大小是 8572 字节4.客户端解析并展示浏览器收到数据后根据Content-type知道这是图片直接把它渲染到页面上。HTTP 协议格式HTTP 的请求报头结构HTTP 请求报文段示例/index.html指定资源文件的名称,这里指的是服务器根目录(站点的根目录,而不是服务器的文件系统根目录 /)中的索引文件。注意:Keep-Alive首部只是请求将连接保持在活跃状态。发出keep-alive请求之后,客户端和服务器端并不一定会统一进行keep-alive会话。它们可以在任意时刻关闭空闲的keep-alive连接,并可随意限制keep-alive连接所处理事务的数量。HTTP 的请求方法HTTP 的应答报头结构HTTP 的应答报头结构HTTP 的应答状态二、搭建WEB服务器首先先写出短连接的服务器过程在一步一步修改。我们通过封装CreatSocket函数用于创建sockfd套接字在通过一个死循环去处理不同客户端的消息我们先实现出没有多线程和多进程的处理逻辑。#include stdio.h #include stdlib.h #include string.h #include arpa/inet.h //转换IP地址 #include netinet/in.h//转换字节序 #include sys/types.h #include sys/socket.h #include unistd.h //close函数 #include assert.h #include stdbool.h //创建socket绑定地址监听端口 int CreateSocket() { int sockfd socket(AF_INET, SOCK_STREAM, 0); assert(sockfd ! -1); struct sockaddr_in server_addr; memset(server_addr, 0, sizeof(server_addr)); server_addr.sin_family AF_INET; server_addr.sin_port htons(80); server_addr.sin_addr.s_addr inet_addr(192.168.199.128); int ret bind(sockfd, (struct sockaddr*)server_addr, sizeof(server_addr)); assert(ret ! -1); ret listen(sockfd, 5); assert(ret ! -1); return sockfd; } //先接收报头并打印出来 void DealHttpRequest(int c) { char requestHead[1024]; ssize_t n recv(c, requestHead, sizeof(requestHead)-1, 0); if(n 0){ perror(recv); return; } requestHead[n] \0; printf(Request:\n%s\n, requestHead); } //接收客户端连接处理请求 void AcceptClient(int sockfd) { struct sockaddr_in client_addr; socklen_t client_len sizeof(client_addr); int c accept(sockfd, (struct sockaddr*)client_addr, client_len); if(c -1){ perror(accept); return; } printf(Accept client %s:%d\n, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); //针对客户端的处理 DealHttpRequest(c); close(c); } int main() { int sockfd CreateSocket(); //处理多个客户端连接 while(true){ AcceptClient(sockfd); } close(sockfd); return 0; }上述代码就是先接受客户端发送的http请求报头先进行打印之后在去处理相应逻辑但是在这里我们需要注意80端口不能被普通用户使用一般情况下1024一下的端口号都不能被普通用户使用。这时候我们需要切换到root用户或者将80端口修改为HTTP 协议的 “通用替代端口”-8080端口即可正常启动。这里我演示的是切换root用户。我通过linux系统下的Firefox浏览器去访问该网址的index.html但是我的路径下是没有这个文件的所以他是会连续重传好几次的但是这时候我们已经可以确定是能够接收到请求报头的接下来就是对报头内容进行解析并返回相应的应答报头。多线程版本多线程版简易 Web 服务器采用一连接一线程的并发模型。服务器首先创建套接字、绑定本机 IP 与端口、完成监听初始化。程序通过死循环持续调用accept()阻塞等待客户端连接。每当有新客户端连接成功后不直接阻塞处理业务而是创建新线程单独处理本次 HTTP 请求主线程立刻回归监听状态实现支持多客户端同时访问的并发能力。子线程负责具体的 HTTP 请求解析与资源响应工作首先读取浏览器发来的 HTTP 请求报头通过字符串解析出用户请求的资源路径如果用户访问根路径/则默认访问index.html主页。通过stat()函数判断目标文件是否存在文件存在读取文件大小拼接并发送200 OK响应报头再读取本地文件内容发送给浏览器完成正常网页访问。文件不存在跳转读取本地404.html页面发送404 Not Found响应报头与错误页面内容实现标准 404 错误提示。所有请求处理完毕后线程统一释放堆内存、关闭客户端文件描述符避免内存泄漏与文件资源泄露子线程任务执行完毕自动退出。整体基于 TCP 流式套接字手动实现简易 HTTP 应答协议利用多线程解决单进程服务器无法并发访问的问题完成了基础 Web 服务器的资源访问与错误处理功能。//多线程版本 #include stdio.h #include stdlib.h #include string.h #include arpa/inet.h //转换IP地址 #include netinet/in.h//转换字节序 #include sys/types.h #include sys/socket.h #include unistd.h //close函数 #include assert.h #include stdbool.h #include sys/stat.h #include fcntl.h #include pthread.h //创建socket绑定地址监听端口 int CreateSocket() { int sockfd socket(AF_INET, SOCK_STREAM, 0); assert(sockfd ! -1); struct sockaddr_in server_addr; memset(server_addr, 0, sizeof(server_addr)); server_addr.sin_family AF_INET; server_addr.sin_port htons(80); server_addr.sin_addr.s_addr inet_addr(192.168.199.128); int ret bind(sockfd, (struct sockaddr*)server_addr, sizeof(server_addr)); assert(ret ! -1); ret listen(sockfd, 5); assert(ret ! -1); return sockfd; } //发送应答报头 void SendResponseHead(int c, int contentLength, bool flag) { char responseHead[256] {0}; if(flag){ strcat(responseHead,HTTP/1.1 200 OK\r\n); }else{ strcat(responseHead,HTTP/1.1 404 Not Found\r\n); } strcat(responseHead,Server:MYWEB/1.1\r\n); sprintf(responseHead strlen(responseHead) ,Content-Length:%d\r\n,contentLength); strcat(responseHead,Content-Type:text/html;charsetutf-8\r\n); strcat(responseHead,\r\n); send(c,responseHead,strlen(responseHead),0); } //发送文件内容 void SendResponseBody(int c, const char *filePath) { int fd open(filePath, O_RDONLY); if(fd -1){ perror(open); return; } char buff[1024]; while(true){ ssize_t n read(fd, buff, sizeof(buff)); if(n 0){ break; } send(c, buff, n, 0); } close(fd); } //先接收报头并打印出来 void* DealHttpRequest(void *arg) { int c *(int *)arg; char requestHead[1024]; ssize_t n recv(c, requestHead, sizeof(requestHead)-1, 0); if(n 0){ perror(recv); free(arg); close(c); return NULL; } requestHead[n] \0; //printf(Request:\n%s\n, requestHead); char *requestWay strtok(requestHead, ); char *filePath strtok(NULL, ); if(strlen(filePath) 1 filePath[0] /){ filePath /index.html; } char path[256] .; strcat(path, filePath); struct stat st; //利用stat结构体成员st_size获取文件大小 //undo,判断文件是否存在不存在则返回404 bool flag true; int ret stat(path, st); //assert(ret ! -1); if(ret -1){ flag false; memset(path, 0, sizeof(path)); strcpy(path, ./404.html); //先修改路径为404.html看看是否存在 struct stat st404; if(stat(path, st404) -1){ // 连404.html都找不到只能返回最基础的404响应 SendResponseHead(c, 0, flag); free(arg); close(c); return NULL; } SendResponseHead(c, st404.st_size, flag); //发送404头部 SendResponseBody(c, path); //发送404页面内容 free(arg); close(c); return NULL; } SendResponseHead(c, st.st_size, flag); //发送头部 SendResponseBody(c, path); //发送文件内容 free(arg); close(c); return NULL; } //接收客户端连接处理请求 void AcceptClient(int sockfd) { while(true){ struct sockaddr_in client_addr; socklen_t client_len sizeof(client_addr); int c accept(sockfd, (struct sockaddr*)client_addr, client_len); if(c -1){ perror(accept); return; } printf(Accept client %s:%d\n, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); int *pc (int *)malloc(sizeof(int)); *pc c; //针对客户端的处理 //多线程处理 pthread_t id; pthread_create(id,NULL,DealHttpRequest,(void *)pc); pthread_detach(id); // 分离线程避免资源泄漏 } } int main() { int sockfd CreateSocket(); //处理多个客户端连接 while(true){ AcceptClient(sockfd); } close(sockfd); return 0; }HTTPS版本HTTPS版本我们需要先生成证书但是这个证书只是适用于测试使用并不能直接进行商用。1.通过这个命令生成证书 openssl req -x509 -newkey rsa:2048 -nodes -keyout server.key -out server.crt -days 365 2.填写相关信息 Country Name (2 letter code) [AU]:CN //中国的国家代码 State or Province Name (full name) [Some-State]: //省份 Locality Name (eg, city) []: //城市 Organization Name (eg, company) [Internet Widgits Pty Ltd]: //组织随便填 Organizational Unit Name (eg, section) []: //部门随便填 Common Name (e.g. server FQDN or YOUR name) []: //最重要填你的服务器 IP否则浏览器会报证书域名不匹配 Email Address []: //测试证书可以不填 3.填写完成后会得到两个文件 server.crt //证书 server.key //私钥 将两个文件放在web_ser.c下的路径 4.每个客户端连接都做 SSL 握手 5.用 SSL_read / SSL_write 代替 recv /send HTTPS 不改变 HTTP 协议本身只负责传输加密。//https版本 #include stdio.h #include stdlib.h #include string.h #include arpa/inet.h #include netinet/in.h #include sys/types.h #include sys/socket.h #include unistd.h #include assert.h #include stdbool.h #include sys/stat.h #include fcntl.h #include pthread.h // OpenSSL 头文件 #include openssl/ssl.h #include openssl/err.h #define PORT 443 #define IP 192.168.199.128 SSL_CTX *ctx NULL; // 全局 SSL 上下文 // 1. 初始化 OpenSSL 加载证书私钥 bool InitSSL() { // OpenSSL 初始化 SSL_library_init(); SSL_load_error_strings(); OpenSSL_add_all_algorithms(); // 创建 TLS 服务器上下文 ctx SSL_CTX_new(TLS_server_method()); if (!ctx) { ERR_print_errors_fp(stderr); return false; } // 加载证书 if (SSL_CTX_use_certificate_file(ctx, server.crt, SSL_FILETYPE_PEM) 0) { ERR_print_errors_fp(stderr); return false; } // 加载私钥 if (SSL_CTX_use_PrivateKey_file(ctx, server.key, SSL_FILETYPE_PEM) 0) { ERR_print_errors_fp(stderr); return false; } // 检查私钥是否匹配证书 if (!SSL_CTX_check_private_key(ctx)) { fprintf(stderr, Private key does not match certificate\n); return false; } return true; } // 2. 创建 socket、绑定、监听端口 443 int CreateSocket() { int sockfd socket(AF_INET, SOCK_STREAM, 0); assert(sockfd ! -1); int opt 1; setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, opt, sizeof(opt)); struct sockaddr_in server_addr; memset(server_addr, 0, sizeof(server_addr)); server_addr.sin_family AF_INET; server_addr.sin_port htons(PORT); server_addr.sin_addr.s_addr inet_addr(IP); int ret bind(sockfd, (struct sockaddr*)server_addr, sizeof(server_addr)); assert(ret ! -1); ret listen(sockfd, 5); assert(ret ! -1); printf(HTTPS server listening on %s:%d\n, IP, PORT); return sockfd; } // 3. 发送 HTTP 响应头不变 void SendResponseHead(SSL *ssl, int contentLength, bool flag) { char responseHead[256] {0}; if (flag) strcat(responseHead, HTTP/1.1 200 OK\r\n); else strcat(responseHead, HTTP/1.1 404 Not Found\r\n); strcat(responseHead, Server:MYWEB/HTTPS\r\n); sprintf(responseHead strlen(responseHead), Content-Length:%d\r\n, contentLength); strcat(responseHead, Content-Type:text/html;charsetutf-8\r\n); strcat(responseHead, \r\n); SSL_write(ssl, responseHead, strlen(responseHead)); } // 4. 发送文件内容用 SSL_write void SendResponseBody(SSL *ssl, const char *filePath) { int fd open(filePath, O_RDONLY); if (fd -1) { perror(open); return; } char buff[1024]; while (true) { ssize_t n read(fd, buff, sizeof(buff)); if (n 0) break; SSL_write(ssl, buff, n); } close(fd); } // 5. 处理请求线程SSL 版 void* DealHttpRequest(void *arg) { SSL *ssl (SSL*)arg; char requestHead[1024]; ssize_t n SSL_read(ssl, requestHead, sizeof(requestHead)-1); if (n 0) { ERR_print_errors_fp(stderr); SSL_shutdown(ssl); SSL_free(ssl); return NULL; } requestHead[n] \0; char *requestWay strtok(requestHead, ); char *filePath strtok(NULL, ); if (strlen(filePath) 1 filePath[0] /) filePath /index.html; char path[256] .; strcat(path, filePath); struct stat st; bool flag true; int ret stat(path, st); if (ret -1) { flag false; strcpy(path, ./404.html); struct stat st404; if (stat(path, st404) -1) { SendResponseHead(ssl, 0, flag); SSL_shutdown(ssl); SSL_free(ssl); return NULL; } SendResponseHead(ssl, st404.st_size, flag); SendResponseBody(ssl, path); SSL_shutdown(ssl); SSL_free(ssl); return NULL; } SendResponseHead(ssl, st.st_size, flag); SendResponseBody(ssl, path); SSL_shutdown(ssl); SSL_free(ssl); return NULL; } // 6. 接受连接 SSL 握手 建线程 void AcceptClient(int sockfd) { while (true) { struct sockaddr_in client_addr; socklen_t client_len sizeof(client_addr); int c accept(sockfd, (struct sockaddr*)client_addr, client_len); if (c -1) { perror(accept); continue; } printf(Accept client %s:%d\n, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); // 新建 SSL 对象 SSL *ssl SSL_new(ctx); SSL_set_fd(ssl, c); // SSL 握手 if (SSL_accept(ssl) 0) { ERR_print_errors_fp(stderr); close(c); SSL_free(ssl); continue; } // 多线程处理 pthread_t id; pthread_create(id, NULL, DealHttpRequest, (void*)ssl); pthread_detach(id); // 分离线程避免资源泄漏 } } int main() { if (!InitSSL()) { fprintf(stderr, SSL init failed\n); return 1; } int sockfd CreateSocket(); AcceptClient(sockfd); close(sockfd); SSL_CTX_free(ctx); return 0; }三、问题1.HTTP 和 HTTPS 协议区别http超文本传输协议https :安全的超文本传输协议 在 HTTP 协议基础上加入了 SSL 协议保证安全传输。HTTP 和 HTTPS 主要应用于 Web 浏览器和网站服务器之间传递数据 HTTP 协议以明文方式发送内容不提供任何方式的数据加密如果攻击者截取了 Web 浏览器和网站服务器之间的传输报文就可以直接读懂其中的信息因此 HTTP 协议不适合传输一些敏感信息比如信用卡号、密码等。而 HTTPS 为了数据传输的安全在 HTTP 协议的基础上加入了 SSL 协议 SSL 协议依靠证书来验证服务器的身份并为浏览器和服务器之间的通信加密。区别(1)HTTPS 协议需要申请CA 证书一般免费的证书很少需要交费.(2)HTTP 是超文本传输协议信息是明文传输 HTTPS 是具有安全性的 SSL 加密传输协议。(3)HTTP 和 HTTPS 使用的是完全不同的连接方式用的端口号也不一样HTTP 80 HTTPS 443。(4)HTTP 协议的TCP 直连明文通信; HTTPS 协议是由SSLHTTP协议构成的可进行加密传输身份认证的网络协议比 HTTP 协议安全,TCP 先连再做 TLS 加密握手最后加密跑 HTTP。2.Cookie是什么应用到会话技术,Cookie;(小饼干,计算机里面的意思就是:不经使用者的认可就由服务器电脑直接写入使用者硬盘中的小型文字档案).也就是说是服务器产生的一小串的数据,在响应的过程中传给客户端,客户端及时保存下来,等到下次访问服务器的时候,就会把这个cookie给携带上。Cookie:有时也用其复数形式 Cookies。类型为“小型文本文件”是某些网站为了辨别用户身份进行Session跟踪而储存在用户本地终端上的数据通常经过加密由用户客户端计算机暂时或永久保存的信息。所谓“cookie”数据是指某些网站为了辨别用户身份储存在用户本地终端上的数据通常经过加密由用户客户端计算机暂时或永久保存的信息。通俗来讲就是指缓存数据包括用户名、密码、注册账户、手机号等公民个人信息。3.SSL握手SSL是Secure Sockets Layer安全套接层协议的缩写可以在Internet上提供秘密性传输。Netscape公司在推出第一个Web浏览器的同时提出了SSL协议标准。其目标是保证两个应用间通信的保密性和可靠性,可在服务器端和用户端同时实现支持。已经成为Internet上保密通讯的工业标准。SSL能使用户/服务器应用之间的通信不被攻击者窃听并且始终对服务器进行认证还可选择对用户进行认证。SSL协议要求建立在应用层协议(HTTP)和传输层协议(TCP)之间。SSL协议的优势在于它是与应用层协议独立无关的高层的应用层协议(例如HTTPFTPTELNET等)能透明地建立于SSL协议之上。SSL协议在应用层协议通信之前就已经完成加密算法、通信密钥的协商及服务器认证工作。在此之后应用层协议所传送的数据都会被加密从而保证通信的私密性。