Java ServerSocketChannel SocketChannel(回调)

时间:2010-04-21 18:27:38

标签: java socketchannel

我正在努力学习Java。我想实现一个简单的网络连接4游戏以及聊天功能。

我希望我的网络逻辑能够无阻塞,所以经过多次研究后我发现SocketChannel是我在重新考虑我的需求之后的事情。

在SocketChannels中缺少CallBack函数仍然没有意义。就像在C#中找到的一样。

我这次的查询是:如何将收到的数据发送到聊天或游戏表单(JFrame)?

非常欢迎一些指导。

1 个答案:

答案 0 :(得分:14)

您需要使用选择器。首先创建一个Selector来接收事件:

Selector selector = Selector.open()

然后,您需要使用选择器注册ServerSocketChannel:

SelectionKey acceptKey = server.register(selector, SelectionKey.OP_ACCEPT);

然后你需要使用Selector来处理事件进来(你可以把它想象成这个过程的“回调”部分:

while(true){
  //how many channel keys are available
  int available = selector.select(); 
  //select is blocking, but should only return if available is >0, this is more of a sanity check
  if(available == 0) continue;

  Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
  while(keys.hasNext()){
    SelectionKey key = keys.next();
    keys.remove();
    //someone is trying to connect to the server socket
    if(key.isAcceptable())  doAccept(key); 
    //someone is sending us data
    else if(key.isReadable()) doRead(key); 
    //we are trying to (and can) send data
    else if(key.isWritable()) doWrite(key);
}

肉将在doAccept(),doRead()和doWrite()中。对于接受键,选择键将包含用于创建新Socket的信息。

doAccept(SelectionKey key){

//create the new socket
SocketChannel socket = ((ServerSocketChannel)key.channel()).accept(); 
//make it non-blocking as well
socket.configureBlocking(false);

...
//here you would likely have some code to init your game objects / communication protocol, etc. and generate an identifier object (used below).
//and be able to find the socket created above
...

//Since it is non blocking it needs a selector as well, and we register for both read and write events
SelectionKey socketKey = socket.register(selector, SelectionKey.OP_READ|SelectionKey.OP_WRITE);
// so we can identify the events as they come in
socketKey.attach(someSocketIndentifier);
}

最后一行将一些对象添加到键中,以便从选择器接收的事件可以归因于连接(例如,它可能是游戏中的玩家)。所以现在你可以接受新的连接,你只需要读写。

doRead(SelectionKey key){
  //here we retrieve the key we attached earlier, so we now what to do / wheer the data is coming from
  MyIdentifierType myIdentifier = (MyIdentifierType)key.attachment();
  //This is then used to get back to the SocketChannel and Read the Data
  myIdentifier.readTheData();
}

类似于写

doWrite(SelectionKey key){
  //here we retrieve the key we attached earlier, so we now what to do / wheer the data is coming from
  MyIdentifierType myIdentifier = (MyIdentifierType)key.attachment();
  //This is then used to get back to the SocketChannel and Read the Data
  myIdentifier.getSocketHandler().writePendingData();
}

阅读非常简单,您只需创建一个ByteBuffer,然后调用SocketChannels读取(ByteBuffer)(或其中一个变体),以便在通道上准备好数据,直到它为空。

写入有点棘手,因为在收到写入事件之前,通常需要缓冲要写入的数据:

class MyNetworkClass{
  ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
  SocketChannel commchannel; //from the server accept processing

  ...

  public void write(byte[] data){
    //here the class writeBuffer object is filled with the data
    //but it isn't actually sent over the socket
    ...
  }

  public void writePendingData(){
    //here actually write the data to the socket
    commchannel.write(writeBuffer);
  }
}

请注意,如果类中的缓冲区已满,则需要使用适当的代码来管理类中的缓冲区,或者如果缓冲区中的所有数据都没有写入套接字,则需要在写入挂起方法中对其进行适当修改。以及在此过程中可能抛出的各种异常。希望这有助于您入门。