# TCP 和 UDP 支持

## TCP 和 UDP 支持

Spring 集成提供了用于通过 Internet 协议接收和发送消息的通道适配器。同时提供了 UDP（用户数据报协议）和 TCP（传输控制协议）适配器。每个适配器都提供了通过底层协议进行单向通信的功能。此外， Spring 集成提供了简单的入站和出站 TCP 网关。当需要双向交流时，就会使用这些工具。

你需要在项目中包含此依赖项：

Maven

```
<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-ip</artifactId>
    <version>5.5.9</version>
</dependency>
```

Gradle

```
compile "org.springframework.integration:spring-integration-ip:5.5.9"
```

### 引言

UDP 入站和出站通道适配器各提供两种类型：

* `UnicastSendingMessageHandler`将数据报包发送到单个目的地。

* `UnicastReceivingChannelAdapter`接收传入的数据报数据包。

* `MulticastSendingMessageHandler`将数据报数据包发送到多播地址。

* `MulticastReceivingChannelAdapter`通过连接到多播地址来接收传入的数据报数据包。

提供了 TCP 入站和出站通道适配器：

* `TcpSendingMessageHandler`通过 TCP 发送消息。

* `TcpReceivingChannelAdapter`通过 TCP 接收消息。

提供了一个入站 TCP 网关。它允许简单的请求-响应处理。虽然网关可以支持任意数量的连接，但每个连接只能进行串行处理。从套接字读取的线程在再次读取之前等待并发送响应。如果连接工厂被配置为一次性连接，则在套接字超时后将关闭连接。

提供了一个出站 TCP 网关。它允许简单的请求-响应处理。如果关联的连接工厂被配置为一次性连接，那么将立即为每个新请求创建一个新连接。否则，如果正在使用该连接，则调用线程将阻塞该连接，直到接收到响应或出现超时或 I/O 错误为止。

TCP 和 UDP 入站通道适配器以及 TCP 入站网关支持`error-channel`属性。这提供了与[Enter the`GatewayProxyFactoryBean`]（./gateway.html#gateway-proxy）中所描述的相同的基本功能。

### UDP 适配器

本节介绍如何配置和使用 UDP 适配器。

#### 出站 UDP 适配器（XML 配置）

以下示例配置了一个 UDP 出站通道适配器：

```
<int-ip:udp-outbound-channel-adapter id="udpOut"
    host="somehost"
    port="11111"
    multicast="false"
    socket-customizer="udpCustomizer"
    channel="exampleChannel"/>
```

|   |当将`multicast`设置为`true`时，还应该在 host 属性中提供多播地址。|
|---|--------------------------------------------------------------------------------------------------------|

UDP 是一种高效但不可靠的协议。 Spring 集成添加了两个属性来提高可靠性：`check-length`和`acknowledge`。当`check-length`设置为`true`时，适配器在消息数据之前使用一个长度字段（以网络字节顺序为四个字节）。这使得接收方能够验证接收到的分组的长度。如果接收系统使用的缓冲区太短，无法包含该数据包，则可以截断该数据包。`length`头提供了一种检测机制。

从版本 4.3 开始，你可以将`port`设置为`0`，在这种情况下，操作系统选择端口。选择的端口可以在适配器启动后调用`getPort()`来发现，并且`isListening()`返回`true`。

从版本 5.3.3 开始，你可以添加一个`SocketCustomizer` Bean 以在创建`DatagramSocket`后修改`DatagramSocket`（例如，调用`setTrafficClass(0x10)`）。

下面的示例显示了一个出站通道适配器，该适配器将长度检查添加到数据报数据包中：

```
<int-ip:udp-outbound-channel-adapter id="udpOut"
    host="somehost"
    port="11111"
    multicast="false"
    check-length="true"
    channel="exampleChannel"/>
```

|   |对于 Spring 集成 UDP 入站通道适配器，请设置其`check-length`属性。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

第二个可靠性改进允许使用应用程序级确认协议。接收方必须在指定的时间内向发送方发送确认信息。

下面的示例显示了一个出站通道适配器，该适配器将长度检查添加到数据报数据包中，并等待确认：

```
<int-ip:udp-outbound-channel-adapter id="udpOut"
    host="somehost"
    port="11111"
    multicast="false"
    check-length="true"
    acknowledge="true"
    ack-host="thishost"
    ack-port="22222"
    ack-timeout="10000"
    channel="exampleChannel"/>
```

|   |将`acknowledge`设置为`true`意味着数据包的接收者可以解释添加到包含确认数据（主机和端口）的数据包的头。<br/>最有可能的情况是，接收者是 Spring 集成入站通道适配器。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |当多播为真时，一个附加属性（`min-acks-for-success`）指定在`ack-timeout`内必须接收多少确认。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------|

从版本 4.3 开始，你可以将`ackPort`设置为`0`，在这种情况下，操作系统选择端口。

#### 出站 UDP 适配器（Java 配置）

下面的示例展示了如何使用 Java 配置出站 UDP 适配器：

```
@Bean
@ServiceActivator(inputChannel = "udpOut")
public UnicastSendingMessageHandler handler() {
    return new UnicastSendingMessageHandler("localhost", 11111);
}
```

（或`MulticastSendingChannelAdapter`表示多播）。

#### 出站 UDP 适配器（Java DSL 配置）

下面的示例展示了如何使用 Java DSL 配置出站 UDP 适配器：

```
@Bean
public IntegrationFlow udpOutFlow() {
    return f -> f.handle(Udp.outboundAdapter("localhost", 1234)
                    .configureSocket(socket -> socket.setTrafficClass(0x10)))
                .get();
}
```

#### 入站 UDP 适配器（XML 配置）

下面的示例展示了如何配置基本的单播入站 UDP 通道适配器。

```
<int-ip:udp-inbound-channel-adapter id="udpReceiver"
    channel="udpOutChannel"
    port="11111"
    receive-buffer-size="500"
    multicast="false"
    socket-customizer="udpCustomizer"
    check-length="true"/>
```

下面的示例展示了如何配置一个基本的多播入站 UDP 通道适配器：

```
<int-ip:udp-inbound-channel-adapter id="udpReceiver"
    channel="udpOutChannel"
    port="11111"
    receive-buffer-size="500"
    multicast="true"
    multicast-address="225.6.7.8"
    check-length="true"/>
```

默认情况下，对入站数据包进行反向 DNS 查找，以将 IP 地址转换为在消息头中使用的主机名。在没有配置 DNS 的环境中，这可能会导致延迟。可以通过将`lookup-host`属性设置为`false`来覆盖此默认行为。

从版本 5.3.3 开始，你可以添加一个`SocketCustomizer` Bean 来修改创建后的`DatagramSocket`。它被称为接收套接字和为发送 ACK 而创建的任何套接字。

#### 入站 UDP 适配器（Java 配置）

下面的示例展示了如何使用 Java 配置入站 UDP 适配器：

```
@Bean
public UnicastReceivingChannelAdapter udpIn() {
    UnicastReceivingChannelAdapter adapter = new UnicastReceivingChannelAdapter(11111);
    adapter.setOutputChannelName("udpChannel");
    return adapter;
}
```

下面的示例展示了如何使用 Java DSL 配置入站 UDP 适配器：

#### 入站 UDP 适配器（Java DSL 配置）

```
@Bean
public IntegrationFlow udpIn() {
    return IntegrationFlows.from(Udp.inboundAdapter(11111))
            .channel("udpChannel")
            .get();
}
```

#### 服务器监听事件

从版本 5.0.2 开始，当入站适配器启动并开始监听时，会发出`UdpServerListeningEvent`。当适配器被配置为侦听端口 0 时，这是有用的，这意味着操作系统选择端口。如果在启动将连接到套接字的其他进程之前需要等待，也可以使用它来代替轮询`isListening()`。

#### 高级出站配置

`<int-ip:udp-outbound-channel-adapter>`（`UnicastSendingMessageHandler`）具有`destination-expression`和`socket-expression`选项。

