JavaNIO套接字【源码笔记】
二、交互示例
本文以代码示例跟踪调用Native函数,看下原型函数的具体释义。例子中“客户端”从文件test02.tmp读取内容后,通过socket发送到“服务端”后写入test01.tmp文件中。
服务端示例
ServerSocketChannel ssc = ServerSocketChannel.open(); // @1ssc.socket().bind (new InetSocketAddress(8121)); // @2
ssc.configureBlocking (false); // @3
while (true) {
SocketChannel sc = ssc.accept( ); // @4
if (sc == null) {
Thread.sleep (2000);
} else {
Path path = Paths.get("/mytest/test01.tmp");
FileChannel fileChannel = FileChannel.open(path,
EnumSet.of(StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING,
StandardOpenOption.WRITE)
);
ByteBuffer buffer = ByteBuffer.allocate(1024);
while(sc.read(buffer) > 0) {
buffer.flip();
fileChannel.write(buffer);
buffer.clear();
}
fileChannel.close();
System.out.println("Receive finish.");
sc.close( ); // @5
}
}
客户端示例
SocketChannel server = SocketChannel.open();SocketAddress socketAddr = new InetSocketAddress("localhost", 8121);
server.connect(socketAddr); // @6
Path path = Paths.get("/mytest/test02.tmp");
FileChannel fileChannel = FileChannel.open(path);
ByteBuffer buffer = ByteBuffer.allocate(1024);
while(fileChannel.read(buffer) > 0) {
buffer.flip();
server.write(buffer);
buffer.clear();
}
fileChannel.close();
System.out.println("Sent finish");
server.close();
三、本地函数释义
代码@1
ServerSocketChannel.open()调用本地函数Net.c#Java_sun_nio_ch_Net_socket0()
fd = socket(domain, type, 0);
原型函数
int type = (stream ? SOCK_STREAM : SOCK_DGRAM);int socket(int domain, int type, int protocol);
函数释义
socket()为通讯创建一个端点,为套接字返回一个文件描述符。 socket()有三个参数:
domain 为创建的套接字指定协议集(或称做地址族 address family)。 例如:
AF_INET 表示IPv4网络协议
AF_INET6 表示IPv6
AF_UNIX 表示本地套接字(使用一个文件)
type(socket类型)如下:
SOCK_STREAM (可靠的面向流服务或流套接字)
SOCK_DGRAM (数据报文服务或者数据报文套接字)
SOCK_SEQPACKET (可靠的连续数据包服务)
SOCK_RAW (在网络层之上自行指定运输层协议头,即原始套接字)
protocol 指定实际使用的传输协议。
最常见的就是IPPROTO_TCP、IPPROTO_SCTP、IPPROTO_UDP、IPPROTO_DCCP
小结:通过Native函数释义看出TCP/IP封装类SocketChannel;UDP/IP的封装类DatagramChannel通过传入socket()函数的类型不同来创建套接字通信端点。
代码@2
bind()调用本地PlainSocketImpl.c#Java_java_net_PlainSocketImpl_socketBind().
NET_Bind(int fd, struct sockaddr *him, int len)rv = bind(fd, him, len);
原型函数
int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);
函数释义
bind() 为一个套接字分配地址bind()有三个参数
sockfd, 表示使用bind函数的套接字描述符
my_addr, 指向sockaddr结构(用于表示所分配地址)的指针
addrlen, 用socklen_t字段指定了sockaddr结构的长度
如果发生错误,函数返回值为-1,否则为0
小结:bind主要将套接字与套接字地址关联。
代码@3
configureBlocking()调用本地函数IOUtil.c#Java_sun_nio_ch_IOUtil_configureBlocking().
int flags = fcntl(fd, F_GETFL);int newflags = blocking ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK);
return (flags == newflags) ? 0 : fcntl(fd, F_SETFL, newflags);
原型函数
int fcntl(int descriptor,int command,...)
函数释义
fcntl函数可执行各种描述符控制的操作。* fcntl函数关于I/O的特性
非阻塞式I/O。通过使用F_SETFL命令设置O_NONBLOCK文件状态标志,可以把一个套接字设置成非阻塞型。例如:
flags = flags | O_NONBLOCK;
fcntl(fd, F_SETFL, flags);
* 信号驱动式I/O。通过使用F_SETFL命令设置O_ASYNC文件状态标志,可以把套接字设置成一旦其状态发生变化,内核就产生一个SIGIO信号。
小结:Java NIO的非阻塞通过本地函数fcntl中F_SETFL来设置。
代码@4
accept()调用本地函数ServerSocketChannelImpl.c#Java_sun_nio_ch_ServerSocketChannelImpl_accept0。
// Java层accept0(FileDescriptor ssfd, FileDescriptor newfd,
InetSocketAddress[] isaa)
// C层
newfd = (jint)accept(ssfd, (struct sockaddr *)&sa, &addrlen);
原型函数
int accept(int socket, struct sockaddr *address, int *address_len);
函数释义
accept用于从已完成连接队列头返回下一个已完成连接。如果accept成功,那么其返回值是由内核自动生成的一个全新描述符,代表与返回客户的TCP连接。第一个参数:“监听套接字描述符”(由socket创建,随后用做bind和listen的第一个参数描述符),accept的返回值为“已连接套接字描述符”。一个服务器通常仅仅创建一个“监听套接字”(由socket创建,随后用做bind和listen的第一个参数的描述符)。内核为每个由服务器进程接受的客户端连接创建一个“已连接套接字”(TCP三路握手已经完成),当服务器完成对某个给定客户端的服务时,相应的已连接套接字就关闭。
第二个参数:address为sockaddr_in结构体变量
第三个参数:address的长度
代码@5
close通过close0(FileDescriptor fd)调用本地函数FileDispatcherImpl.c#Java_sun_nio_ch_FileDispatcherImpl_close0
int result = close(fd);
原型函数
int close(int sockfd)
函数释义
通常的Unix close函数也用来关闭套接字,并终止TCP连接。
小结:close一个TCP套接字的默认行为是把该套接字标记成已关闭,然后立即返回到调用进程。该套接字描述符不能再由调用进程使用。即不能作为write/read的第一个参数
代码@6
close函数通过connect0(boolean preferIPv6,FileDescriptor fd,InetAddress remote,int remotePort)调用本地函数Net.c#Java_sun_nio_ch_Net_connect0
rv = connect(fdval(env, fdo), (struct sockaddr *)&sa, sa_len);
原型函数
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen)
函数释义
TCP用户用connect函数建立与TCP服务器的连接。第一个参数:sockfd是由socket函数返回的套接字描述符
第二个参数:套接字地址结构的指针
第三个参数:地址结构大小
四、本文总结
主要跟了下Java NIO套接字中函数的本地原型函数及其含义。Java NIO Socket通道可以运行非阻塞模式以及可选择的,不必为每个socket连接新建一个线程,避免管理大量线程上下文切换的总开销;借助NIO类,一个或者几个线程就可以管理成百上千的活动socket连接并且只有很少甚至没有性能损失。
五、参考书籍
《Java NIO》、《UNIX网络编程 卷1》
「瓜农老梁 学习同行」
以上是 JavaNIO套接字【源码笔记】 的全部内容, 来源链接: utcz.com/z/513467.html