HTTP持久连接

HTTP持久连接详解

Persistent Connections

What is HTTP Persistent Connections?

HTTP persistent connections, also called HTTP keep-alive, or HTTP connection reuse, is the idea of using the same TCP connection to send and receive multiple HTTP requests/responses, as opposed to opening a new one for every single request/response pair. Using persistent connections is very important for improving HTTP performance.

There are several advantages of using persistent connections, including:

HTTP持久连接也称为HTTP keep-alive,或者叫HTTP连接重用,意思是指使用同一个TCP连接来处理多个HTTP requests/responses,与每个request/response pair open一个连接的方式相反。持久连接对于提高HTTP performance非常重要。

  • Network friendly. Less network traffic due to fewer setting up and tearing down of TCP connections.
  • Reduced latency on subsequent request. Due to avoidance of initial TCP handshake
  • Long lasting connections allowing TCP sufficient time to determine the congestion state of the network, thus to react appropriately.网络接口友好的:通过减少TCP连接的初始化和清理操作(握手),降低了网络传输
    为后续请求提高响应时间:通过避免TCP握手
    长连接允许TCP有足够的时间来测定网络连接状态,并做适当优化。

 

The advantages are even more obvious with HTTPS or HTTP over SSL/TLS. There, persistent connections may reduce the number of costly SSL/TLS handshake to establish security associations, in addition to the initial TCP connection set up.

这些优点在使用HTTPS或者基于SSL/TLS的HTTP协议时尤为明显。因为持久连接可以减少TCP连接的初始化设置及SSL/TLS握手的次数。

 

In HTTP/1.1, persistent connections are the default behavior of any connection. That is, unless otherwise indicated, the client SHOULD assume that the server will maintain a persistent connection, even after error responses from the server. However, the protocol provides means for a client and a server to signal the closing of a TCP connection.

HTTP/1.1规范中,默认使用持久连接。也就是说,在没有明确指定的情况下,客户端是(应该)假设服务端可以维持一个持久连接的,即使返回了错误的应答消息。然而,协议规定客户端与服务端均可以主动关闭这个持久的TCP连接。

 

What makes a connection reusable?

Since TCP by its nature is a stream based protocol, in order to reuse an existing connection, the HTTP protocol has to have a way to indicate the end of the previous response and the beginning of the next one. Thus, it is required that all messages on the connection MUST have a self-defined message length (i.e., one not defined by closure of the connection). Self demarcation is achieved by either setting the Content-Length header, or in the case of chunked transfer encoded entity body, each chunk starts with a size, and the response body ends with a special last chunk.

TCP协议本质上基于流的协议,为了重用已经打开的连接,HTTP协议必须能够识别前一个应答的结束和下一个请求的开始。因此呢,这就要求所有通过这个连接传递的消息必须有一个自定义的(message length)消息段(也就是,one not defined by closure of the connection)。Self demarcation is achieved by either setting the Content-Length header, or in the case of chunked transfer encoded entity body,each chunk starts with a size, and the response body ends with a special last chunk.(大概就是在应答和请求中添加标识)

 

What happens if there are proxy servers in between?

Since persistent connections applies to only one transport link, it is important that proxy servers correctly signal persistent/or-non-persistent connections separately with its clients and the origin servers (or to other proxy servers). From a HTTP client or server's perspective, as far as persistence connection is concerned, the presence or absence of proxy servers is transparent.

(代理应该可以正确的标识持久连接/非持久连接,代理对客户端和服务端均是透明的)

 

What does the current JDK do for Keep-Alive?

The JDK supports both HTTP/1.1 and HTTP/1.0 persistent connections.

When the application finishes reading the response body or when the application calls close() on the InputStream returned by URLConnection.getInputStream(), the JDK's HTTP protocol handler will try to clean up the connection and if successful, put the connection into a connection cache for reuse by future HTTP requests.

The support for HTTP keep-Alive is done transparently. However, it can be controlled by system properties http.keepAlive, and http.maxConnections, as well as by HTTP/1.1 specified request and response headers.

The system properties that control the behavior of Keep-Alive are:

http.keepAlive=<boolean>
default: true

Indicates if keep alive (persistent) connections should be supported.

http.maxConnections=<int>
default: 5

Indicates the maximum number of connections per destination to be kept alive at any given time

HTTP header that influences connection persistence is:

Connection: close

If the "Connection" header is specified with the value "close" in either the request or the response header fields, it indicates that the connection should not be considered 'persistent' after the current request/response is complete.

The current implementation doesn't buffer the response body. Which means that the application has to finish reading the response body or call close() to abandon the rest of the response body, in order for that connection to be reused. Furthermore, current implementation will not try block-reading when cleaning up the connection, meaning if the whole response body is not available, the connection will not be reused.