你可以使用`destination-expression`作为硬编码`host`的运行时替代项-`port`对，以针对`requestMessage`（具有用于计算上下文的根对象）确定传出数据报数据包的目标地址。表达式必须在 URI 样式中求值为`URI`、`String`（参见[RFC-2396](https://www.ietf.org/rfc/rfc2396.txt)）或`SocketAddress`。你也可以为这个表达式使用入站`IpHeaders.PACKET_ADDRESS`头。在框架中，当我们在`UnicastReceivingChannelAdapter`中接收数据报并将其转换为消息时，`DatagramPacketMessageMapper`填充此报头。标头值正是传入数据报`DatagramPacket.getSocketAddress()`的结果。

使用`socket-expression`，出站通道适配器可以使用（例如）入站通道适配器套接字通过接收到的相同端口发送数据报。在我们的应用程序作为 UDP 服务器工作，而客户机在网络地址转换之后进行操作的场景中，它是有用的。这个表达式必须求值为`DatagramSocket`。`requestMessage`用作求值上下文的根对象。不能将`socket-expression`参数与`multicast`和`acknowledge`参数一起使用。下面的示例展示了如何配置带有转换器的 UDP 入站通道适配器，该转换器将转换为大写并使用套接字：

```
<int-ip:udp-inbound-channel-adapter id="inbound" port="0" channel="in" />

<int:channel id="in" />

<int:transformer expression="new String(payload).toUpperCase()"
                       input-channel="in" output-channel="out"/>

<int:channel id="out" />

<int-ip:udp-outbound-channel-adapter id="outbound"
                        socket-expression="@inbound.socket"
                        destination-expression="headers['ip_packetAddress']"
                        channel="out" />
```

下面的示例展示了与 Java DSL 的等效配置：

```
@Bean
public IntegrationFlow udpEchoUpcaseServer() {
    return IntegrationFlows.from(Udp.inboundAdapter(11111).id("udpIn"))
            .<byte[], String>transform(p -> new String(p).toUpperCase())
            .handle(Udp.outboundAdapter("headers['ip_packetAddress']")
                    .socketExpression("@udpIn.socket"))
            .get();
}
```

### TCP 连接工厂

#### 概述

对于 TCP，底层连接的配置是通过使用连接工厂提供的。提供了两种类型的连接工厂：客户端连接工厂和服务器连接工厂。客户端连接工厂建立传出连接。服务器连接工厂监听传入的连接。

出站通道适配器使用客户端连接工厂，但你也可以向入站通道适配器提供对客户端连接工厂的引用。该适配器接收在出站适配器创建的连接上接收的任何传入消息。

入站通道适配器或网关使用服务器连接工厂。（事实上，没有连接工厂，连接工厂就无法正常工作）。你还可以向出站适配器提供对服务器连接工厂的引用。然后，你可以使用该适配器发送对同一连接上的传入消息的回复。

|   |只有当回复包含连接工厂插入到原始消息中的`ip_connectionId`头时，才将回复消息路由到连接。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |这是在入站和出站适配器之间共享连接工厂时执行的消息关联程度。<br/>这样的共享允许在 TCP 上进行异步双向通信。<br/>默认情况下，只有有效负载信息是使用 TCP 传输的。<br/>因此，任何消息关联都必须由下游组件（例如聚合器或其他端点）执行。<br/>版本 3.0 中引入了对传输选定头部的支持。<br/>有关更多信息，请参见[TCP 消息相关性](#ip-correlation)。|
|---| |

你可以为每种类型的适配器最多提供一个对连接工厂的引用。

Spring 集成提供了使用`java.net.Socket`和`java.nio.channel.SocketChannel`的连接工厂。

下面的示例展示了一个使用`java.net.Socket`连接的简单服务器连接工厂：

```
<int-ip:tcp-connection-factory id="server"
    type="server"
    port="1234"/>
```

下面的示例展示了一个使用`java.nio.channel.SocketChannel`连接的简单服务器连接工厂：

```
<int-ip:tcp-connection-factory id="server"
    type="server"
    port="1234"
    using-nio="true"/>
```

|   |从 Spring 集成版本 4.2 开始，如果服务器被配置为侦听一个随机端口（通过将端口设置为`0`），你可以通过使用`getPort()`获得操作系统选择的实际端口，<br/>也是如此，`getServerSocketAddress()`让你获得完整的`SocketAddress`。<br/>参见[Javadoc for`TcpServerConnectionFactory`接口]（https://DOCS. Spring.io/ Spring-integration/api/org/springframework/integration/ip/tcp/connection/tcpserverconnectionfactory.html）以获取更多信息。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

```
<int-ip:tcp-connection-factory id="client"
    type="client"
    host="localhost"
    port="1234"
    single-use="true"
    so-timeout="10000"/>
```

下面的示例展示了一个客户机连接工厂，该工厂使用`java.net.Socket`连接，并为每个消息创建一个新的连接：

```
<int-ip:tcp-connection-factory id="client"
    type="client"
    host="localhost"
    port="1234"
    single-use="true"
    so-timeout="10000"
    using-nio=true/>
```

从版本 5.2 开始，客户机连接工厂支持以秒为单位指定的属性`connectTimeout`，默认为 60。

另见[基于注释的配置](#ip-annotation)和[对 TCP 组件使用 Java DSL](#ip-dsl)。

#### 消息划分（序列化器和反序列化器）

TCP 是一种流协议。这意味着必须为通过 TCP 传输的数据提供某种结构，以便接收者可以将数据划分为离散的消息。连接工厂被配置为使用序列化器和反序列化器在消息有效负载和通过 TCP 发送的位之间进行转换。这是通过分别为入站消息和出站消息提供反序列化器和序列化器来实现的。 Spring 集成提供了许多标准序列化器和反序列化器。

`ByteArrayCrlfSerializer`<sup>*</sup>将字节数组转换为一个字节流，然后是回车和 LineFeed 字符（`\r\n`）。这是默认的序列化器（和反序列化器），可以（例如）将 Telnet 用作客户端。

`ByteArraySingleTerminatorSerializer`<sup>*</sup>将字节数组转换为一个字节流，后面跟着一个终止字符（默认值是`0x00`）。

`ByteArrayLfSerializer`<sup>*</sup>将字节数组转换为一个字节流，后面跟着一个 LineFeed 字符（`0x0a`）。

`ByteArrayStxEtxSerializer`<sup>*</sup>将字节数组转换为一个字节流，该字节流前跟一个 STX（`0x02`），后跟一个 ETX（`0x03`）。

`ByteArrayLengthHeaderSerializer`将一个字节数组转换为一个字节流，前跟一个二进制长度（按网络字节顺序）。这是一个有效的反序列化器，因为它不需要解析每个字节来查找终止字符序列。它也可以用于包含二进制数据的有效负载。前面的序列化器只支持有效负载中的文本。长度头的默认大小是 4 个字节（一个整数），允许最多（2^31-1）个字节的消息。但是，`length`头可以是最多 255 字节的消息的单字节（无符号），也可以是最多（2^16-1）字节的消息的无符号短（2 字节）。如果需要任何其他格式的头文件，则可以提供子类`ByteArrayLengthHeaderSerializer`并为`readHeader`和`writeHeader`方法提供实现。绝对最大数据大小为（2^31-1）字节。从版本 5.2 开始，头文件的值可以包括有效负载之外的头文件的长度。设置`inclusive`属性以启用该机制（对于生产者和消费者，必须将其设置为相同）。

`ByteArrayRawSerializer`<sup>*</sup>将字节数组转换为字节流，并且不添加额外的消息划分数据。有了这个序列化器（和反序列化器），消息的结束由客户端以有序的方式关闭套接字来表示。当使用此序列化器时，消息接收将挂起，直到客户端关闭套接字或发生超时为止。超时不会导致消息。当使用这个序列化器并且客户机是 Spring 集成应用程序时，客户机必须使用配置为`single-use="true"`的连接工厂。这样做会导致适配器在发送消息后关闭套接字。序列化器本身不会关闭连接。你应该只对通道适配器（而不是网关）使用的连接工厂使用这个序列化器，并且连接工厂应该由入站或出站适配器使用，而不是两者都使用。另见本节后面的`ByteArrayElasticRawDeserializer`。但是，从版本 5.2 开始，出站网关有一个新的属性`closeStreamAfterSend`；这允许使用原始序列化器/反序列化器，因为 EOF 是向服务器发送信号的，同时保持连接打开以接收答复。

|   |在版本 4.2.2 之前，当使用非阻塞 I/O（蔚来）时，这个序列化器将超时（在读取期间）视为文件的结束，到目前为止，读取的数据是作为消息发出的。，<br/>这是不可靠的，不应用于分隔消息。，<br/>它现在将这些条件视为异常，<br/>在不太可能的情况下，你以这种方式使用它，通过将`treatTimeoutAsEndOfMessage`构造函数参数设置为`true`，可以恢复以前的行为。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

每一个都是`AbstractByteArraySerializer`的子类，它实现了`org.springframework.core.serializer.Serializer`和`org.springframework.core.serializer.Deserializer`。对于向后兼容性，使用`AbstractByteArraySerializer`的任意子类进行序列化的连接也接受首先转换为字节数组的`String`。这些序列化器和反序列化器中的每一个都将包含相应格式的输入流转换为字节数组有效负载。

为了避免由于表现不佳的客户机（不遵守配置的序列化器的协议的客户机）而导致内存耗尽，这些序列化器设置了最大的消息大小。如果收到的消息超过此大小，将引发异常。默认的最大消息大小为 2048 字节。你可以通过设置`maxMessageSize`属性来增加它。如果使用默认的序列化器或反序列化器并希望增加最大消息大小，则必须使用`maxMessageSize`属性设置将最大消息大小声明为显式 Bean，并配置连接工厂使用该 Bean。

本节前面标有<sup>*</sup>的类使用中间缓冲区，并将解码后的数据复制到最终大小正确的缓冲区。从版本 4.3 开始，你可以通过设置`poolSize`属性来配置这些缓冲区，以使这些原始缓冲区可以重用，而不是为每个消息分配和丢弃这些缓冲区，这是默认的行为。将属性设置为负值将创建一个没有限制的池。如果池是有界的，你还可以设置`poolWaitTimeout`属性（以毫秒为单位），在此之后，如果没有可用的缓冲区，将抛出异常。它默认值为无穷大。这样的异常会导致插座关闭。

如果希望在自定义反序列化器中使用相同的机制，则可以扩展`AbstractPooledBufferByteArraySerializer`（而不是它的超类，`AbstractByteArraySerializer`）并实现`doDeserialize()`而不是`deserialize()`。缓冲区将自动返回到池中。`AbstractPooledBufferByteArraySerializer`还提供了一种方便的实用方法：`copyToSizedArray()`。

5.0 版本增加了`ByteArrayElasticRawDeserializer`。这类似于上面`ByteArrayRawSerializer`的反序列化器端，只是不需要设置`maxMessageSize`。在内部，它使用一个`ByteArrayOutputStream`，允许缓冲区根据需要进行增长。客户端必须有序地关闭套接字，以表示消息结束.

|   |此反序列化器仅应在对等方受信任时使用；由于内存不足，它容易受到 DOS 附加的影响。|
|---|----------------------------------------------------------------------------------------------------------------------------------|

`MapJsonSerializer`使用 Jackson`ObjectMapper`在`Map`和 JSON 之间进行转换。你可以将这个序列化器与`MessageConvertingTcpMessageMapper`和`MapMessageConverter`一起使用，以在 JSON 中传输选定的头和有效负载。

|   |Jackson`ObjectMapper`不能在流中对消息进行划分。<br/>因此，`MapJsonSerializer`需要委托给另一个序列化器或反序列化器来处理消息划分。<br/>默认情况下，使用`ByteArrayLfSerializer`，导致消息的格式为`<json><LF>`，但是，你可以将它配置为使用其他工具。<br/>（下一个示例演示如何这样做。）|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

最终的标准序列化器是`org.springframework.core.serializer.DefaultSerializer`，你可以使用它来使用 Java 序列化转换可序列化对象。`org.springframework.core.serializer.DefaultDeserializer`用于包含可序列化对象的流的入站反序列化。

如果不希望使用默认的序列化器和反序列化器（`ByteArrayCrLfSerializer`），则必须在连接工厂上设置`serializer`和`deserializer`属性。下面的示例展示了如何做到这一点：

```
<bean id="javaSerializer"
      class="org.springframework.core.serializer.DefaultSerializer" />
<bean id="javaDeserializer"
      class="org.springframework.core.serializer.DefaultDeserializer" />

<int-ip:tcp-connection-factory id="server"
    type="server"
    port="1234"
    deserializer="javaDeserializer"
    serializer="javaSerializer"/>
```

一个服务器连接工厂，它使用`java.net.Socket`连接并在有线上使用 Java 序列化。

有关连接工厂上可用的属性的详细信息，请参见本节末尾的[参考文献](#ip-annotation)。

默认情况下，对入站数据包进行反向 DNS 查找，以将 IP 地址转换为在消息头中使用的主机名。在没有配置 DNS 的环境中，这可能会导致连接延迟。你可以通过将`lookup-host`属性设置为`false`来覆盖此默认行为。

|   |你还可以修改套接字和套接字工厂的属性。<br/>有关更多信息，请参见[SSL/TLS 支持](#ssl-tls)。<br/>如前所述，无论是否使用 SSL，这种修改都是可能的。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

另见[基于注释的配置](#ip-annotation)和[对 TCP 组件使用 Java DSL](#ip-dsl)。

#### 自定义序列化器和反序列化器

如果你的数据不是标准反序列化器之一支持的格式，那么你可以实现自己的；还可以实现自定义序列化器。

要实现自定义序列化器和反序列化器对，请实现`org.springframework.core.serializer.Deserializer`和`org.springframework.core.serializer.Serializer`接口。

当反序列化器检测到消息之间的闭合输入流时，它必须抛出`SoftEndOfStreamException`；这是向框架发出的信号，以表明闭合是“正常的”。如果在解码消息时流已关闭，则应该抛出其他一些异常。

从版本 5.2 开始，`SoftEndOfStreamException`现在是`RuntimeException`，而不是扩展`IOException`。

#### TCP 缓存客户端连接工厂

由于[早些时候注意到](#ip-intro)，TCP 套接字可以是“一次性”（一个请求或响应）或共享的。在大容量环境中，共享套接字在出站网关中的性能不佳，因为该套接字一次只能处理一个请求或响应。

为了提高性能，你可以使用协作通道适配器而不是网关，但这需要应用程序级消息相关性。有关更多信息，请参见[TCP 消息相关性](#ip-correlation)。

Spring Integration2.2 引入了一种缓存客户端连接工厂，它使用共享套接字池，让网关通过共享连接池处理多个并发请求。

#### TCP 故障转移客户端连接工厂

你可以配置一个 TCP 连接工厂，该工厂支持对一个或多个其他服务器的故障转移。在发送消息时，工厂会对其所有配置的工厂进行迭代，直到可以发送消息或找不到连接为止。最初，使用配置列表中的第一个工厂。如果连接随后失败，则下一个工厂将成为当前工厂。下面的示例展示了如何配置故障转移客户机连接工厂：

```
<bean id="failCF" class="o.s.i.ip.tcp.connection.FailoverClientConnectionFactory">
    <constructor-arg>
        <list>
            <ref bean="clientFactory1"/>
            <ref bean="clientFactory2"/>
        </list>
    </constructor-arg>
</bean>
```

|   |当使用故障转移连接工厂时，`singleUse`属性必须在工厂本身和配置为使用的工厂列表之间保持一致。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|

当与共享连接（`singleUse=false`）一起使用时，连接工厂有两个与失败返回相关的属性：

* `refreshSharedInterval`

* `closeOnRefresh`

考虑基于上述配置的以下场景：假设`clientFactory1`不能建立连接，但`clientFactory2`可以。当`failCF``getConnection()`方法在`refreshSharedInterval`通过后被调用时，我们将再次尝试使用`clientFactory1`进行连接；如果成功，将关闭与`clientFactory2`的连接。如果`closeOnRefresh`是`false`，则“旧”连接将保持打开状态，并且如果第一个工厂再次发生故障，则将来可能会重用该连接。

将`refreshSharedInterval`设置为仅在该时间过期后尝试与第一个工厂重新连接；如果只希望在当前连接失败时返回第一个工厂，则将其设置为`Long.MAX_VALUE`（默认）。

设置`closeOnRefresh`以在刷新实际创建新连接后关闭“旧”连接。

|   |如果任何委托工厂是`CachingClientConnectionFactory`，则这些属性不适用，因为连接缓存是在这里处理的；在这种情况下，将始终参考连接工厂列表以获得连接。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

从版本 5.3 开始，这些默认值为`Long.MAX_VALUE`和`true`，因此工厂仅在当前连接失败时尝试失败返回。要恢复到以前版本的默认行为，请将它们设置为`0`和`false`。

另见[测试连接](#testing-connections)。

#### TCP 线程关联连接工厂

Spring 集成版本 5.0 介绍了这种连接工厂。它将连接绑定到调用线程，并且每次线程发送消息时都会重用相同的连接。这将一直持续到连接被关闭（由服务器或网络关闭），或者直到线程调用`releaseConnection()`方法。连接本身由另一个客户端工厂实现提供，必须将其配置为提供非共享（一次性使用）连接，以便每个线程获得一个连接。

下面的示例展示了如何配置 TCP 线程关联连接工厂：

```
@Bean
public TcpNetClientConnectionFactory cf() {
    TcpNetClientConnectionFactory cf = new TcpNetClientConnectionFactory("localhost",
            Integer.parseInt(System.getProperty(PORT)));
    cf.setSingleUse(true);
    return cf;
}

@Bean
public ThreadAffinityClientConnectionFactory tacf() {
    return new ThreadAffinityClientConnectionFactory(cf());
}

@Bean
@ServiceActivator(inputChannel = "out")
public TcpOutboundGateway outGate() {
    TcpOutboundGateway outGate = new TcpOutboundGateway();
    outGate.setConnectionFactory(tacf());
    outGate.setReplyChannelName("toString");
    return outGate;
}
```

### 测试连接

在某些情况下，在连接首次打开时发送某种健康检查请求可能是有用的。一个这样的场景可能是当使用[TCP 故障转移客户端连接工厂](#failover-cf)时，如果所选的服务器允许打开连接但报告它不健康，那么我们可能会失败。

为了支持此功能，可以向客户端连接工厂添加`connectionTest`。

```
/**
 * Set a {@link Predicate} that will be invoked to test a new connection; return true
 * to accept the connection, false the reject.
 * @param connectionTest the predicate.
 * @since 5.3
 */
public void setConnectionTest(@Nullable Predicate<TcpConnectionSupport> connectionTest) {
    this.connectionTest = connectionTest;
}
```

要测试连接，请将一个临时侦听器附加到测试中的连接上。如果测试失败，将关闭连接并引发异常。当与[TCP 故障转移客户端连接工厂](#failover-cf)一起使用时，会触发尝试下一台服务器。

|   |只有来自服务器的第一个答复将被发送到测试侦听器。|
|---|------------------------------------------------------------------|

在下面的示例中，如果服务器在发送`PING`时回复`PONG`，则认为服务器是健康的。

```
Message<String> ping = new GenericMessage<>("PING");
byte[] pong = "PONG".getBytes();
clientFactory.setConnectionTest(conn -> {
    CountDownLatch latch = new CountDownLatch(1);
    AtomicBoolean result = new AtomicBoolean();
    conn.registerTestListener(msg -> {
        if (Arrays.equals(pong, (byte[]) msg.getPayload())) {
            result.set(true);
        }
        latch.countDown();
        return false;
    });
    conn.send(ping);
    try {
        latch.await(10, TimeUnit.SECONDS);
    }
    catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
    return result.get();
});
```

### TCP 连接拦截器

你可以使用对`TcpConnectionInterceptorFactoryChain`的引用来配置连接工厂。你可以使用拦截器将行为添加到连接中，例如协商、安全性和其他选项。该框架目前没有提供拦截器，但参见[`InterceptedSharedConnectionTests`在源库中]（https://github.com/ Spring-projects/ Spring-integration/blob/main/ Spring-integration-ip/SRC/test/java/org/springframework/integration/ip/tcp/tcp/interceptedsharedconnectiontests.java）中的一个示例。

测试用例中使用的`HelloWorldInterceptor`工作方式如下：

拦截器首先配置一个客户端连接工厂。当通过被拦截的连接发送第一条消息时，拦截器通过该连接发送“Hello”，并期望接收“World！”。当这种情况发生时，谈判就完成了，原始消息就被发送了。使用相同连接的其他消息不需要任何额外的协商就可以发送。

当配置服务器连接工厂时，拦截器要求第一条消息为“Hello”，如果是，则返回“world！”。否则，它将抛出一个导致连接关闭的异常。

所有`TcpConnection`方法都被截获。拦截器工厂为每个连接创建拦截器实例。如果拦截器是有状态的，那么工厂应该为每个连接创建一个新实例。如果没有状态，则相同的拦截器可以包装每个连接。拦截器工厂被添加到拦截器工厂链的配置中，你可以通过设置`interceptor-factory`属性将其提供给连接工厂。拦截器必须扩展`TcpConnectionInterceptorSupport`。工厂必须实现`TcpConnectionInterceptorFactory`接口。`TcpConnectionInterceptorSupport`具有 passthrough 方法。通过扩展这个类，你只需要实现你希望截获的那些方法。

下面的示例展示了如何配置连接拦截器工厂链：

```
<bean id="helloWorldInterceptorFactory"
    class="o.s.i.ip.tcp.connection.TcpConnectionInterceptorFactoryChain">
    <property name="interceptors">
        <array>
            <bean class="o.s.i.ip.tcp.connection.HelloWorldInterceptorFactory"/>
        </array>
    </property>
</bean>

<int-ip:tcp-connection-factory id="server"
    type="server"
    port="12345"
    using-nio="true"
    single-use="true"
    interceptor-factory-chain="helloWorldInterceptorFactory"/>

<int-ip:tcp-connection-factory id="client"
    type="client"
    host="localhost"
    port="12345"
    single-use="true"
    so-timeout="100000"
    using-nio="true"
    interceptor-factory-chain="helloWorldInterceptorFactory"/>
```

### TCP 连接事件

从版本 3.0 开始，对`TcpConnection`实例的更改由`TcpConnectionEvent`实例报告。`TcpConnectionEvent`是`ApplicationEvent`的一个子类，因此可以由`ApplicationListener`中定义的任何`ApplicationListener`接收——例如[事件入站通道适配器](./event.html#appevent-inbound)。

`TcpConnectionEvents`具有以下属性：

* `connectionId`:连接标识符，你可以在消息头中使用它将数据发送到连接。

* `connectionFactoryName`:连接所属的连接工厂的 Bean 名称。

* `throwable`:`Throwable`（仅用于`TcpConnectionExceptionEvent`事件）。

* `source`:the`TcpConnection`。例如，你可以使用它来确定带有`getHostAddress()`的远程 IP 地址（需要强制转换）。

此外，自版本 4.0 以来，在[TCP 连接工厂](#tcp-connection-factories)中讨论的标准反序列化器现在在对数据流进行解码时遇到问题时发出`TcpDeserializationExceptionEvent`实例。这些事件包含异常、正在构建过程中的缓冲区，以及发生异常时缓冲区中的偏移量（如果可用）。应用程序可以使用正常的`ApplicationListener`或`ApplicationEventListeningMessageProducer`（参见[Receiving Spring Application Events](./event.html#appevent-inbound)）来捕获这些事件，从而允许对问题进行分析。

从版本 4.0.7 和 4.1.3 开始，每当服务器套接字上出现意外异常（例如在使用服务器套接字时出现`BindException`）时，都会发布`TcpConnectionServerExceptionEvent`实例。这些事件具有对连接工厂和原因的引用。

从版本 4.2 开始，每当端点（入站网关或协作出站通道适配器）接收到由于`ip_connectionId`报头无效而无法路由到连接的消息时，都会发布`TcpConnectionFailedCorrelationEvent`实例。出站网关还会在收到延迟回复（发送方线程已超时）时发布此事件。该事件包含连接 ID 以及`cause`属性中的异常，其中包含失败的消息。

从版本 4.3 开始，在启动服务器连接工厂时会发出`TcpConnectionServerListeningEvent`。当工厂被配置为侦听端口 0 时，这是有用的，这意味着操作系统选择端口。如果在启动连接到套接字的其他进程之前需要等待，也可以使用它来代替轮询`isListening()`。

|   |为了避免延迟监听线程接受连接，该事件将在单独的线程上发布。|
|---|---------------------------------------------------------------------------------------------------------------|

从版本 4.3.2 开始，每当无法创建客户端连接时，都会发出`TcpConnectionFailedEvent`。事件的源是连接工厂，你可以使用它来确定无法建立连接的主机和端口。

### TCP 适配器

提供了使用连接工厂[前面提到过](#tcp-events)的 TCP 入站和出站通道适配器。这些适配器有两个相关的属性：`connection-factory`和`channel`。`connection-factory`属性指示将使用哪个连接工厂来管理适配器的连接。`channel`属性指定消息到达出站适配器的通道，以及由入站适配器放置的消息的通道。虽然入站和出站适配器可以共享一个连接工厂，但服务器连接工厂总是由入站适配器“拥有”的。客户端连接工厂总是由出站适配器“拥有”的。每种类型中只有一个适配器可以获得对连接工厂的引用。下面的示例展示了如何定义客户机和服务器 TCP 连接工厂：

```
<bean id="javaSerializer"
      class="org.springframework.core.serializer.DefaultSerializer"/>
<bean id="javaDeserializer"
      class="org.springframework.core.serializer.DefaultDeserializer"/>

<int-ip:tcp-connection-factory id="server"
    type="server"
    port="1234"
    deserializer="javaDeserializer"
    serializer="javaSerializer"
    using-nio="true"
    single-use="true"/>

<int-ip:tcp-connection-factory id="client"
    type="client"
    host="localhost"
    port="#{server.port}"
    single-use="true"
    so-timeout="10000"
    deserializer="javaDeserializer"
    serializer="javaSerializer"/>

<int:channel id="input" />

<int:channel id="replies">
    <int:queue/>
</int:channel>

<int-ip:tcp-outbound-channel-adapter id="outboundClient"
    channel="input"
    connection-factory="client"/>

<int-ip:tcp-inbound-channel-adapter id="inboundClient"
    channel="replies"
    connection-factory="client"/>

<int-ip:tcp-inbound-channel-adapter id="inboundServer"
    channel="loop"
    connection-factory="server"/>

<int-ip:tcp-outbound-channel-adapter id="outboundServer"
    channel="loop"
    connection-factory="server"/>

<int:channel id="loop"/>
```

另见[基于注释的配置](#ip-annotation)和[对 TCP 组件使用 Java DSL](#ip-dsl)。

在前面的配置中，到达`input`通道的消息通过`client`连接工厂创建的连接进行序列化，在服务器上接收，并放置在`loop`通道上。由于`loop`是`outboundServer`的输入通道，因此消息将在相同的连接上循环回来，由`inboundClient`接收，并保存在`replies`通道中。Java 序列化是在有线上使用的。

通常，入站适配器使用`type="server"`连接工厂，该工厂监听传入的连接请求。在某些情况下，你可能希望反向建立连接，使得入站适配器连接到外部服务器，然后等待该连接上的入站消息。

通过在入站适配器上设置`client-mode="true"`来支持此拓扑结构。在这种情况下，连接工厂的类型必须是`client`，并且必须将`single-use`设置为`false`。

另外两个属性支持此机制。`retry-interval`指定（以毫秒为单位）框架在连接失败后尝试重新连接的频率。`scheduler`提供了一个`TaskScheduler`来调度连接尝试并测试连接是否仍然处于活动状态。

如果不提供调度程序，则使用框架的默认[任务调度程序](./configuration.html#namespace-taskscheduler) Bean。

对于出站适配器，通常在发送第一条消息时建立连接。出站适配器上的`client-mode="true"`导致在适配器启动时建立连接。默认情况下，适配器会自动启动。同样，连接工厂必须是类型`client`并且具有`single-use="false"`。还支持`retry-interval`和`scheduler`。如果连接失败，则调度程序或在发送下一条消息时重新建立该连接。

对于入站和出站，如果适配器已启动，则可以通过发送`<control-bus />`命令：`@adapter_id.retryConnection()`来强制适配器建立连接。然后，你可以使用`@adapter_id.isClientModeConnected()`检查当前状态。

### TCP 网关

入站 TCP 网关`TcpInboundGateway`和出站 TCP 网关`TcpOutboundGateway`分别使用服务器和客户端连接工厂。每个连接一次可以处理一个请求或响应。

入站网关在使用传入的有效负载构造消息并将其发送到`requestChannel`之后，将等待响应，并通过将响应消息写入连接来从响应消息发送有效负载。

|   |对于入站网关，你必须保留或填充`ip_connectionId`头，因为它用于将消息与连接关联起来。<br/>发端于网关的消息自动具有该头集合。<br/>如果回复被构造为新消息，你需要设置消息头。<br/>可以从传入的消息中捕获消息头的值。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

与入站适配器一样，入站网关通常使用`type="server"`连接工厂，该工厂监听传入的连接请求。在某些情况下，你可能希望反向建立连接，使得入站网关连接到外部服务器，然后等待并回复该连接上的入站消息。

在入站网关上使用`client-mode="true"`可以支持此拓扑结构。在这种情况下，连接工厂的类型必须是`client`，并且必须将`single-use`设置为`false`。

另外两个属性支持此机制。`retry-interval`指定（以毫秒为单位）框架在连接失败后尝试重新连接的频率。`scheduler`提供`TaskScheduler`以调度连接尝试并测试连接是否仍然处于活动状态。

如果网关被启动，你可以通过发送`<control-bus/>`命令：`@adapter_id.retryConnection()`来强制网关建立连接，并用`@adapter_id.isClientModeConnected()`检查当前状态。

出站网关在通过连接发送消息后，等待响应，构造响应消息，并将其放在响应通道上。连接上的通信是单线程的。一次只能处理一条消息。如果另一个线程试图在接收到当前响应之前发送消息，则它将阻塞，直到之前的任何请求完成（或超时）。但是，如果客户机连接工厂被配置为一次性连接，那么每个新请求都会获得自己的连接，并立即进行处理。以下示例配置了入站 TCP 网关：

```
<int-ip:tcp-inbound-gateway id="inGateway"
    request-channel="tcpChannel"
    reply-channel="replyChannel"
    connection-factory="cfServer"
    reply-timeout="10000"/>
```

如果使用了配置有默认序列化器或反序列化器的连接工厂，则消息是`\r\n`分隔的数据，网关可以由简单的客户端（例如 Telnet）使用。

下面的示例展示了一个出站 TCP 网关：

```
<int-ip:tcp-outbound-gateway id="outGateway"
    request-channel="tcpChannel"
    reply-channel="replyChannel"
    connection-factory="cfClient"
    request-timeout="10000"
    remote-timeout="10000"/> <!-- or e.g.
remote-timeout-expression="headers['timeout']" -->
```

`client-mode`当前不能与出站网关一起使用。

从版本 5.2 开始，出站网关可以配置为`closeStreamAfterSend`属性。如果连接工厂被配置为`single-use`（每个请求/回复都有一个新的连接），网关将关闭输出流；这向服务器发出 EOF 信号。如果服务器使用 EOF 来确定消息的结束，而不是流中的某个分隔符，但为了接收答复而保持连接打开，那么这将非常有用。

通常情况下，调用线程会在网关中阻塞，等待响应（或超时）。从版本 5.3 开始，你可以在网关上设置`async`属性，并释放发送线程以执行其他工作。回复（或错误）将在接收线程上发送。这仅在使用`TcpNetClientConnectionFactory`时适用，在使用蔚来时忽略它，因为存在一个竞争条件，即在接收到答复之后发生的套接字错误可以在答复之前传递到网关。

|   |当使用一个共享连接（`singleUse=false`）时，当另一个正在处理中时，一个新的请求将被阻止，直到收到当前的答复。<br/>如果你希望在一个长寿命连接池上支持并发请求，请考虑使用`CachingClientConnectionFactory`。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

从版本 5.4 开始，入站可以配置为`unsolicitedMessageChannel`。未经请求的入站消息将被发送到此通道，以及延迟的回复（其中客户端超时）。为了在服务器端支持这一点，你现在可以向连接工厂注册多个`TcpSender`s。网关和通道适配器自动注册自己。当从服务器发送未经请求的消息时，你必须将适当的`IpHeaders.CONNECTION_ID`添加到所发送的消息中。

另见[基于注释的配置](#ip-annotation)和[对 TCP 组件使用 Java DSL](#ip-dsl)。

### TCP 消息相关性

IP 端点的一个目标是提供与 Spring 集成应用程序以外的系统的通信。由于这个原因，默认情况下只发送和接收消息有效负载。自 3.0 以来，你可以通过使用 JSON、Java 序列化或自定义序列化器和反序列化器来传输头文件。有关更多信息，请参见[传输头](#ip-headers)。框架（除了使用网关时）或服务器端的协作通道适配器不提供消息相关性。[在本文的后面部分](#ip-collaborating-adapters)，我们讨论了可用于应用程序的各种相关技术。在大多数情况下，这需要消息的特定应用程序级相关性，即使消息有效负载包含一些自然的相关性数据（例如订单号）。

#### 网关

网关自动关联消息。但是，对于相对小容量的应用程序，应该使用出站网关。当你将连接工厂配置为对所有消息对使用单个共享连接时（“single-use=“false”"），一次只能处理一个消息。新消息必须等待，直到收到对上一条消息的答复。当连接工厂为每个新消息配置为使用一个新连接（“single-use=“true”"）时，此限制不适用。虽然此设置可以提供比共享连接环境更高的吞吐量，但它会带来为每个消息对打开和关闭新连接的开销。

因此，对于大容量的消息，可以考虑使用一对协作的通道适配器。然而，要做到这一点，你需要提供协作逻辑。

Spring Integration2.2 中介绍的另一种解决方案是使用`CachingClientConnectionFactory`，它允许使用共享连接池。

#### 协作出站和入站通道适配器

为了实现大容量的吞吐量（避免使用网关的陷阱，如[前面提到过](#ip-gateways)），你可以配置一对协作的出站和入站通道适配器。你还可以使用协作适配器（服务器端或客户端）进行完全异步通信（而不是使用请求-回复语义）。在服务器端，消息相关性由适配器自动处理，因为入站适配器添加了一个头，使出站适配器能够确定在发送回复消息时使用哪个连接。

|   |在服务器端，你必须填充`ip_connectionId`头，因为它是用来将消息与连接关联起来的。<br/>发端于入站适配器的消息自动具有该头集。<br/>如果你希望构造要发送的其他消息，你需要设置消息头。<br/>你可以从传入的消息中获得消息头的值。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

在客户端，如果需要，应用程序必须提供自己的相关逻辑。你可以通过多种方式做到这一点。

如果消息有效负载具有一些自然的相关数据（例如事务 ID 或订单号），并且你不需要保留原始出站消息中的任何信息（例如回复通道标头），这种关联很简单，在任何情况下都可以在应用程序级别完成。

如果消息有效负载具有一些自然的相关数据（例如事务 ID 或订单号），但需要保留原始出站消息中的一些信息（例如回复通道头），你可以保留原始出站消息的副本（可能通过使用发布-订阅通道），并使用聚合器重新组合必要的数据。

对于前两种情况中的任何一种，如果有效负载没有自然的相关数据，则可以在出站通道适配器的上游提供一个转换器，以用这样的数据增强有效负载。这样的转换器可以将原始有效负载转换为一个新对象，该对象既包含原始有效负载，也包含消息头的某些子集。当然，头中的活动对象（例如应答通道）不能包含在转换后的有效负载中。

如果选择这样的策略，则需要确保连接工厂具有适当的序列化器-反序列化器对来处理这样的有效负载（例如`DefaultSerializer`和`DefaultDeserializer`，它们使用 Java 序列化，或自定义序列化器和反序列化器）。[TCP 连接工厂](#tcp-connection-factories)中提到的`ByteArray*Serializer`选项，包括默认的`ByteArrayCrLfSerializer`，不支持这样的有效载荷，除非转换后的有效载荷是`String`或`byte[]`。

|   |在 2.2 版本发布之前，当协作通道适配器使用客户端连接工厂时，`so-timeout`属性默认为默认的回复超时（10 秒）。<br/>这意味着，如果入站适配器在这段时间内没有接收到任何数据，套接字已关闭。<br/><br/>这种默认行为在真正的异步环境中是不合适的，因此它现在默认为无限超时。<br/>你可以通过将客户端连接工厂上的`so-timeout`属性设置为 10000 毫秒来恢复以前的默认行为。|
|---| |

从版本 5.4 开始，多个出站通道适配器和一个`TcpInboundChannelAdapter`可以共享相同的连接工厂。这允许应用程序同时支持请求/回复和任意服务器客户端消息传递。有关更多信息，请参见[TCP 网关](#tcp-gateways)。

#### 传输头文件

TCP 是一种流协议。`Serializers`和`Deserializers`在流内划分消息。在 3.0 之前，只有消息有效负载（`String`或`byte[]`）可以通过 TCP 传输。从 3.0 开始，你可以传输选定的报头以及有效负载。但是，“live”对象，例如`replyChannel`头，不能序列化。

通过 TCP 发送头信息需要一些额外的配置。

第一步是为`ConnectionFactory`提供使用`mapper`属性的`MessageConvertingTcpMessageMapper`。这个映射器委托给任何`MessageConverter`实现来转换消息到和来自某个对象，这些对象可以通过配置的`serializer`和`deserializer`进行序列化和反序列化。

Spring 集成提供了`MapMessageConverter`，其允许对与有效负载一起添加到`Map`对象的标题列表进行规范。生成的映射有两个条目：`payload`和`headers`。`headers`条目本身是`Map`，并包含所选的标题。

第二步是提供一个序列化器和一个反序列化器，它们可以在`Map`和某些线格式之间进行转换。这可以是自定义的`Serializer`或`Deserializer`，如果对等系统不是 Spring 集成应用程序，则通常需要它。

Spring 集成提供了一个`MapJsonSerializer`来将一个`Map`转换为和从 JSON 转换。它使用 Spring 积分`JsonObjectMapper`。如果需要，可以提供自定义`JsonObjectMapper`。默认情况下，序列化器在对象之间插入一个 LineFeed（`0x0a`）字符。有关更多信息，请参见[Javadoc](https://docs.spring.io/spring-integration/api/org/springframework/integration/ip/tcp/serializer/MapJsonSerializer.html)。

|   |`JsonObjectMapper`使用 Classpath 上的`Jackson`的任何版本。|
|---|-------------------------------------------------------------------------------|

你还可以使用`Map`和`DefaultDeserializer`来使用`Map`的标准 Java 序列化。

下面的示例显示了一个连接工厂的配置，该工厂使用 JSON 传输`correlationId`、`sequenceNumber`和`sequenceSize`头：

```
<int-ip:tcp-connection-factory id="client"
    type="client"
    host="localhost"
    port="12345"
    mapper="mapper"
    serializer="jsonSerializer"
    deserializer="jsonSerializer"/>

<bean id="mapper"
      class="o.sf.integration.ip.tcp.connection.MessageConvertingTcpMessageMapper">
    <constructor-arg name="messageConverter">
        <bean class="o.sf.integration.support.converter.MapMessageConverter">
            <property name="headerNames">
                <list>
                    <value>correlationId</value>
                    <value>sequenceNumber</value>
                    <value>sequenceSize</value>
                </list>
            </property>
        </bean>
    </constructor-arg>
</bean>

<bean id="jsonSerializer" class="o.sf.integration.ip.tcp.serializer.MapJsonSerializer" />
```

使用前面的配置发送的消息，其有效负载为“Something”，该消息将显示在连接上，如下所示：

```
{"headers":{"correlationId":"things","sequenceSize":5,"sequenceNumber":1},"payload":"something"}
```

### 关于非阻塞 I/O（蔚来）

使用蔚来（参见`using-nio`中的[IP 配置属性](#ip-endpoint-reference)）可以避免指定从每个套接字读取的线程。对于少量的套接字，你可能会发现，不使用蔚来，再加上异步切换（例如到`QueueChannel`），其性能与使用蔚来相同或更好。

在处理大量连接时，应该考虑使用蔚来。然而，蔚来的使用还有其他一些影响。一个线程池（在任务执行器中）在所有套接字中共享。每个传入消息都被组装并作为从该池中选择的线程上的一个单独的工作单元发送到配置的通道。到达同一套接字的两条连续消息可能由不同的线程处理。这意味着消息发送到通道的顺序是不确定的。没有对到达套接字的消息进行严格的排序。

对于某些应用程序来说，这不是问题。对其他人来说，这是一个问题。如果需要严格的排序，可以考虑将`using-nio`设置为`false`，并使用异步切换。

或者，你可以在入站端点的下游插入一个重新排序程序，以将消息返回到正确的序列。如果在连接工厂上将`apply-sequence`设置为`true`，则到达 TCP 连接的消息将设置`sequenceNumber`和`correlationId`头。重序器使用这些头来将消息返回到正确的序列。

|   |从版本 5.1.4 开始，优先考虑接受新的连接，而不是从现有的连接中读取，<br/>通常，这应该没有什么影响，除非你有非常高的新进入连接的速率，<br/>如果你希望恢复到以前的行为，给予读取优先权，将`TcpNioServerConnectionFactory`上的`multiAccept`属性设置为`false`。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 池大小

不再使用池大小属性。以前，当未指定任务执行器时，它会指定默认线程池的大小。它还用于设置服务器套接字上的连接待办事项。第一个功能不再需要了（见下一段）。第二个函数被`backlog`属性代替。

以前，当使用带有蔚来的固定线程池任务执行器（这是默认的）时，可能会出现死锁并停止处理。当缓冲区已满，从套接字读取的线程试图向缓冲区添加更多数据，并且没有可用的线程在缓冲区中腾出空间时，就会出现问题。这只发生在一个非常小的池规模，但它可能是在极端条件下。自 2.2 以来，两次更改消除了这个问题。首先，默认的任务执行器是一个缓存的线程池执行器。其次，添加了死锁检测逻辑，这样，如果发生线程饥饿，将抛出一个异常，而不是死锁，从而释放死锁资源。

|   |既然默认的任务执行器是无界的，如果消息处理需要较长的时间，那么在传入消息的速率很高的情况下，可能会出现内存不足的情况，<br/>如果你的应用程序表现出这种类型的行为，则应该使用池大小适当的池任务执行器，但见[下一节](#io-thread-pool-task-executor-caller-runs)。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 具有`CALLER_RUNS`策略的线程池任务执行器

当使用带有`CallerRunsPolicy`（`CALLER_RUNS`使用`<task/>`命名空间时）的固定线程池并且队列容量很小时，应该记住一些重要的考虑因素。

如果不使用固定线程池，则以下内容不适用。

对于蔚来连接，有三种不同的任务类型。I/O 选择器处理在一个专用线程上执行（通过使用任务执行器检测事件、接受新的连接并将 I/O 读操作分配给其他线程）。当一个 I/O 读取器线程（向其发送读操作）读取数据时，它会将数据传递给另一个线程来组装传入的消息。大型消息可能需要多次读取才能完成。这些“汇编”线程可以在等待数据时阻塞。当一个新的读事件发生时，读取器确定这个套接字是否已经有一个汇编器，如果没有，则运行一个新的汇编器。当组装过程完成后，组装程序线程将返回到池中。

当池耗尽、`CALLER_RUNS`拒绝策略正在使用以及任务队列已满时，这可能会导致死锁。当池为空并且队列中没有空间时，IO 选择器线程接收一个`OP_READ`事件，并使用执行器分配读取。队列已满，因此选择器线程本身启动读进程。现在，它检测到这个套接字没有汇编器，并且在读取之前，启动一个汇编器。同样，队列已满，选择器线程将成为汇编器。汇编器现在被阻塞，等待数据被读取，而这永远不会发生。由于选择器线程无法处理新事件，连接工厂现在处于死锁状态。

为了避免这种死锁，我们必须避免选择器（或阅读器）线程执行组装任务。我们希望对 IO 和 Assembly 操作使用单独的池。

该框架提供了`CompositeExecutor`，它允许配置两个不同的执行器：一个用于执行 IO 操作，另一个用于消息程序集。在这种环境中，IO 线程永远不能成为汇编线程，死锁也不会发生。

此外，任务执行器应该被配置为使用`AbortPolicy`（`ABORT`时使用`<task>`）。当一个 I/O 任务不能完成时，它会被推迟一小段时间并不断地重试，直到它可以完成并分配一个汇编器。默认情况下，延迟为 100ms，但你可以通过在连接工厂上设置`readDelay`属性（在使用 XML 名称空间配置时`read-delay`）来更改它。

以下三个示例展示了如何配置复合执行器：

```
@Bean
private CompositeExecutor compositeExecutor() {
    ThreadPoolTaskExecutor ioExec = new ThreadPoolTaskExecutor();
    ioExec.setCorePoolSize(4);
    ioExec.setMaxPoolSize(10);
    ioExec.setQueueCapacity(0);
    ioExec.setThreadNamePrefix("io-");
    ioExec.setRejectedExecutionHandler(new AbortPolicy());
    ioExec.initialize();
    ThreadPoolTaskExecutor assemblerExec = new ThreadPoolTaskExecutor();
    assemblerExec.setCorePoolSize(4);
    assemblerExec.setMaxPoolSize(10);
    assemblerExec.setQueueCapacity(0);
    assemblerExec.setThreadNamePrefix("assembler-");
    assemblerExec.setRejectedExecutionHandler(new AbortPolicy());
    assemblerExec.initialize();
    return new CompositeExecutor(ioExec, assemblerExec);
}
```

```
<bean id="myTaskExecutor" class="org.springframework.integration.util.CompositeExecutor">
    <constructor-arg ref="io"/>
    <constructor-arg ref="assembler"/>
</bean>

<task:executor id="io" pool-size="4-10" queue-capacity="0" rejection-policy="ABORT" />
<task:executor id="assembler" pool-size="4-10" queue-capacity="0" rejection-policy="ABORT" />
```

```
<bean id="myTaskExecutor" class="org.springframework.integration.util.CompositeExecutor">
    <constructor-arg>
        <bean class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
            <property name="threadNamePrefix" value="io-" />
            <property name="corePoolSize" value="4" />
            <property name="maxPoolSize" value="8" />
            <property name="queueCapacity" value="0" />
            <property name="rejectedExecutionHandler">
                <bean class="java.util.concurrent.ThreadPoolExecutor.AbortPolicy" />
            </property>
        </bean>
    </constructor-arg>
    <constructor-arg>
        <bean class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
            <property name="threadNamePrefix" value="assembler-" />
            <property name="corePoolSize" value="4" />
            <property name="maxPoolSize" value="10" />
            <property name="queueCapacity" value="0" />
            <property name="rejectedExecutionHandler">
                <bean class="java.util.concurrent.ThreadPoolExecutor.AbortPolicy" />
            </property>
        </bean>
    </constructor-arg>
</bean>
```

### SSL/TLS 支持

支持安全套接字层/传输层安全。当使用蔚来时，JDK5+`SSLEngine`特性用于在建立连接后处理握手。当不使用蔚来时，使用标准`SSLSocketFactory`和`SSLServerSocketFactory`对象来创建连接。提供了许多策略接口，以允许进行重要的定制。这些接口的默认实现提供了开始安全通信的最简单方法。

#### 入门

无论是否使用蔚来，都需要在连接工厂上配置`ssl-context-support`属性。此属性引用了一个 \<bean/\>定义，该定义描述了所需密钥存储的位置和密码。

SSL/TLS 对等点需要两个密钥存储区：

* 包含用于标识对等方的私钥和公钥对的密钥存储库

* 一个信任存储库，其中包含受信任的对等节点的公钥。请参阅 JDK 提供的`keytool`实用程序的文档。关键的步骤是

  1. 创建一个新的密钥对并将其存储在密钥存储库中。

  2. 导出公钥。

  3. 将公钥导入对等方的信任存储库。

  4. 为另一个同龄人重复一遍。

|   |在测试用例中，在两个对等点上使用相同的密钥存储是很常见的，但是在生产中应该避免这种情况。|
|---|---------------------------------------------------------------------------------------------------------------|

在建立密钥存储之后，下一步是将它们的位置指示给 Bean 并向连接工厂提供对该 Bean 的引用。

下面的示例配置一个 SSL 连接：

```
<bean id="sslContextSupport"
    class="o.sf.integration.ip.tcp.connection.support.DefaultTcpSSLContextSupport">
    <constructor-arg value="client.ks"/>
    <constructor-arg value="client.truststore.ks"/>
    <constructor-arg value="secret"/>
    <constructor-arg value="secret"/>
</bean>

<ip:tcp-connection-factory id="clientFactory"
    type="client"
    host="localhost"
    port="1234"
    ssl-context-support="sslContextSupport" />
```

`DefaultTcpSSLContextSupport`类还具有一个可选的`protocol`属性，它可以是`SSL`或`TLS`（默认值）。

密钥存储库文件名（前两个构造函数参数）使用 Spring `Resource`抽象。默认情况下，这些文件位于 Classpath 上，但是你可以通过使用`file:`前缀（以在文件系统上查找文件）来重写该文件。

从版本 4.3.6 开始，当你使用蔚来时，你可以在连接工厂上指定`ssl-handshake-timeout`（以秒为单位）。在等待数据时，在 SSL 握手过程中使用此超时（默认为 30 秒）。如果超过了超时，进程将停止，套接字将关闭。

#### 主机验证

从版本 5.0.8 开始，你可以配置是否启用主机验证。从版本 5.1 开始，它是默认启用的；禁用它的机制取决于你是否使用蔚来。

主机验证用于确保你所连接的服务器与证书中的信息相匹配，即使证书是可信的。

当使用蔚来时，例如配置`DefaultTcpNioSSLConnectionSupport`。

```
@Bean
public DefaultTcpNioSSLConnectionSupport connectionSupport() {
    DefaultTcpSSLContextSupport sslContextSupport = new DefaultTcpSSLContextSupport("test.ks",
            "test.truststore.ks", "secret", "secret");
    sslContextSupport.setProtocol("SSL");
    DefaultTcpNioSSLConnectionSupport tcpNioConnectionSupport =
            new DefaultTcpNioSSLConnectionSupport(sslContextSupport, false);
    return tcpNioConnectionSupport;
}
```

第二个构造函数参数禁用主机验证。然后将`connectionSupport` Bean 注入到蔚来连接工厂。

当不使用蔚来时，配置在`TcpSocketSupport`中：

```
connectionFactory.setTcpSocketSupport(new DefaultTcpSocketSupport(false));
```

同样，构造函数参数禁用主机验证。

### 先进技术

本节介绍了一些高级技术，你可能会发现这些技术在某些情况下会有所帮助。

#### 策略接口

在许多情况下，前面描述的配置是支持 TCP/IP 上的安全通信所需的全部。 Spring 然而，集成提供了许多策略接口，以允许自定义和修改套接字工厂和套接字：

* `TcpSSLContextSupport`

* `TcpSocketFactorySupport`

* `TcpSocketSupport`

* `TcpNetConnectionSupport`

* `TcpNioConnectionSupport`

##### the`TcpSSLContextSupport`策略界面

下面的清单显示了`TcpSSLContextSupport`策略接口：

```
public interface TcpSSLContextSupport {

    SSLContext getSSLContext() throws Exception;

}
```

`TcpSSLContextSupport`接口的实现负责创建`SSLContext`对象。该框架提供的实现方式是`DefaultTcpSSLContextSupport`，[前面描述的](#ip-ssl-tls-getting-started)。如果你需要不同的行为，实现这个接口，并向连接工厂提供对你的类实现的 Bean 的引用。

##### 的`TcpSocketFactorySupport`策略界面

下面的清单显示了`TcpSocketFactorySupport`策略接口的定义：

```
public interface TcpSocketFactorySupport {

    ServerSocketFactory getServerSocketFactory();

    SocketFactory getSocketFactory();

}
```

该接口的实现负责获得对`ServerSocketFactory`和`SocketFactory`的引用。提供了两种实现方式。第一个是`DefaultTcpNetSocketFactorySupport`用于非 SSL 套接字（当没有`ssl-context-support`属性被定义时）。这使用了 JDK 的默认工厂。第二个实现方式是`DefaultTcpNetSSLSocketFactorySupport`。默认情况下，这是在定义`ssl-context-support`属性时使用的。它使用由 Bean 创建的`SSLContext`来创建套接字工厂。

|   |只有当`using-nio`是`false`时，此接口才适用。<br/>蔚来不使用套接字工厂。|
|---|---------------------------------------------------------------------------------------------|

##### 的`TcpSocketSupport`策略界面

下面的清单显示了`TcpSocketSupport`策略接口的定义：

```
public interface TcpSocketSupport {

    void postProcessServerSocket(ServerSocket serverSocket);

    void postProcessSocket(Socket socket);

}
```

这个接口的实现可以在套接字被创建之后，在所有配置的属性被应用之后，但在套接字被使用之前修改它们。无论你是否使用蔚来，这一点都适用。例如，你可以使用此接口的实现来修改 SSL 套接字上受支持的密码套件，或者你可以添加一个侦听器，该侦听器在 SSL 握手完成后得到通知。框架提供的唯一实现是`DefaultTcpSocketSupport`，它不会以任何方式修改套接字。

要提供你自己的`TcpSocketFactorySupport`或`TcpSocketSupport`的实现，通过分别设置`socket-factory-support`和`socket-support`属性，向连接工厂提供对自定义类型的 bean 的引用。

##### 的`TcpNetConnectionSupport`策略界面

下面的清单显示了`TcpNetConnectionSupport`策略接口的定义：

```
public interface TcpNetConnectionSupport {

    TcpNetConnection createNewConnection(Socket socket,
            boolean server, boolean lookupHost,
            ApplicationEventPublisher applicationEventPublisher,
            String connectionFactoryName) throws Exception;

}
```

调用这个接口来创建`TcpNetConnection`类型的对象（或其子类）。该框架提供了一个实现（`DefaultTcpNetConnectionSupport`），默认情况下，该实现创建简单的`TcpNetConnection`对象。它有两个性质：`pushbackCapable`和`pushbackBufferSize`。当启用 Push Back 时，该实现返回一个子类，该子类将连接的`InputStream`包装在`PushbackInputStream`中。与`PushbackInputStream`默认值对齐，缓冲区大小默认值为 1。这使得反序列化器可以“未读”（向后推送）字节到流中。下面的简单示例展示了如何在委托反序列化器中使用它，该代理反序列化器在第一个字节“窥视”以确定调用哪个反序列化器：

```
public class CompositeDeserializer implements Deserializer<byte[]> {

    private final ByteArrayStxEtxSerializer stxEtx = new ByteArrayStxEtxSerializer();

    private final ByteArrayCrLfSerializer crlf = new ByteArrayCrLfSerializer();

    @Override
    public byte[] deserialize(InputStream inputStream) throws IOException {
        PushbackInputStream pbis = (PushbackInputStream) inputStream;
        int first = pbis.read();
        if (first < 0) {
            throw new SoftEndOfStreamException();
        }
        pbis.unread(first);
        if (first == ByteArrayStxEtxSerializer.STX) {
            this.receivedStxEtx = true;
            return this.stxEtx.deserialize(pbis);
        }
        else {
            this.receivedCrLf = true;
            return this.crlf.deserialize(pbis);
        }
    }

}
```

##### 该`TcpNioConnectionSupport`策略接口

下面的清单显示了`TcpNioConnectionSupport`策略接口的定义：

```
public interface TcpNioConnectionSupport {

    TcpNioConnection createNewConnection(SocketChannel socketChannel,
            boolean server, boolean lookupHost,
            ApplicationEventPublisher applicationEventPublisher,
            String connectionFactoryName) throws Exception;

}
```

调用这个接口来创建`TcpNioConnection`对象（或来自子类的对象）。 Spring 集成提供了两种实现方式：`DefaultTcpNioSSLConnectionSupport`和`DefaultTcpNioConnectionSupport`。使用哪一种取决于是否使用 SSL。一个常见的用例是子类`DefaultTcpNioSSLConnectionSupport`并覆盖`postProcessSSLEngine`。参见[SSL 客户端身份验证示例](#ssl-client-authentication-example)。与`DefaultTcpNetConnectionSupport`一样，这些实现也支持后推。

#### 示例：启用 SSL 客户机身份验证

要在使用 SSL 时启用客户端证书身份验证，该技术取决于是否使用蔚来。当你不蔚来时，请提供一个自定义`TcpSocketSupport`实现来对服务器套接字进行后处理：

```
serverFactory.setTcpSocketSupport(new DefaultTcpSocketSupport() {

    @Override
    public void postProcessServerSocket(ServerSocket serverSocket) {
        ((SSLServerSocket) serverSocket).setNeedClientAuth(true);
    }

});
```

（在使用 XML 配置时，通过设置`socket-support`属性提供对 Bean 的引用）。

当你使用蔚来时，提供一个自定义的`TcpNioSslConnectionSupport`实现来对`SSLEngine`进行后处理，如下例所示：

```
@Bean
public DefaultTcpNioSSLConnectionSupport tcpNioConnectionSupport() {
    return new DefaultTcpNioSSLConnectionSupport(serverSslContextSupport) {

            @Override
            protected void postProcessSSLEngine(SSLEngine sslEngine) {
                sslEngine.setNeedClientAuth(true);
            }

    }
}

@Bean
public TcpNioServerConnectionFactory server() {
    ...
    serverFactory.setTcpNioConnectionSupport(tcpNioConnectionSupport());
    ...
}
```

（当你使用 XML 配置时，从版本 4.3.7 开始，通过设置`nio-connection-support`属性来提供对 Bean 的引用）。

### IP 配置属性

下表描述了可以设置以配置 IP 连接的属性：

|      Attribute Name       |Client?|Server?|Allowed Values |属性描述|
|---------------------------|-------|-------|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|          `type`           |   Y   |   Y   |client, server |确定连接工厂是客户机还是服务器。|
|          `host`           |   Y   |   N   |               |目标的主机名称或 IP 地址。|
|          `port`           |   Y   |   Y   |               |港口。|
|       `serializer`        |   Y   |   Y   |               |用于序列化有效负载的`Serializer`的实现。<br/>默认为`ByteArrayCrLfSerializer`|
|      `deserializer`       |   Y   |   Y   |               |用于反序列化有效负载的`Deserializer`的实现。<br/>默认为`ByteArrayCrLfSerializer`|
|        `using-nio`        |   Y   |   Y   |`true`, `false`|连接是否使用蔚来。<br/>有关更多信息，请参考`java.nio`包。<br/>请参见[关于非阻塞 I/O（蔚来）](#note-nio)。<br/>默认：`false`。|
|  `using-direct-buffers`   |   Y   |   N   |`true`, `false`|当使用蔚来时，无论连接是否使用直接缓冲区。<br/>有关更多信息，请参阅`java.nio.ByteBuffer`文档。<br/>必须是`false`如果`using-nio`是`false`。|
|     `apply-sequence`      |   Y   |   Y   |`true`, `false`|当你使用蔚来时，可能需要对消息进行重新排序。<br/>当将此属性设置为`true`、`correlationId`和`sequenceNumber`时，将头添加到接收到的消息中。<br/>参见<br/>。<br/>默认：`false`。|
|       `so-timeout`        |   Y   |   Y   |               |默认值为`0`（无穷大），除了具有`single-use="true"`的服务器连接工厂。<br/>在这种情况下，它默认为默认的回复超时（10 秒）。|
|   `so-send-buffer-size`   |   Y   |   Y   |               |见`java.net.Socket.``setSendBufferSize()`。|
| `so-receive-buffer-size`  |   Y   |   Y   |               |见`java.net.Socket.``setReceiveBufferSize()`。|
|      `so-keep-alive`      |   Y   |   Y   |`true`, `false`|见`java.net.Socket.setKeepAlive()`。|
|        `so-linger`        |   Y   |   Y   |               |用提供的值将`linger`设置为`true`。<br/>参见`java.net.Socket.setSoLinger()`。|
|     `so-tcp-no-delay`     |   Y   |   Y   |`true`, `false`|见`java.net.Socket.setTcpNoDelay()`。|
|    `so-traffic-class`     |   Y   |   Y   |               |见`java.net.Socket.``setTrafficClass()`。|
|      `local-address`      |   N   |   Y   |               |在多主机系统中，为套接字所绑定的接口指定一个 IP 地址。|
|      `task-executor`      |   Y   |   Y   |               |指定用于套接字处理的特定执行器。<br/>如果不提供，则使用内部缓存的线程执行器。<br/>在某些需要使用特定任务执行器的平台上需要，例如`WorkManagerTaskExecutor`。|
|       `single-use`        |   Y   |   Y   |`true`, `false`|指定连接是否可以用于多个消息。<br/>如果`true`，则为每个消息使用一个新的连接。|
|        `pool-size`        |   N   |   N   |               |此属性不再使用。<br/>对于向后兼容性，它设置了 backlog，但是你应该使用`backlog`来指定服务器工厂中的连接 backlog。|
|         `backlog`         |   N   |   Y   |               |设置服务器工厂的连接待办事项。|
|       `lookup-host`       |   Y   |   Y   |`true`, `false`|指定是否对 IP 地址进行反向查找，以将其转换为在消息头中使用的主机名。<br/>如果错误，则使用 IP 地址。<br/>默认：`true`。|
|`interceptor-factory-chain`|   Y   |   Y   |               |见[TCP 连接拦截器](#ip-interceptors)。|
|   `ssl-context-support`   |   Y   |   Y   |               |见`[SSL/TLS Support](#ssl-tls)`。|
| `socket-factory-support`  |   Y   |   Y   |               |见`[SSL/TLS Support](#ssl-tls)`。|
|     `socket-support`      |   Y   |   Y   |               |见[SSL/TLS 支持](#ssl-tls)。|
| `nio-connection-support`  |   Y   |   Y   |               |见[先进技术](#tcp-advanced-techniques)。|
|       `read-delay`        |   Y   |   Y   |   long \> 0   |重试之前的延迟（以毫秒为单位）。由于线程不足，上次尝试失败后的读取。<br/>默认值：100。<br/>仅当`using-nio`为`true`时才适用。|

下表描述了可以设置以配置 UDP 入站通道适配器的属性：

|     Attribute Name     |Allowed Values |属性描述|
|------------------------|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|         `port`         |               |适配器监听的端口。|
|      `multicast`       |`true`, `false`|UDP 适配器是否使用组播。|
|  `multicast-address`   |               |当多播为真时，适配器加入的多播地址。|
|      `pool-size`       |               |指定可以并发处理多少个包。<br/>只有在未配置任务执行器的情况下才适用。<br/>默认情况：5。|
|     task-executor      |               |指定用于套接字处理的特定执行器。<br/>如果不提供，则使用内部池执行器。<br/>在某些需要使用特定任务执行器的平台上需要`WorkManagerTaskExecutor`。<br/>有关线程需求，请参见池大小。|
| `receive-buffer-size`  |               |用于接收`DatagramPackets`的缓冲区的大小。<br/>通常设置为最大传输单元的大小。<br/>如果使用比发送的数据包的大小更小的缓冲区，则可能发生截断。<br/>你可以通过使用`check-length`属性来检测这一点。|
|     `check-length`     |`true`, `false`|无论 UDP 适配器是否期望接收到数据包中的数据长度字段。<br/>用于检测数据包的截断。|
|      `so-timeout`      |               |有关更多信息，请参见`java.net.DatagramSocket`中的`setSoTimeout()`方法。|
| `so-send-buffer-size`  |               |用于 UDP 确认数据包。<br/>有关更多信息，请参见`java.net.DatagramSocket`中的 setsendBufferSize（）方法。|
|`so-receive-buffer-size`|               |有关更多信息，请参见`java.net.DatagramSocket.setReceiveBufferSize()`。|
|    `local-address`     |               |在多主机系统中，为套接字绑定到的接口指定一个 IP 地址。|
|    `error-channel`     |               |如果下游组件抛出异常，则将包含异常和失败消息的`MessagingException`消息发送到此通道。|
|     `lookup-host`      |`true`, `false`|指定是否对 IP 地址进行反向查找，以将其转换为在消息头中使用的主机名。<br/>如果`false`，则使用该 IP 地址。<br/>默认：`true`。|

下表描述了可以设置以配置 UDP 出站通道适配器的属性：

|     Attribute Name     |Allowed Values |属性描述|
|------------------------|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|         `host`         |               |主机名称或 IP 地址的目标。<br/>对于多播 UDP 适配器，多播地址。|
|         `port`         |               |目的地上的港口。|
|      `multicast`       |`true`, `false`|UDP 适配器是否使用组播。|
|     `acknowledge`      |`true`, `false`|无论 UDP 适配器是否需要来自目的地的确认。<br/>启用后，它需要设置以下四个属性：`ack-host`，`ack-port`，`ack-timeout`，和`min-acks-for- success`。|
|       `ack-host`       |               |当`acknowledge`是`true`时，指示应该将确认发送到的主机或 IP 地址。<br/>通常是当前的主机，但可能是不同的——例如，当正在使用网络地址转换时。|
|       `ack-port`       |               |当`acknowledge`是`true`时，指示应将确认发送到的端口。<br/>适配器在此端口上监听确认。|
|     `ack-timeout`      |               |当`acknowledge`是`true`时，表示适配器等待确认的时间（以毫秒为单位）。<br/>如果没有及时接收到确认，则适配器抛出一个异常。|
|`min-acks-for- success` |               |默认值为 1。<br/>对于多播适配器，你可以将其设置为更大的值，这需要来自多个目标的确认。|
|     `check-length`     |`true`, `false`|UDP 适配器是否在发送到目的地的数据包中包括数据长度字段。|
|     `time-to-live`     |               |对于多播适配器，指定`MulticastSocket`的 time-to-live 属性。<br/>控制多播的作用域。<br/>有关更多信息，请参阅 Java API 文档。|
|      `so-timeout`      |               |有关更多信息，请参见`java.net.DatagramSocket`setsotimeout（）方法。|
| `so-send-buffer-size`  |               |有关更多信息，请参见`java.net.DatagramSocket`中的`setSendBufferSize()`方法。|
|`so-receive-buffer-size`|               |用于 UDP 确认数据包。<br/>有关更多信息，请参见`java.net.DatagramSocket`中的`setReceiveBufferSize()`方法。|
|     local-address      |               |在多主机系统中，对于 UDP 适配器，指定用于响应消息的套接字绑定到的接口的 IP 地址。<br/>对于多播适配器，它还确定多播包被发送到哪个接口。|
|    `task-executor`     |               |指定用于确认处理的特定执行器。<br/>如果不提供，则使用内部单线程执行器。<br/>在某些需要使用特定任务执行器的平台上需要，例如`WorkManagerTaskExecutor`。<br/>一个线程专门用于处理确认（如果`acknowledge`选项为真）。|
|`destination-expression`|SpEL expression|要计算的 SPEL 表达式，以确定将哪个`SocketAddress`用作传出 UDP 数据包的目标地址。|
|  `socket-expression`   |SpEL expression|要计算的 SPEL 表达式，以确定使用哪个数据报套接字来发送传出的 UDP 数据包。|

下表描述了可以设置以配置 TCP 入站通道适配器的属性：

|   Attribute Name   |Allowed Values |属性描述|
|--------------------|---------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|     `channel`      |               |入站消息被发送到的通道。|
|`connection-factory`|               |如果连接工厂的类型为`server`，则该工厂由此适配器“拥有”。<br/>如果其类型为`client`，则由出站通道适配器“拥有”，并且此适配器接收出站适配器创建的连接上的任何传入消息。|
|  `error-channel`   |               |如果下游组件引发异常，则将包含异常和失败消息的`MessagingException`消息发送到此通道。|
|   `client-mode`    |`true`, `false`|当`true`，入站适配器充当一个客户端，用于建立连接，然后在该连接上接收传入消息。<br/>默认：`false`。<br/>另请参见`retry-interval`和<br/>连接工厂必须是类型`client`，并且将`single-use`设置为`false`。|
|  `retry-interval`  |               |当在`client-mode`中，指定连接尝试之间或连接失败之后需要等待的毫秒数。<br/>默认值：60000（60 秒）。|
|    `scheduler`     |`true`, `false`|指定用于管理`TaskScheduler`连接的`TaskScheduler`。<br/>如果未指定，则默认为全局 Spring 集成`taskScheduler` Bean，其缺省池大小为 10。<br/>参见[配置任务调度程序](./configuration.html#namespace-taskscheduler)。|

下表描述了可以设置以配置 TCP 出站通道适配器的属性：

|   Attribute Name   |Allowed Values |属性描述|
|--------------------|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|     `channel`      |               |出站消息到达的通道。|
|`connection-factory`|               |如果连接工厂的类型为`client`，则该工厂由此适配器“拥有”，<br/>如果它的类型为`server`，则该工厂由入站通道适配器“拥有”，该适配器尝试将消息与接收原始入站消息的连接关联起来。|
|   `client-mode`    |`true`, `false`|当`true`时，出站适配器在连接启动后立即尝试建立连接。<br/>当`false`时，当发送第一条消息时，连接就建立了。<br/>默认值：`false`。<br/>另请参见`retry-interval`和<br/>。<br/>连接工厂必须是类型`client`并将`single-use`设置为`false`。|
|  `retry-interval`  |               |当在`client-mode`中，指定连接尝试之间或连接失败之后需要等待的毫秒数。<br/>默认值：60000（60 秒）。|
|    `scheduler`     |`true`, `false`|指定用于管理`TaskScheduler`连接的`client-mode`。<br/>如果未指定，则默认为全局 Spring 集成`taskScheduler` Bean，其缺省池大小为 10。<br/>参见[配置任务调度程序](./configuration.html#namespace-taskscheduler)。|

下表描述了可以设置以配置 TCP 入站网关的属性：

|   Attribute Name   |Allowed Values |属性描述|
|--------------------|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|`connection-factory`|               |连接工厂必须是服务器类型的。|
| `request-channel`  |               |传入消息被发送到的通道。|
|  `reply-channel`   |               |回复消息可能到达的通道。<br/>通常，回复是通过添加到入站消息头中的临时回复通道到达的。|
|  `reply-timeout`   |               |网关等待回复的时间（以毫秒为单位）。<br/>默认值：1000（1 秒）。|
|  `error-channel`   |               |如果下游组件抛出了异常，则将包含异常和失败消息的`MessagingException`消息发送到此通道。<br/>来自该流的任何回复都将作为网关的响应返回。|
|   `client-mode`    |`true`, `false`|当`true`，入站网关充当客户端，用于建立连接，然后在客户端上接收（并回复）传入消息该连接。<br/>默认：false。<br/>另请参见`retry-interval`和`scheduler`。<br/>连接工厂必须是类型`client`并且将`single-use`设置为`false`。|
|  `retry-interval`  |               |当在`client-mode`中，指定连接尝试之间或连接失败之后需要等待的毫秒数。<br/>默认值：60000（60 秒）。|
|    `scheduler`     |`true`, `false`|指定用于管理`TaskScheduler`连接的`client-mode`。<br/>如果未指定，则默认为全局 Spring 集成`taskScheduler` Bean，其缺省池大小为 10。<br/>参见[配置任务调度程序](./configuration.html#namespace-taskscheduler)。|

下表描述了可以设置以配置 TCP 出站网关的属性：

|       Attribute Name        |Allowed Values|属性描述|
|-----------------------------|--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|    `connection-factory`     |              |连接工厂必须是`client`类型。|
|      `request-channel`      |              |发送消息的通道。|
|       `reply-channel`       |              |可选的。<br/>发送回复消息的通道。|
|      `remote-timeout`       |              |网关等待远程系统回复的时间，以毫秒为单位。<br/>与`remote-timeout-expression`互斥。<br/>默认值：10000（10 秒）。<br/>注意：在 4.2 之前的版本中，该值默认为`reply-timeout`（如果设置）。|
| `remote-timeout-expression` |              |一种 SPEL 表达式，针对消息进行求值，以确定网关等待远程系统答复的时间（以毫秒为单位）。<br/>与`remote-timeout`互斥。|
|      `request-timeout`      |              |如果没有使用一次性连接工厂，则网关等待访问共享连接的时间（以毫秒为单位）。|
|       `reply-timeout`       |              |网关在向应答通道发送应答时所等待的时间（以毫秒为单位）。<br/>仅在应答通道可能阻塞（例如当前已满的有界队列通道）时才适用。|
|           `async`           |              |在发送后释放发送线程；回复（或错误）将在接收线程上发送。|
|`unsolicited``MessageChannel`|              |向其发送未经请求的消息和延迟回复的通道。|

### IP 消息头

IP 消息头

该模块使用以下`MessageHeader`实例：

|      Header Name      |      IpHeaders Constant       |说明|
|-----------------------|-------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|     `ip_hostname`     |          `HOSTNAME`           |接收 TCP 消息或 UDP 数据包的主机名。<br/>如果`lookupHost`是`false`，则包含 IP 地址。|
|     `ip_address`      |         `IP_ADDRESS`          |接收 TCP 消息或 UDP 数据包的 IP 地址。|
|       `ip_port`       |            `PORT`             |UDP 数据包的远程端口。|
| ip\_localInetAddress  |      `IP_LOCAL_ADDRESS`       |连接套接字的本地`InetAddress`（自版本 4.2.5 起）。|
|      `ip_ackTo`       |         `ACKADDRESS`          |UDP 应用程序级确认发送到的远程 IP 地址。<br/>该框架在数据包中包括确认信息。|
|      `ip_ackId`       |           `ACK_ID`            |用于 UDP 应用程序级确认的相关 ID。<br/>该框架在数据包中包括确认信息。|
|  `ip_tcp_remotePort`  |         `REMOTE_PORT`         |TCP 连接的远程端口。|
|   `ip_connectionId`   |        `CONNECTION_ID`        |TCP 连接的唯一标识符。<br/>由入站消息框架设置。<br/>当发送到服务器端入站通道适配器或回复入站网关时，需要此标识符，以便端点可以确定将消息发送到的连接。|
|`ip_actualConnectionId`|    `ACTUAL_CONNECTION_ID`     |仅供参考。<br/>当使用缓存或故障转移客户端连接工厂时，它包含实际的底层连接 ID。|
|     `contentType`     |`MessageHeaders.``CONTENT_TYPE`|入站消息的可选内容类型<br/>在此表之后描述。<br/>注意，与其他头常量不同，该常量位于`MessageHeaders`类中，而不是`IpHeaders`类中。|

对于入站消息，`ip_hostname`、`ip_address`、`ip_tcp_remotePort`和`ip_connectionId`由默认的`TcpHeaderMapper`映射。如果你将映射器的`addContentTypeHeader`属性设置为`true`，映射器将设置`contentType`标头（默认情况下，`application/octet-stream;charset="UTF-8"`）。你可以通过设置`contentType`属性来更改默认值。你可以通过子类化`TcpHeaderMapper`并重写`supplyCustomHeaders`方法来添加额外的标题。例如，当使用 SSL 时，可以通过从`TcpConnection`对象获得会话对象来添加`SSLSession`的属性，该对象作为参数提供给`supplyCustomHeaders`方法。

对于出站消息，`String`有效负载被转换为`byte[]`，并具有默认（`UTF-8`）字符集。设置`charset`属性以更改默认值。

在定制 Mapper 属性或子类时，将 Mapper 声明为 Bean，并使用`mapper`属性向连接工厂提供一个实例。

### 基于注释的配置

下面示例存储库中的示例显示了使用注释而不是 XML 时可用的一些配置选项：

```
@EnableIntegration (1)
@IntegrationComponentScan (2)
@Configuration
public static class Config {

    @Value(${some.port})
    private int port;

    @MessagingGateway(defaultRequestChannel="toTcp") (3)
    public interface Gateway {

        String viaTcp(String in);

    }

    @Bean
    @ServiceActivator(inputChannel="toTcp") (4)
    public MessageHandler tcpOutGate(AbstractClientConnectionFactory connectionFactory) {
        TcpOutboundGateway gate = new TcpOutboundGateway();
        gate.setConnectionFactory(connectionFactory);
        gate.setOutputChannelName("resultToString");
        return gate;
    }

    @Bean (5)
    public TcpInboundGateway tcpInGate(AbstractServerConnectionFactory connectionFactory)  {
        TcpInboundGateway inGate = new TcpInboundGateway();
        inGate.setConnectionFactory(connectionFactory);
        inGate.setRequestChannel(fromTcp());
        return inGate;
    }

    @Bean
    public MessageChannel fromTcp() {
        return new DirectChannel();
    }

    @MessageEndpoint
    public static class Echo { (6)

        @Transformer(inputChannel="fromTcp", outputChannel="toEcho")
        public String convert(byte[] bytes) {
            return new String(bytes);
        }

        @ServiceActivator(inputChannel="toEcho")
        public String upCase(String in) {
            return in.toUpperCase();
        }

        @Transformer(inputChannel="resultToString")
        public String convertResult(byte[] bytes) {
            return new String(bytes);
        }

    }

    @Bean
    public AbstractClientConnectionFactory clientCF() { (7)
        return new TcpNetClientConnectionFactory("localhost", this.port);
    }

    @Bean
    public AbstractServerConnectionFactory serverCF() { (8)
        return new TcpNetServerConnectionFactory(this.port);
    }

}
```

|**1**|Spring 标准的集成注释使基础设施能够用于集成应用程序。|
|-----|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|搜索`@MessagingGateway`接口。|
|**3**|流的客户端的入口点。<br/>调用应用程序可以使用`@Autowired`对此`Gateway` Bean 并调用其方法。|
|**4**|出站端点由`MessageHandler`和对其进行包装的使用者组成。在此场景中，<br/>根据通道类型配置端点。|
|**5**|入站端点（在 TCP/UDP 模块中）都是消息驱动的，因此只需要声明为简单的`@Bean`实例。|
|**6**|这个类提供了在这个样例流中使用的许多 POJO 方法（服务器端的`@Transformer`和`@ServiceActivator`，客户端的`@Transformer`）。|
|**7**|客户端连接工厂。|
|**8**|服务器端连接工厂。|

### 对 TCP 组件使用 Java DSL

对 TCP 组件的 DSL 支持包括适配器和网关的规范，带有工厂方法的`Tcp`类来创建连接工厂 bean，以及带有工厂方法的`TcpCodecs`类来创建序列化器和反序列化器。有关更多信息，请参考他们的 Javadocs。

下面是使用 DSL 配置使用 DSL 的流的一些示例。

例1.服务器适配器流

```
@Bean
public IntegrationFlow server() {
    return IntegrationFlows.from(Tcp.inboundAdapter(Tcp.netServer(1234)
                            .deserializer(TcpCodecs.lengthHeader1())
                            .backlog(30))
                        .errorChannel("tcpIn.errorChannel")
                        .id("tcpIn"))
            .transform(Transformers.objectToString())
            .channel("tcpInbound")
            .get();
}
```

例2.客户端适配器流

```
@Bean
public IntegrationFlow client() {
    return f -> f.handle(Tcp.outboundAdapter(Tcp.nioClient("localhost", 1234)
                        .serializer(TcpCodecs.lengthHeader1())));
}
```

例3.服务器网关流

```
@Bean
public IntegrationFlow server() {
    return IntegrationFlows.from(Tcp.inboundGateway(Tcp.netServer(1234)
                            .deserializer(TcpCodecs.lengthHeader1())
                            .serializer(TcpCodecs.lengthHeader1())
                            .backlog(30))
                        .errorChannel("tcpIn.errorChannel")
                        .id("tcpIn"))
            .transform(Transformers.objectToString())
            .channel("tcpInbound")
            .get();
}
```

例4.客户端网关流程

```
@Bean
public IntegrationFlow client() {
    return f -> f.handle(Tcp.outboundGateway(Tcp.nioClient("localhost", 1234)
                        .deserializer(TcpCodecs.lengthHeader1())
                        .serializer(TcpCodecs.lengthHeader1())));
}
```