JavaNIO套接字【源码笔记】

编程

二、交互示例

本文以代码示例跟踪调用Native函数,看下原型函数的具体释义。例子中“客户端”从文件test02.tmp读取内容后,通过socket发送到“服务端”后写入test01.tmp文件中。

服务端示例

ServerSocketChannel ssc = ServerSocketChannel.open(); // @1

ssc.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

回到顶部