What's new in Tiger?

When the application encounters a HTTP 400 or 500 response, it may ignore the IOException and then may issue another HTTP request. In this case, the underlying TCP connection won't be Kept-Alive because the response body is still there to be consumed, so the socket connection is not cleared, therefore not available for reuse. What the application needs to do is call HttpURLConnection.getErrorStream() after catching the IOException , read the response body, then close the stream. However, some existing applications are not doing this. As a result, they do not benefit from persistent connections. To address this problem, we have introduced a workaround.

The workaround involves buffering the response body if the response is >=400, up to a certain amount and within a time limit, thus freeing up the underlying socket connection for reuse. The rationale behind this is that when the server responds with a >=400 error (client error or server error. One example is "404: File Not Found" error), the server usually sends a small response body to explain whom to contact and what to do to recover.

Several new Sun implementation specific properties are introduced to help clean up the connections after error response from the server.

The major one is:

sun.net.http.errorstream.enableBuffering=<boolean>
default: false

With the above system property set to true (default is false), when the response code is >=400, the HTTP handler will try to buffer the response body. Thus freeing up the underlying socket connection for reuse. Thus, even if the application doesn't call getErrorStream(), read the response body, and then call close(), the underlying socket connection may still be kept-alive and reused.

The following two system properties provide further control to the error stream buffering behavior:

sun.net.http.errorstream.timeout=<int> in millisecond
default: 300 millisecond

sun.net.http.errorstream.bufferSize=<int> in bytes
default: 4096 bytes

What can you do to help with Keep-Alive?

Do not abandon a connection by ignoring the response body. Doing so may results in idle TCP connections. That needs to be garbage collected when they are no longer referenced.

If getInputStream() successfully returns, read the entire response body.

When calling getInputStream() from HttpURLConnection, if an IOException occurs, catch the exception and call getErrorStream() to get the response body (if there is any).

Reading the response body cleans up the connection even if you are not interested in the response content itself. But if the response body is long and you are not interested in the rest of it after seeing the beginning, you can close the InputStream. But you need to be aware that more data could be on its way. Thus the connection may not be cleared for reuse.

Here's a code example that complies to the above recommendation:

try {
	URL a = new URL(args[0]);
	URLConnection urlc = a.openConnection();
	is = conn.getInputStream();
	int ret = 0;
	while ((ret = is.read(buf)) > 0) {
	  processBuf(buf);
	}
	// close the inputstream
	is.close();
} catch (IOException e) {
	try {
		respCode = ((HttpURLConnection)conn).getResponseCode();
		es = ((HttpURLConnection)conn).getErrorStream();
		int ret = 0;
		// read the response body
		while ((ret = es.read(buf)) > 0) {
			processBuf(buf);
		}
		// close the errorstream
		es.close();
	} catch(IOException ex) {
		// deal with the exception
	}
}

If you know ahead of time that you won't be interested in the response body, you should issue a HEAD request instead of a GET request. For example when you are only interested in the meta info of the web resource or when testing for its validity, accessibility and recent modification. Here's a code snippet:

URL a = new URL(args[0]);
URLConnection urlc = a.openConnection();
HttpURLConnection httpc = (HttpURLConnection)urlc;
// only interested in the length of the resource
httpc.setRequestMethod("HEAD");
int len = httpc.getContentLength();

Future directions in the JDK?

  • More aggressive clean-up. An example is measured blocking read when application calls close before finish reading the response body
  • pipelining requests to further increase the network bandwidth utilization and reduce latency

 

 

