HTTP协议和静态web服务器

  • (一)三次握手和四次挥手
  • (二)HTTP协议
      • 2.1 HTTP协议的定义
      • 2.2 HTTP协议的组成
  • (三)搭建python自带静态web服务器
      • 3.1 静态web服务器是什么
      • 3.2 如何搭建python自带的静态web服务器
      • 3.3 访问本地的静态文件
      • 3.4 查看浏览器和搭建的web静态服务器的通信过程
  • (四)静态web服务器-返回固定页面数据
      • 4.1 开发自己的静态web服务器
      • 4.2 返回固定页面数据的代码示例
  • (五) 静态web服务器-返回指定页面数据
      • 5.1 静态web服务器的问题
      • 5.2 返回指定页面数据的代码示例
        • 丢包、粘包问题解决思路和方法
        • 发送接收缓冲 消息格式定义

🏘️🏘️个人简介:以山河作礼。
🎖️🎖️:Python领域新星创作者,CSDN实力新星认证,阿里云社区专家博主
🎁🎁:Web全栈开发专栏:《Web全栈开发》免费专栏,欢迎阅读!

(一)三次握手和四次挥手

TCP (Transmission Control Protocol) 是在互联网协议(IP)上的一种基于连接(面向连接)的传输层协议。数据通信的基本要素包括“所传输的数据”“发送方”“接收方“三个要素,还有保证所传输的数据是完好无损,正确无误的。这一普遍要求都是由TCP满足的,下面介绍TCP的三次握手和四次挥手过程。

〖Web全栈开发③〗—HTTP协议和静态web服务器

三次握手:

1.客户端发送一个SYN,表示要发起一个连接请求。

2.服务端收到客户端请求之后,向客户端发送一个SYN和ACK,表示确认收到请求并准备好连接。

3.客户端接收到服务端的响应之后,向服务端发回ACK,表示确认可以建立连接了。

这个时候客户端和服务端就建立起了连接,可以开始通信了。

四次挥手:

1.客户端发送一个FIN,表示要释放连接。

2.服务端接收到FIN之后,会回复一个ACK,表示确认收到。

3.服务端准备释放连接时,同样发送一个FIN给客户端。

4.客户端接收到服务端的FIN之后,会回复一个ACK,表示确认收到释放请求。

这个时候,客户端和服务端都释放了连接,连接断开。

总结:

1.三次握手是建立TCP连接的必要步骤,而四次挥手则是释放TCP连接的必要步骤。

2.三次握手和四次挥手的目的是确保数据传输的可靠性和正确性。

3.连接的释放过程比连接的建立过程要复杂,这是因为连接释放过程需要双方都知道连接已经释放,而连接建立过程只需要被请求方知道即可。

4.TCP协议的三次握手和四次挥手是TCP协议可靠性的重要保证,也是网络通信的必要约定。

(二)HTTP协议

〖Web全栈开发③〗—HTTP协议和静态web服务器

2.1 HTTP协议的定义

2.2 HTTP协议的组成

〖Web全栈开发③〗—HTTP协议和静态web服务器

**〖Web全栈开发③〗—HTTP协议和静态web服务器
**

〖Web全栈开发③〗—HTTP协议和静态web服务器

(三)搭建python自带静态web服务器

3.1 静态web服务器是什么

静态web服务器是指可以为出发请求的浏览器提供静态文档的程序。

平时我们浏览百度新闻数据的时候,每天的新闻数据都会发生变化,那访问的这个页面就是动态的。

而静态的Web服务器,页面的数据不会发生变化。

3.2 如何搭建python自带的静态web服务器

搭建python自带的静态web服务器使用命令:

python -m http.server

〖Web全栈开发③〗—HTTP协议和静态web服务器

3.3 访问本地的静态文件

在浏览器中访问本地的html文件

首先在命令行进入该文件所在目录,然后启动静态服务器:

〖Web全栈开发③〗—HTTP协议和静态web服务器

最后可以在浏览器进行访问

〖Web全栈开发③〗—HTTP协议和静态web服务器

3.4 查看浏览器和搭建的web静态服务器的通信过程

〖Web全栈开发③〗—HTTP协议和静态web服务器

〖Web全栈开发③〗—HTTP协议和静态web服务器

(四)静态web服务器-返回固定页面数据

4.1 开发自己的静态web服务器

4.2 返回固定页面数据的代码示例

import socket
if __name__ == '__main__':
    # 创建tcp服务端套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 设置端口号复用, 程序退出端口立即释放
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
    # 绑定端口号
    tcp_server_socket.bind(("", 9000))
    # 设置监听
    tcp_server_socket.listen(128)
    while True:
        # 等待接受客户端的连接请求
        new_socket, ip_port = tcp_server_socket.accept()
        # 代码执行到此,说明连接建立成功
        recv_client_data = new_socket.recv(4096)
        # 对二进制数据进行解码
        recv_client_content = recv_client_data.decode("utf-8")
        print(recv_client_content)
        with open("static/index.html", "rb") as file:
            # 读取文件数据
            file_data = file.read()
        # 响应行
        response_line = "HTTP/1.1 200 OK\r\n"
        # 响应头
        response_header = "Server: PWS1.0\r\n"
        # 响应体
        response_body = file_data
        # 拼接响应报文
        response_data = (response_line + response_header + "\r\n").encode("utf-8") + response_body
        # 发送数据
        new_socket.send(response_data)
        # 关闭服务与客户端的套接字
        new_socket.close()

