java 阻塞与非阻塞_Java NIO(4): 阻塞与非阻塞

本节课是小密圈《进击的Java新人》第十六周第四节课。这一节课,我们通过 linux 上的 socket 编程,来说明一下IO模型。

我们通过继续第八周的课程:Java网络编程(二):套接字,来看一下正式的网络编程的例子。

阻塞式IO

我们先看一个阻塞式的例子:

#include#include#include#include#include#include#include

#define MAXLEN 4096

int main(int argc, char** argv)

{

int listenfd, sock_fd;

struct sockaddr_in servaddr;

char buff[MAXLEN];

int n;

if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){

printf("create socket error: %s(errno: %d)/n",strerror(errno),errno);

exit(0);

}

memset(&servaddr, 0, sizeof(servaddr));

servaddr.sin_family = AF_INET;

servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

servaddr.sin_port = htons(8001);

if( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){

printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);

exit(0);

}

if( listen(listenfd, 10) == -1){

printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);

exit(0);

}

printf("waiting for client to connect\n");

while(1){

if( (sock_fd = accept(listenfd, (struct sockaddr*)NULL, NULL)) == -1){

printf("accept socket error: %s(errno: %d)",strerror(errno),errno);

continue;

}

n = recv(sock_fd, buff, MAXLEN, 0);

buff[n] = '\0';

printf("recv msg from client: %s\n", buff);

close(sock_fd);

break;

}

close(listenfd);

}

然后使用gcc编译一下:

root@ecs-2f21:~/hinusDocs/cpp# gcc -o io io.c

使用这段Java客户端代码去连接服务端,可以看到服务端会打印出"hello world"

public class NetClient {

public static void main(String[] args) {

try {

SocketChannel socketChannel = SocketChannel.open();

socketChannel.connect(new InetSocketAddress("127.0.0.1", 8001));

socketChannel.configureBlocking(true);

ByteBuffer writeBuffer = ByteBuffer.allocate(1024);

writeBuffer.put("hello world!".getBytes());

writeBuffer.flip();

socketChannel.write(writeBuffer);

socketChannel.close();

} catch (IOException e) {

}

}

}

非阻塞式IO

我们上节课讲了非阻塞式IO,在 linux 上,要做到这一点,就要使用 fcnt 这个函数。具体地说,就是把原来的recv变成这样:

int flags = fcntl(sock_fd, F_GETFL, 0);

fcntl(sock_fd, F_SETFL,flags | O_NONBLOCK);

int total = 0;

while (total < 12) {

n = recv(sock_fd, buff, MAXLINE, 0);

if (n >= 0) {

printf("I can do something else %d\n", n);

total += n;

}

}

通过设置sockfd,把它改成非阻塞的。这样一来,recv方法不管是否读到数据,都会立即返回,所以我们会看到,当n等于0的时候,就会有大量的打印。也就是说,非阻塞的socket给我们提供了一种可能:检查一下socket上有没有数据,如果有数据,就可以取到这个数据,执行后面的逻辑,如果没有数据,那么recv函数也会立即返回,可以做点其他的事情。

这种IO模型就是非阻塞的。虽然recv函数没有直接让线程休眠,但本质上,我们使用了一个自旋在这里不断地检查数据,直到数据到达以后,才会跳出这个循环。所以,这种写法仍然是服务端在等待客户端,仍然是一种同步模型。

同步就一定阻塞吗?

通过上面的例子,我们也看到了,同步和阻塞并不是等价的。同步的意义只是说客户端发过来的数据到达之前,我干不了其他的事情。而阻塞强调的是调用一个函数,线程会不会休眠。在上面的非阻塞模型里,虽然调用recv不会再使线程休眠了,但程序并没有去执行什么有效的逻辑,所以这本质上仍然是一个同步模型。

Java中的非阻塞

在Java中要使用非阻塞非常简单,只需要在socketChannel上调用:

socketChannel.configureBlocking(false);

我们来看一下,它的具体实现:

在IDE里通过查看JDK源码可以找到:

protected void implConfigureBlocking(boolean var1) throws IOException {

IOUtil.configureBlocking(this.fd, var1);

} // in SocketChannelImpl

然后在IOUtil里看到这是一个 static native 方法:

public static native void configureBlocking(FileDescriptor var0, boolean var1) throws IOException;

这个方法的具体实现位于 jdk/src/solaris/native/sun/nio/ch/IOUtil.c 中:

static int

configureBlocking(int fd, jboolean blocking)

{

int flags = fcntl(fd, F_GETFL);

int newflags = blocking ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK);

return (flags == newflags) ? 0 : fcntl(fd, F_SETFL, newflags);

}

JNIEXPORT void JNICALL

Java_sun_nio_ch_IOUtil_configureBlocking(JNIEnv *env, jclass clazz,

jobject fdo, jboolean blocking)

{

if (configureBlocking(fdval(env, fdo), blocking) < 0)

JNU_ThrowIOExceptionWithLastError(env, "Configure blocking failed");

}

哈哈,下面是configureBlocking的JNI定义,上面的那个是真正的实现。原来,JDK中也不过是使用 fcntl 来实现设置 socket 是否阻塞这个功能的。可见,要想真正地理解Java 在服务器上做了什么事情,必须有很好的服务端编程能力。而我们的课程就都集中在这一点上,这也是通过其他渠道学Java往往很难能学到的地方。

好了,今天就到这里。

作业:

1. 使用Java写一版非阻塞的服务端程序,和本节课中的C代码对应的。

2. 上节课我们讲了5种模型,今天给出了前两种模型的例子。对照本节课的例子,再去理解上节课的理论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值