<think>我们正在讨论的是RNDIS相关的内核配置。RNDIS(Remote Network Driver Interface Specification)是微软定义的USB网络设备协议,允许USB设备充当网络适配器。在Linux中,RNDIS可以通过USB Gadget框架实现,使得Linux设备(如嵌入式系统)可以通过USB连接到主机并作为网络设备使用。 根据用户提供的引用[1],我们看到了一些与USB网络相关的内核配置选项,其中包括: - `CONFIG_USB_USBNET=y`:启用通用的USB网络驱动框架。 - `CONFIG_USB_NET_RNDIS_HOST=y`:这个选项实际上用于主机端(host)驱动,它使得Linux主机可以支持连接到RNDIS设备(例如Android手机或Windows设备)。但是,用户询问的是如何配置Linux设备作为RNDIS设备(gadget模式),因此我们需要关注的是Gadget驱动中的RNDIS功能。 对于Linux设备作为RNDIS设备(即USB从设备模式),我们需要配置的是Gadget驱动中的RNDIS功能。相关的配置选项包括: 1. **核心配置**: - `CONFIG_USB_GADGET=y`:启用USB Gadget支持。 - `CONFIG_USB_CONFIGFS=y`:允许通过用户空间配置USB Gadget功能(推荐,因为它更灵活)。 - `CONFIG_USB_CONFIGFS_RNDIS=y`:在ConfigFS框架下启用RNDIS功能。 2. **具体功能驱动**: - `CONFIG_USB_ETH=y`:启用USB以太网Gadget驱动(这个驱动支持多种子协议,包括RNDIS)。 - `CONFIG_USB_ETH_RNDIS=y`:在USB以太网Gadget驱动中启用RNDIS协议支持。 注意:在较新的内核中,RNDIS功能通常集成在`CONFIG_USB_ETH`驱动中,并通过`CONFIG_USB_ETH_RNDIS`选项来启用RNDIS协议。 ### 配置步骤 1. **通过menuconfig配置内核**: ```bash Device Drivers ---> [*] USB support ---> <*> USB Gadget Support ---> [*] USB functions configurable through configfs (CONFIG_USB_CONFIGFS) [*] RNDIS (CONFIG_USB_CONFIGFS_RNDIS) <*> Ethernet Gadget (CONFIG_USB_ETH) # 这个选项会包含RNDIS支持 [*] RNDIS support (CONFIG_USB_ETH_RNDIS) ``` 或者,如果你使用的是旧内核(没有ConfigFS),你可能需要选择: ```bash Device Drivers ---> [*] USB support ---> <*> USB Gadget Support ---> <*> Ethernet Gadget (with CDC Ethernet support) (CONFIG_USB_ETH) [*] RNDIS support (CONFIG_USB_ETH_RNDIS) ``` 2. **编译并安装内核**。 3. **使用ConfigFS配置RNDIS Gadget**(以ConfigFS方式为例): - 挂载configfs(如果尚未挂载): ```bash mount -t configfs none /sys/kernel/config ``` - 创建gadget目录: ```bash cd /sys/kernel/config/usb_gadget mkdir g1 cd g1 ``` - 设置USB Vendor ID和Product ID(例如,RNDIS通常使用0x1234和0x5678,但实际使用中应使用合法的ID): ```bash echo 0x1234 > idVendor echo 0x5678 > idProduct ``` - 创建配置: ```bash mkdir configs/c.1 ``` - 启用RNDIS功能: ```bash mkdir functions/rndis.usb0 ln -s functions/rndis.usb0 configs/c.1 ``` - 指定UDC控制器(需要根据你的硬件替换`dwc3.0.auto`): ```bash echo "dwc3.0.auto" > UDC ``` 4. **在主机上验证**:连接USB线到主机(如WindowsLinux主机),主机应该会识别到一个新的网络设备。 ### 注意事项 - **主机驱动**:在Windows主机上,通常需要安装RNDIS驱动Windows自带)。Linux主机需要启用`CONFIG_USB_NET_RNDIS_HOST`(如引用[1]所示)来支持作为主机连接RNDIS设备。 - **复合设备**:如果你需要同时启用多个功能(例如RNDIS和Mass Storage),可以通过ConfigFS创建多个功能并链接到同一个配置中。 ### 调试 如果设备无法被识别,可以检查: - 内核日志:`dmesg` 查看是否有错误信息。 - 确保UDC控制器驱动已加载并工作正常。 - 检查ConfigFS配置步骤是否正确。 ### 总结表格 | **配置选项** | **作用** | |-------------------------------|----------------------------------------| | `CONFIG_USB_GADGET` | 启用USB Gadget支持(必需) | | `CONFIG_USB_CONFIGFS` | 启用ConfigFS配置方式(推荐) | | `CONFIG_USB_CONFIGFS_RNDIS` | 在ConfigFS中启用RNDIS功能 | | `CONFIG_USB_ETH` | 启用USB以太网Gadget驱动(包含RNDIS) | | `CONFIG_USB_ETH_RNDIS` | 在USB以太网Gadget驱动中启用RNDIS协议 | §§相关问题§§ 1. 如何通过ConfigFS配置同时支持RNDIS和Mass Storage的复合USB设备? 2. 在Linux主机上如何加载RNDIS主机驱动以识别远程的RNDIS设备? 3. RNDIS协议与CDC-ECM协议在USB网络传输中有何性能差异? 4. 如何调试USB Gadget枚举失败的问题? [^1]: 在USB网络适配器的配置中,`CONFIG_USB_NET_RNDIS_HOST`用于主机端支持RNDIS设备。 [^2]: 在`f_mass_storage.c`中,`struct fsg_config`用于配置大容量存储Gadget,但本问题中未直接使用。</think>### RNDIS 内核配置详解 RNDIS(Remote Network Driver Interface Specification)是微软定义的 USB 网络协议标准,允许 USB 设备作为虚拟网卡使用。在 Linux 内核中,相关配置分为**主机端**(识别 RNDIS 设备)和**设备端**(模拟 RNDIS 网卡)两部分: --- #### 一、主机端配置(识别 RNDIS 设备) 当 Linux 作为**主机**时(如 PC 连接 Android 手机),需启用以下选项识别 RNDIS 设备: ```bash Device Drivers → [*] Network device support → [*] USB Network Adapters → <*> USB RNDIS Host support # CONFIG_USB_NET_RNDIS_HOST=y ``` ![内核配置路径示意图](https://example.com/rndis_host_config.png) > **关键说明**: > - 此配置使 Linux 主机能识别 Windows/Android 设备的 RNDIS 网络接口[^1] > - 依赖 `CONFIG_USB_USBNET`(通用 USB 网络框架) > - 连接后通过 `ifconfig -a` 可看到 `usb0` 或 `rndis0` 接口 --- #### 二、设备端配置(模拟 RNDIS 网卡) 当 Linux 设备作为 **USB 从设备**时(如嵌入式开发板),需通过 Gadget 框架模拟 RNDIS 网卡: ```bash Device Drivers → [*] USB support → <*> USB Gadget Support → [*] USB functions configurable through configfs # CONFIG_USB_CONFIGFS=y <*> Ethernet Gadget → # CONFIG_USB_ETH=y [*] RNDIS support # CONFIG_USB_ETH_RNDIS=y ``` > **关键说明**: > 1. **必须启用复合设备支持**(`CONFIG_USB_CONFIGFS`) > 2. RNDIS 协议实现位于 `drivers/usb/gadget/function/f_rndis.c` > 3. 需匹配 UDC 控制器驱动(如 `CONFIG_USB_DWC3`) --- #### 三、动态启用 RNDIS Gadget 通过 ConfigFS 动态配置(无需重新编译内核): ```bash # 挂载 configfs mount -t configfs none /sys/kernel/config # 创建 Gadget cd /sys/kernel/config/usb_gadget mkdir g1 && cd g1 # 设置设备描述符(VID/PID) echo 0x1d6b > idVendor # Linux Foundation echo 0x0104 > idProduct # Multifunction Composite Gadget # 创建 RNDIS 功能 mkdir functions/rndis.usb0 # 绑定到配置 mkdir configs/c.1 ln -s functions/rndis.usb0 configs/c.1 # 启用 UDC 控制器 echo "dwc3.0.auto" > UDC # 根据实际硬件修改 ``` 启用后主机将识别为 **RNDIS 兼容网卡**,可通过 `ip addr` 分配 IP[^2]。 --- #### 四、配置参数优化 在 `f_rndis.c` 中可调整关键参数: ```c // 控制 RNDIS 响应速度(单位:毫秒) module_param(rndis_response_delay, uint, 0644); MODULE_PARM_DESC(rndis_response_delay, "RNDIS response delay in ms"); // 设置最大传输单元(默认 1514 字节) static struct eth_dev_ops rndis_ops = { .get_drvinfo = rndis_get_drvinfo, .check_connect = rndis_check_connect, .mtu = 1514, // 可修改为 9000 支持巨帧 }; ``` --- #### 五、验证与调试 | **验证方法** | **命令/操作** | **预期结果** | |---------------------------|----------------------------------|----------------------------------| | 主机识别设备 | `lsusb -v` | 显示 `RNDIS` 设备类 | | 网络接口状态 | `ip link show dev usb0` | 状态为 `UP` | | 数据传输测试 | `ping <设备IP>` | 收到 ICMP 回复 | | 查看内核日志 | `dmesg \| grep rndis` | 无错误提示 | **常见问题解决**: - **主机无法识别**:检查 `idVendor/idProduct` 是否冲突 - **频繁断连**:增大 `rndis_response_delay` 值(如 `100ms`) - **低速传输**:确认 UDC 支持 USB2.0/3.0(`cat /sys/class/udc/*/speed`) --- ### 总结表格 | **配置类型** | **内核选项** | **作用** | |--------------------|----------------------------------|-----------------------------------| | 主机端支持 | `CONFIG_USB_NET_RNDIS_HOST` | 识别外部 RNDIS 设备 | | 设备端核心 | `CONFIG_USB_CONFIGFS` | 启用动态配置框架 | | RNDIS 协议实现 | `CONFIG_USB_ETH_RNDIS` | 提供 RNDIS 协议栈 | | 硬件控制器 | `CONFIG_USB_DWC3` (示例) | 驱动 USB 物理接口 |
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值