(五) 静态web服务器-返回指定页面数据

5.1 静态web服务器的问题

目前的web服务器,不管用户访问什么页面,返回的都是固定页面的数据,接下来需要根据用户的请求返回指定页面的数据

返回指定页面数据的实现步骤:

  1. 获取用户请求资源的路径。
  2. 根据请求资源的路劲,读取指定文件的数据。
  3. 组装指定文件数据的响应报文,发送给浏览器。
  4. 判断亲贵的文件在服务端不存在,组装404状态的响应报文,发送给浏览器。

5.2 返回指定页面数据的代码示例

import socket
def main():
    # 创建tcp服务端套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 设置端口号复用, 程序退出端口立即释放
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
    # 绑定端口号
    tcp_server_socket.bind(("", 9000))
    # 设置监听
    tcp_server_socket.listen(128)
    while True:
        # 等待接受客户端的连接请求
        new_socket, ip_port = tcp_server_socket.accept()
        # 代码执行到此,说明连接建立成功
        recv_client_data = new_socket.recv(4096)
        if len(recv_client_data) == 0:
            print("关闭浏览器了")
            new_socket.close()
            return
        # 对二进制数据进行解码
        recv_client_content = recv_client_data.decode("utf-8")
        print(recv_client_content)
        # 根据指定字符串进行分割,最大分割次数指定2
        request_list = recv_client_content.split(" ", maxsplit=2)
        # print("===")
        # print(request_list)
        # print("===")
        # 获取请求资源路径
        request_path = request_list[1]
        print(request_path)
        # 判断请求的是否是根目录,如果条件成立,指定首页数据返回
        if request_path == "/":
            request_path = "/index.html"
        try:
            # 动态打开指定文件
            with open("static" + request_path, "rb") as file:
                # 读取文件数据
                file_data = file.read()
        except Exception as e:
            # 请求资源不存在,返回404数据
            # 响应行
            response_line = "HTTP/1.1 404 Not Found\r\n"
            # 响应头
            response_header = "Server: PWS1.0\r\n"
            with open("static/error.html", "rb") as file:
                file_data = file.read()
            # 响应体
            response_body = file_data
            # 拼接响应报文
            response_data = (response_line + response_header + "\r\n").encode("utf-8") + response_body
            # 发送数据
            new_socket.send(response_data)
        else:
            # 响应行
            response_line = "HTTP/1.1 200 OK\r\n"
            # 响应头
            response_header = "Server: PWS1.0\r\n"
            # 响应体
            response_body = file_data
            # 拼接响应报文
            response_data = (response_line + response_header + "\r\n").encode("utf-8") + response_body
            # 发送数据
            new_socket.send(response_data)
        finally:
            # 关闭服务与客户端的套接字
            new_socket.close()
if __name__ == '__main__':
    main()

丢包、粘包问题解决思路和方法

问题:

Socket有一个缓冲区,缓冲区是一个流,先进先出,发送和取出都可以自定义大小,如果缓冲区的数据未取完,则可能会存在数据堆积。其中recv(1024)表示从缓冲区里取最大为1024个字节,但实际取值大小是不确定的,可能会导致丢包,socket发送两条连续数据时,也有可能最终会拼接成一条进行发送,所以也会导致粘包问题的产生。

解决的一些办法和思路:

在每条数据发送之间增加停顿时间,如【tiem.sleep(0.5) # 延时0.5s】

每次发送后等待对方确认接收数据,确认完毕后再发送下一条(加验证),否则重传

减少一次性发送和接收数据的大小,理论上buffer size越小丢包或粘包率就越低,建议在1024~10240之间

发送接收缓冲 消息格式定义

编程的时候,如果要跟某个IP建立连接,我们需要调用操作系统提供的 socket API。

socket 在操作系统层面,可以理解为一个文件。我们可以对这个文件进行一些方法操作。

用listen方法,可以让程序作为服务器监听其他客户端的连接。
用connect,可以作为客户端连接服务器。
用send或write可以发送数据,recv或read可以接收数据。
在建立好连接之后,如果我们想给远端服务发点什么东西,那就只需要对这个文件执行写操作就行了。

剩下的发送工作自然就是由操作系统内核来完成了。

既然是写给操作系统,那操作系统就需要提供一个地方给用户写。同理,接收消息也是一样。这个地方就是 socket 缓冲区。

用户发送消息的时候写给 send buffer(发送缓冲区)
用户接收消息的时候写给 recv buffer(接收缓冲区)
也就是说一个socket ,会带有两个缓冲区,一个用于发送,一个用于接收。因为这是个先进先出的结构,有时候也叫它们发送、接收队列。

在企业中开发的程序通信,消息往往有格式定义。消息的格式定义可以归入OSI中的表示层

比如:定义的消息,包括消息头和消息体。

消息头存放消息的数据格式(消息的长度、类型、状态等),消息体存放具体传送的数据。

对于TCP协议传输信息的程序来说,格式一定要有明确规定的消息边界。因为TCP传输的是字节流(bytes stream),如果消息中没有指定边界或者长度,接收方就不知道完整的消息从字节流的哪里开始,到哪里结束。

指定消息的边界有两种方式:

1.用特殊字节作为消息的结尾符号

可以用消息中不易出现的字符串(比如 FFFFFF)作为消息的结尾字符

2.在消息开头某个位置,直接指定消息的长度

UDP协议通常不用指定边界,因为UDP是数据报协议,应用程序从socket接收到的

必定是发送方发送的完整消息。

发表回复