对之前学过socket进行总结,但是这次会比较深入的体会一下,主要以孙卫琴老师的书为主,加上自己的总结:
一般的socket和serversocket就不总结了,主要进入非阻塞通信进行总结:
首先考虑一下阻塞的通讯在哪些地方会产生阻塞:
1,从线程方面考虑:
线程执行了Thread.sleep(int n)方法,线程放弃CPU,睡眠N毫秒,然后恢复运行;
线程执行一段同步代码,由于无法获得相关的同步锁,只好进入阻塞状态,等到获得了同步锁,才恢复运行;
线程执行了一个对象的wait()方法,进入阻塞状态,只有等到其他线程执行了该对象的notify()或notifyAll()方法,才可能将其唤醒;
线程执行I/O操作或进行远程通信时,会因为等待相关的资源而进入阻塞状态。例如,当线程执行System.in.read()方法时,如果用户没有向控制台输入数据,则该线程会一直等读到用户的输入数据才从read()方法返回;
2,进行远程通信时,在客户程序中,线程在以下情况可能进入阻塞状态
请求与服务器建立连接时,即当线程执行Socket的带参数的构造方法,或执行Socket的connect()方法,会进入阻塞状态,直到连接成功,此线程才从Socket的构造方法或connect()方法返回;
线程从Socket的输入流读入数据时,如果没有足够的数据,就会进入阻塞状态,直到读到了足够的数据,或者到达输入流的末尾,或者出现异常,才从输入流的read()方法返回或异常中断,输入流中有多少数据才算足够呢?这要看线程执行的read()方法的类型;(1)int read()只要输入流中有一个字节,就算足够;(2)int read(byte[] buff)只要输入流中的字节数目与参数buff数组的长度相同,就算足够(3)String readLine()只要输入流中有一行字符串,就算足够,值得注意的是,InputStream类并没有readLine()方法,在过滤流BufferedReader类中才有此方法
线程向Socket的输入流写一批数据时,可能会进入阻塞状态,等到输出了所有的数据,或者出现异常,才从输入流的write方法返回或异常中断;
调用Socket的setSolinger方法设置了关闭Socket的延迟时时间,那么当线程执行socket的close()方法时,会进入阻塞状态,直到底层Socket发送完所有剩下 数据,或者超过了setSolinger方法设置的延迟时间,才从close()方法返回;
3,在服务器程序中,线程在以下情况下可能会进入阻塞状态
线程执行ServerSocket的accept()方法,等待客户的连接,直到接收到了客户连接,才能从accept()方法返回;
线程从Socket的输入流读入数据,如果输入流没有足够的数据,就会进入阻塞状态;
线程向Socket的输出流写入一批数据,可能会进入阻塞状态,等到输出了所有的数据,或者出现异常,才从输出流的write方法返回或异常中断。
由此,如果从性能方面来说,多线程的设计是十分重要,主要能提高并发性,另外一方面,在通过Socket的输入流和输出流来写数据时,都可能进入阻塞状态,称为阻塞I/O。
下面来介绍java.nio中主要的几个类:
ServerSocketChannel:ServerSocket的代替类,支持阻塞通信和非阻塞通信;
SocketChannel:Socket的代替类,支持阻塞通信与非阻塞通信;
Selector:为ServerSocketChannel监控接收连接就绪事件,为SocketChannel监控连接就绪,读就绪和写就绪事件;
SelectorKey:代表ServerSocketChannel及SocketChannel向Selector注册事件的句柄,当一个SelectorKey对象位于Selector对象的selected-keys集合中时,就表示与这个SelectionKey对象相关的事件发生了。
ServerSocketChannel及SocketChannel都是SelectableChannel的子类,SelectableChannel类及其子类都能委托Selector来监控它们可能发生的一些事情,这种委托过程也成为注册事件的过程。
继承者 java.nio.channels.spi.AbstractInterruptibleChannel
继承者 java.nio.channels.SelectableChannel
继承者 java.nio.channels.spi.AbstractSelectableChannel
继承者 java.nio.channels.ServerSocketChannel
SelectionKey key = serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);实际上这个方法调用的是SelectableChannel的register的方法;
ServerSocketChannel只可能发生一种事件,SelectionKey.OP_ACCEPT:接收连接就绪事件,表示至少有一个客户链接,服务器可以接收这个连接。
SocketChannel可能发生以下三种事件,SelectionKey.OP_ACCEPT:连接就绪事件,表示客户与服务器的连接已经建立成功;
Selection.OP_READ:读就绪事件,表示输入流中已经有了可读数据,以执行读操作了
Selection.OP_WRITE:写就绪事件,表示已经向输出流写数据了。
现在来看看register的源码:
AbstractSelectableChannel.java中:
public final SelectionKey register(Selector sel, int ops,
Object att)
throws ClosedChannelException
{
if (!isOpen())// 判断此通道是否处于打开状态。
throw new ClosedChannelException();
if ((ops & ~validOps()) != 0)//如果ops不在操作集中则抛出异常
throw new IllegalArgumentException();
synchronized (regLock) {//获得锁,防止多程程造成不安全
if (blocking)//如果是设置为阻塞,则抛出异常
throw new IllegalBlockingModeException();
SelectionKey k = findKey(sel);//获得SelectionKey句柄
if (k != null) {
k.interestOps(ops);//获取此键的 interest 集合
k.attach(att);//把Object关联上去
}
if (k == null) {//如果k为空,那么重新注册
// New registration
k = ((AbstractSelector)sel).register(this, ops, att);//向此选择器注册给定的通道。返回表示给定通道向此选择器注册的新键
addKey(k);//把selectionKey注册到Selctor上
}
return k;
}
}
private SelectionKey findKey(Selector sel) {
synchronized (keyLock) {
if (keys == null)//如果SelectionKey[]为空,则返回null
return null;
for (int i = 0; i < keys.length; i++)
if ((keys[i] != null) && (keys[i].selector() == sel))//keys[i].selector(),返回为此选择器创建的键。即使已取消该键后,此方法仍将继续返回选择器。如果SelectionKey不为空并且SelectionKey返回的selector如果等于sel,那么Selector上的SelectionKey句柄返回
return keys[i];
return null;
}
}
private void addKey(SelectionKey k) {
synchronized (keyLock) {
int i = 0;
if ((keys != null) && (keyCount < keys.length)) {
// Find empty element of key array
for (i = 0; i < keys.length; i++)
if (keys[i] == null)
break;
} else if (keys == null) {
keys = new SelectionKey[3];
} else {
// Grow key array
int n = keys.length * 2;
SelectionKey[] ks = new SelectionKey[n];
for (i = 0; i < keys.length; i++)
ks[i] = keys[i];
keys = ks;
i = keyCount;
}
keys[i] = k;
keyCount++;
}
}
一般的socket和serversocket就不总结了,主要进入非阻塞通信进行总结:
首先考虑一下阻塞的通讯在哪些地方会产生阻塞:
1,从线程方面考虑:
线程执行了Thread.sleep(int n)方法,线程放弃CPU,睡眠N毫秒,然后恢复运行;
线程执行一段同步代码,由于无法获得相关的同步锁,只好进入阻塞状态,等到获得了同步锁,才恢复运行;
线程执行了一个对象的wait()方法,进入阻塞状态,只有等到其他线程执行了该对象的notify()或notifyAll()方法,才可能将其唤醒;
线程执行I/O操作或进行远程通信时,会因为等待相关的资源而进入阻塞状态。例如,当线程执行System.in.read()方法时,如果用户没有向控制台输入数据,则该线程会一直等读到用户的输入数据才从read()方法返回;
2,进行远程通信时,在客户程序中,线程在以下情况可能进入阻塞状态
请求与服务器建立连接时,即当线程执行Socket的带参数的构造方法,或执行Socket的connect()方法,会进入阻塞状态,直到连接成功,此线程才从Socket的构造方法或connect()方法返回;
线程从Socket的输入流读入数据时,如果没有足够的数据,就会进入阻塞状态,直到读到了足够的数据,或者到达输入流的末尾,或者出现异常,才从输入流的read()方法返回或异常中断,输入流中有多少数据才算足够呢?这要看线程执行的read()方法的类型;(1)int read()只要输入流中有一个字节,就算足够;(2)int read(byte[] buff)只要输入流中的字节数目与参数buff数组的长度相同,就算足够(3)String readLine()只要输入流中有一行字符串,就算足够,值得注意的是,InputStream类并没有readLine()方法,在过滤流BufferedReader类中才有此方法
线程向Socket的输入流写一批数据时,可能会进入阻塞状态,等到输出了所有的数据,或者出现异常,才从输入流的write方法返回或异常中断;
调用Socket的setSolinger方法设置了关闭Socket的延迟时时间,那么当线程执行socket的close()方法时,会进入阻塞状态,直到底层Socket发送完所有剩下 数据,或者超过了setSolinger方法设置的延迟时间,才从close()方法返回;
3,在服务器程序中,线程在以下情况下可能会进入阻塞状态
线程执行ServerSocket的accept()方法,等待客户的连接,直到接收到了客户连接,才能从accept()方法返回;
线程从Socket的输入流读入数据,如果输入流没有足够的数据,就会进入阻塞状态;
线程向Socket的输出流写入一批数据,可能会进入阻塞状态,等到输出了所有的数据,或者出现异常,才从输出流的write方法返回或异常中断。
由此,如果从性能方面来说,多线程的设计是十分重要,主要能提高并发性,另外一方面,在通过Socket的输入流和输出流来写数据时,都可能进入阻塞状态,称为阻塞I/O。
下面来介绍java.nio中主要的几个类:
ServerSocketChannel:ServerSocket的代替类,支持阻塞通信和非阻塞通信;
SocketChannel:Socket的代替类,支持阻塞通信与非阻塞通信;
Selector:为ServerSocketChannel监控接收连接就绪事件,为SocketChannel监控连接就绪,读就绪和写就绪事件;
SelectorKey:代表ServerSocketChannel及SocketChannel向Selector注册事件的句柄,当一个SelectorKey对象位于Selector对象的selected-keys集合中时,就表示与这个SelectionKey对象相关的事件发生了。
ServerSocketChannel及SocketChannel都是SelectableChannel的子类,SelectableChannel类及其子类都能委托Selector来监控它们可能发生的一些事情,这种委托过程也成为注册事件的过程。
实际上ServerSocketChannel和SocketChannel跟SelectableChannel的继承关系中间多了一个AbstarctSelectableChannel:
java.lang.Object继承者 java.nio.channels.spi.AbstractInterruptibleChannel
继承者 java.nio.channels.SelectableChannel
继承者 java.nio.channels.spi.AbstractSelectableChannel
继承者 java.nio.channels.ServerSocketChannel
SelectionKey key = serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);实际上这个方法调用的是SelectableChannel的register的方法;
ServerSocketChannel只可能发生一种事件,SelectionKey.OP_ACCEPT:接收连接就绪事件,表示至少有一个客户链接,服务器可以接收这个连接。
SocketChannel可能发生以下三种事件,SelectionKey.OP_ACCEPT:连接就绪事件,表示客户与服务器的连接已经建立成功;
Selection.OP_READ:读就绪事件,表示输入流中已经有了可读数据,以执行读操作了
Selection.OP_WRITE:写就绪事件,表示已经向输出流写数据了。
现在来看看register的源码:
AbstractSelectableChannel.java中:
public final SelectionKey register(Selector sel, int ops,
Object att)
throws ClosedChannelException
{
if (!isOpen())// 判断此通道是否处于打开状态。
throw new ClosedChannelException();
if ((ops & ~validOps()) != 0)//如果ops不在操作集中则抛出异常
throw new IllegalArgumentException();
synchronized (regLock) {//获得锁,防止多程程造成不安全
if (blocking)//如果是设置为阻塞,则抛出异常
throw new IllegalBlockingModeException();
SelectionKey k = findKey(sel);//获得SelectionKey句柄
if (k != null) {
k.interestOps(ops);//获取此键的 interest 集合
k.attach(att);//把Object关联上去
}
if (k == null) {//如果k为空,那么重新注册
// New registration
k = ((AbstractSelector)sel).register(this, ops, att);//向此选择器注册给定的通道。返回表示给定通道向此选择器注册的新键
addKey(k);//把selectionKey注册到Selctor上
}
return k;
}
}
private SelectionKey findKey(Selector sel) {
synchronized (keyLock) {
if (keys == null)//如果SelectionKey[]为空,则返回null
return null;
for (int i = 0; i < keys.length; i++)
if ((keys[i] != null) && (keys[i].selector() == sel))//keys[i].selector(),返回为此选择器创建的键。即使已取消该键后,此方法仍将继续返回选择器。如果SelectionKey不为空并且SelectionKey返回的selector如果等于sel,那么Selector上的SelectionKey句柄返回
return keys[i];
return null;
}
}
private void addKey(SelectionKey k) {
synchronized (keyLock) {
int i = 0;
if ((keys != null) && (keyCount < keys.length)) {
// Find empty element of key array
for (i = 0; i < keys.length; i++)
if (keys[i] == null)
break;
} else if (keys == null) {
keys = new SelectionKey[3];
} else {
// Grow key array
int n = keys.length * 2;
SelectionKey[] ks = new SelectionKey[n];
for (i = 0; i < keys.length; i++)
ks[i] = keys[i];
keys = ks;
i = keyCount;
}
keys[i] = k;
keyCount++;
}
}
暂时先到这里,明天继续......