前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java NIO套接字【源码笔记】

Java NIO套接字【源码笔记】

作者头像
瓜农老梁
发布2020-02-26 11:46:14
8850
发布2020-02-26 11:46:14
举报
文章被收录于专栏:瓜农老梁
目录
代码语言:javascript
复制
一、TCP/IP套接字函数交互图示
二、交互示例
三、本地函数释义
四、本文总结
五、参考书籍
六、系列文章
一、TCP/IP套接字函数交互图示
二、交互示例

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

服务端示例

代码语言:javascript
复制
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
    }
}

客户端示例

代码语言:javascript
复制
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()

代码语言:javascript
复制
fd = socket(domain, type, 0);

原型函数

代码语言:javascript
复制
int type = (stream ? SOCK_STREAM : SOCK_DGRAM);
int socket(int domain, int type, int protocol);

函数释义

代码语言:javascript
复制
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().

代码语言:javascript
复制
NET_Bind(int fd, struct sockaddr *him, int len)
rv = bind(fd, him, len);

原型函数

代码语言:javascript
复制
intbind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);

函数释义

代码语言:javascript
复制
bind() 为一个套接字分配地址
bind()有三个参数
    sockfd, 表示使用bind函数的套接字描述符
    my_addr, 指向sockaddr结构(用于表示所分配地址)的指针
    addrlen, 用socklen_t字段指定了sockaddr结构的长度
如果发生错误,函数返回值为-1,否则为0

小结:bind主要将套接字与套接字地址关联。

代码@3 configureBlocking()调用本地函数IOUtil.c#Java_sun_nio_ch_IOUtil_configureBlocking().

代码语言:javascript
复制
int flags = fcntl(fd, F_GETFL);
int newflags = blocking ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK);
return (flags == newflags) ? 0 : fcntl(fd, F_SETFL, newflags);

原型函数

代码语言:javascript
复制
int fcntl(int descriptor,int command,...)

函数释义

代码语言:javascript
复制
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。

代码语言:javascript
复制
// Java层
accept0(FileDescriptor ssfd, FileDescriptor newfd,
                               InetSocketAddress[] isaa)
// C层
newfd = (jint)accept(ssfd, (struct sockaddr *)&sa, &addrlen);

原型函数

代码语言:javascript
复制
intaccept(int socket, struct sockaddr *address, int *address_len);

函数释义

代码语言:javascript
复制
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

代码语言:javascript
复制
int result = close(fd);

原型函数

代码语言:javascript
复制
int close(int sockfd)

函数释义

代码语言:javascript
复制
通常的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

代码语言:javascript
复制
rv = connect(fdval(env, fdo), (struct sockaddr *)&sa, sa_len);

原型函数

代码语言:javascript
复制
intconnect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen)

函数释义

代码语言:javascript
复制
TCP用户用connect函数建立与TCP服务器的连接。
第一个参数:sockfd是由socket函数返回的套接字描述符
第二个参数:套接字地址结构的指针
第三个参数:地址结构大小
四、本文总结

主要跟了下Java NIO套接字中函数的本地原型函数及其含义。Java NIO Socket通道可以运行非阻塞模式以及可选择的,不必为每个socket连接新建一个线程,避免管理大量线程上下文切换的总开销;借助NIO类,一个或者几个线程就可以管理成百上千的活动socket连接并且只有很少甚至没有性能损失。

五、参考书籍

《Java NIO》、《UNIX网络编程 卷1》

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-02-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 瓜农老梁 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 目录
  • 一、TCP/IP套接字函数交互图示
  • 二、交互示例
  • 三、本地函数释义
  • 四、本文总结
  • 五、参考书籍
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档