Java NIO的Socket通道

阅读数:11 评论数:0

跳转到新版页面

分类

python/Java

正文

一、Socket通道

分部socket通道类(DatagramChannel、SocketChannel和ServerSocketChannel)在被实例化时都会创建一个对等的socket对象(DatagramSocket、Socket、ServerSocket),Socket通道将与通信协议相关的操作交由socket对象负责。

二、ServerSocketChannel

1、API

public abstract class ServerSocketChannel extends AbstractSelectableChannel
  {
      public static ServerSocketChannel open() throws IOException;
      public abstract ServerSocket socket();
      public abstract ServerSocket accept()throws IOException;
      public final int validOps();
  }

(1)打开ServerSocketChannel

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

(2)关闭

serverSocketChannel.close();

(3)监听新进来的连接

通过ServerSocketChannel.accept()方法监听新进来的连接,当accept()方法返回的时候,它返回一个包含新进来的连接SocketChannel。因此,accept()方法会一直阻塞到有新连接到达。

 while(true){
            SocketChannel socketChannel = serverSocketChannel.accept();
            //...
        }

在非阻塞模式下,accept() 方法会立刻返回,如果还没有新进来的连接,返回的将是null。 因此,需要检查返回的SocketChannel是否是null

三、SocketChannel

Java NIO中的SocketChannel是一个连接到TCP网络套接字的通道。

1、打开SocketChannel

SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));

2、关闭SocketChannel

socketChannel.close();

3、从SocketChannel读取数据

ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = socketChannel.read(buf);

read()方法返回的int值表示读了多少字节进Buffer里。如果返回的是-1,表示已经读到了流的末尾(连接关闭了)

4、写入SocketChannel

String newData = "New String to write to file..." + System.currentTimeMillis();
 
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
 
buf.flip();
 
while(buf.hasRemaining()) {
    channel.write(buf);
}

Write()方法无法保证能写多少字节到SocketChannel。所以,我们重复调用write()直到Buffer没有要写的字节为止。

四、DatagramChannel

面向UDP的通道,是无连接的。

1、打开

DatagramChannel channel = DatagramChannel.open();
    channel.socket().bind(new InetSocketAddress(9999));

上面的例子可以在UDP 9999上接收数据包。

2、接收数据

    ByteBuffer buf = ByteBuffer.allocate(48);
    buf.clear();
    channel.receive(buf);

如果Buffer容不下收到的数据,多出的数据将被丢弃。

3、发送数据

String newData = "New String to write to file..." + System.currentTimeMillis();
     
    ByteBuffer buf = ByteBuffer.allocate(48);
    buf.clear();
    buf.put(newData.getBytes());
    buf.flip();
     
    int bytesSent = channel.send(buf, new   InetSocketAddress("jenkov.com", 80));

五、ByteBuffer基本概念

1、 两个子类

这个类它有两个子类,二者操作字节的方法基本相同,大部分操作字节的方法都在父类中定义。

一般如果一个ByteBuffer经常被重用的话,就可以使用DirectorByteBuffer,如果需要经常释放和分配的地方用HeapByteBuffer。

(1)HeapByteBuffer

分配在堆上,数byte[]数组的一种封装形式。

public static ByteBuffer allocate(int capacity){
  if(capacity<0)
    throw new IllegalArgumentException();
  return new HeapByteBuffer(capacity,capacity);
}

(2)DirectByteBuffer

不分配在堆上,它不被GC直接管理,直接由系统内存进行分配。

public static ByteBuffer allocateDirect(int capacity){
  return new DirectorByteBuffer(capacity);
}

2、capacity position limit mark

mark<=position<=limit<=capacity

(1)mark:是position指针的标记位置。

(2)position:表示当前有效数据的指针位置。

(3)limit:数据有效性的一个上限范围。

(4)capacity:Buffer的容量,是分配好的一个内存块大小,分配好后大小不可变。

整个buffer中只有position-limit之间的数据是有效的。

初始位置:

写入n个字节后:

调用flip,由写模式转为读模式:

六、ByteBuffer的四大操作方法

1、字节数组“指针”操作

(1)get方法,获取一个字节,position++;

(2)put方法存储一个字节,position++;

(3)flip方法,准备下一个状态,一般用于读完数据或者写完数据之后再次进行读写功能的时候需要进行调用,position=0,mark=-1,limit=position。

(4)rewind方法,将position设回0,所以你可以重读Buffer中的所有数据。

(5)clear方法,position将被设回0,limit被设置成 capacity的值。换句话说,Buffer 被清空了。Buffer中的数据并未清除,只是这些标记告诉我们可以从哪里开始往Buffer里写数据。

它和fllip有一个区别就是limit的值不一样。

(6)reset方法,position=mark。

(7)mark方法,mark=position。

(8)remaing方法和hasRemaing方法,hasRemaing方法是判断ByteBuffer有没有到上限,即position是否大于limit,remaing方法获取limit-position的值。

// 示例
ByteBuffer buff = ByteBuffer.allocate(10); 
for(int i=0;i<5;++i){
  buff.put((byte)i);  // 使用put方法一次存入5个数据
}

buff.flip(); //limit=position,position=0,要开始读操作,所以要设置一个状态

for(int i=0;i<4;i++){
  System.out.print(buff.get()+",");
}

buff.clear();  //后面又要进行新的写操作

byte[] byteArry = new byte[5];
for(int i=0;i<5;i++){
  byteArray[i] = (byte)(i+3);
}
buff.put(byteArray);  // 一次性写入一个字节数组

buff.rewind();  // 由于limit是10,所以会读出后面5个脏数据

 

2、内存分配功能

(1)allocate和allocateDirect方法的区别就是一个直接从JVM的堆内存进行分配,一个是直接从系统内存中进行分配的。

(2)wrap方法和上面分配的方法的区别是,他在分配内存的时候就可以传递字节数组,相当于allocate+put操作了,但是它有一个特点就是这个字节将和BytBuffer相互影响,就是彼此之间的值改变会影响到对方,因为wrap源码内部就是直接将这个字节数组赋值给了hb属性,所以在堆内存中都是一块内存。而且wrap是在堆中分配内存的。

ByteBuffer buffer = ByteBuffer.allocate(10240);
ByteBuffer directBuffer = ByteBuffer.allocateDirect(10240);
byte[] bytes = new byte[32];

buffer = ByteBuffer.wrap(bytes);
bytes[10]=-99;  // 会同时修改buffer中值

3、子buffer操作

(1)slice方法

copy一个原来ByteBuffer的position-limit之间的有效数据,由于和源ByteBuffer共用一个hb,只是改变了position和limit、capacity的值,内容肯定是相互影响,但是没有影响到源ByteBuffer的position和limit值。

ByteBuffer buff = ByteBuffer.allocate(10);
buff.position(2);
buffer.limit(4);
ByteBuffer slice = buff.slice();

(2)duplicate方法

不仅把所有的内容copy过来,还把mark, position, limit, capacity也全部copy过来,与源ByteBuffer的内容是相互影响的,但不会影响源ByteBuffer 的position和limit方法。

(3)array方法

这个方法直接返回ByteBuffer的全局的字节数组,所以内容肯定是相互影响的。

byte[] array = buff.array();

(4)get方法

byte[] array = new byte[buff.remaining];
buff.get(array);

把源ByteBuffer中的数据复制到新的字节数组中,内容互不影响。

4、数据压缩和其他基本类型之间的转化

(1)compact()方法

所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面。limit属性依然像clear()方法一样,设置成capacity。现在Buffer准备好写数据了,但是不会覆盖未读的数据。

(2)getInt、asIntBuffer、order方法