Stream Control Transport Protocol (SCTP) in Java
By Chris Hegarty, June 2009
SCTP 已经在JDK7中被批准实现了。相关的API和参考实现在sctp openjdk project. 该项目被集成到了JDK7的里程碑3里面,后续版本都支持SCTP。
SCTP简介
SCTP是一个可靠的,面向消息的传输协议。在TCP/IP协议中和UDP/TCP处于同一层。SCTP是基于sesstion的,要先建立端点之间的连接,然后传输数据。
SCTP支持多址的,也就是说一个端点可以由多个地址表示,每个地址可以用来传输数据,从而提供了网络的冗余。端点之间可以在建立连接的时候,交换多个地址。其中一个地址是主地址,也是向对端传输数据的默认地址。一个端口代表一个特定的session。
SCTP是基于消息的。每一个association支持多个独立的逻辑流。每个流代表了一系列顺序消息。每个流都是相互独立的,也就是流的唯一标识和序列号在数据包中存在,从而使得每个流中的消息都是顺序传递的。
SCTP的关键特性
- Message framing 消息帧
- Reliable transport service可靠传输
- Session-oriented 基于session的
- Ordered and unordered message delivery 顺序和无顺序的消息传递
- Multi-Homing 多址
- Association between exactly two endpoints 每两个端点间有association
- Each endpoint may be represented by multiple IP addresses 每个端点可以由多个IP地址
- Provides failover and redundancy 提供了冗余
- Multi-Streaming 多流
- Data partitioned into multiple streams 数据被分成多个流传输
- Independent sequenced delivery 独立顺序传递
- Eliminates head-of-line blocking 消除了??
Support for SCTP in JDK 7
SCTP API是基于NIO设计的,因此可以利用非阻塞的复用I/O. 引入了一个新的包com.sun.nio.sctp。包名字不是java.nio.channels.sctp,这意味着这些API是完全公开的。当这些API成熟后,再引入到标准API中。
这个包中的主要类有三个新channel类型。可以分为两个逻辑组。
1 第一个组和TCP类似,包括了sctpChannel 和 SctpServerChannel。一个SctpChannel只能控制一个association,也就是说只能和一个端点发送接受数据。 SctpSeverChannel监听和接受在socket地址上的接入。
2 第二组包括了SctpMultiChannel。这个类可以控制多个association,因此可以和多个端点传送数据。
SCTP是事件驱动的。程序能接收到特定的SCTP事件。这些事件尤其实在SctpMultiChannel有用。SctpMultiChannel可以控制多个association,所以需要跟踪每个association的状态。例如,当收到AssociationChangNotification的时候,表示有个新的连接的association或者断开。如果association支持动态地址配置,PeerAddressChangeNotification表示IP地址在对端刚刚增加或者删除。
Multi-Streaming Example
这个例子展示了多个流。服务器端视线了一个时间的协议,它把当前日期时间在一个流中以英语形式传递,一个流中以法语形式传递。为了简便,本程序没有考虑异常处理。
Multilingual DayTime Server
Here is the source code for DaytimeServer.
public class DaytimeServer { static int SERVER_PORT = 3456; static int US_STREAM = 0; static int FR_STREAM = 1; static SimpleDateFormat USformatter = new SimpleDateFormat( "h:mm:ss a EEE d MMM yy, zzzz", Locale.US); static SimpleDateFormat FRformatter = new SimpleDateFormat( "h:mm:ss a EEE d MMM yy, zzzz", Locale.FRENCH); public static void main(String[] args) throws IOException { SctpServerChannel ssc = SctpServerChannel.open(); InetSocketAddress serverAddr = new InetSocketAddress(SERVER_PORT); ssc.bind(serverAddr); ByteBuffer buf = ByteBuffer.allocateDirect(60); CharBuffer cbuf = CharBuffer.allocate(60); Charset charset = Charset.forName("ISO-8859-1"); CharsetEncoder encoder = charset.newEncoder(); while (true) { SctpChannel sc = ssc.accept(); /* get the current date */ Date today = new Date(); cbuf.put(USformatter.format(today)).flip(); encoder.encode(cbuf, buf, true); buf.flip(); /* send the message on the US stream */ MessageInfo messageInfo = MessageInfo.createOutgoing(null, US_STREAM); sc.send(buf, messageInfo); /* update the buffer with French format */ cbuf.clear(); cbuf.put(FRformatter.format(today)).flip(); buf.clear(); encoder.encode(cbuf, buf, true); buf.flip(); /* send the message on the French stream */ messageInfo.streamNumber(FR_STREAM); sc.send(buf, messageInfo); cbuf.clear(); buf.clear(); sc.close(); } } }
Multilingual DayTime Client
Here is the source code for DaytimeClient.
public class DaytimeClient { static int SERVER_PORT = 3456; static int US_STREAM = 0; static int FR_STREAM = 1; public static void main(String[] args) throws IOException { InetSocketAddress serverAddr = new InetSocketAddress("localhost", SERVER_PORT); ByteBuffer buf = ByteBuffer.allocateDirect(60); Charset charset = Charset.forName("ISO-8859-1"); CharsetDecoder decoder = charset.newDecoder(); SctpChannel sc = SctpChannel.open(serverAddr, 0, 0); /* handler to keep track of association setup and termination */ AssociationHandler assocHandler = new AssociationHandler(); /* expect two messages and two notifications */ MessageInfo messageInfo = null; do { messageInfo = sc.receive(buf, System.out, assocHandler); buf.flip(); if (buf.remaining() > 0 && messageInfo.streamNumber() == US_STREAM) { System.out.println("(US) " + decoder.decode(buf).toString()); } else if (buf.remaining() > 0 && messageInfo.streamNumber() == FR_STREAM) { System.out.println("(FR) " + decoder.decode(buf).toString()); } buf.clear(); } while (messageInfo != null); sc.close(); } static class AssociationHandler extends AbstractNotificationHandler<printstream> { public HandlerResult handleNotification(AssociationChangeNotification not, PrintStream stream) { if (not.event().equals(COMM_UP)) { int outbound = not.association().maxOutboundStreams(); int inbound = not.association().maxInboundStreams(); stream.printf("New association setup with %d outbound streams" + ", and %d inbound streams.\n", outbound, inbound); } return HandlerResult.CONTINUE; } public HandlerResult handleNotification(ShutdownNotification not, PrintStream stream) { stream.printf("The association has been shutdown.\n"); return HandlerResult.RETURN; } } }
Sample Output
Following is an example of the output you might get:
>: java DaytimeClient New association setup with 32 outbound streams, and 32 inbound streams. (US) 4:00:51 PM Fri 15 May 09, British Summer Time (FR) 4:00:51 PM ven. 15 mai 09, Heure d'ete britannique The association has been shutdown.
As well as posting comments on this article, please feel free to email the sctp development mailing list.
About the Author
Chris Hegarty is a software engineer at Sun Microsystems in Ireland. In his spare time, he rides a superbike.