java,Socket,NIO随笔记录

      这个答案答的很好。Socket与SocketChannel是俩套api而已,对于网络tcp通信而言不会关心你上层是用何api实现通信的。所以答案是肯定的。SocketChannel可以设置为非阻塞的,所以在某种情况下性能更好,线程不会被挂住。SocketChannel还能注册selector和感兴趣的事件。selector多路复用器这里就不多做介绍了。

     在这里介绍一下通信过程中数据的流动过程。首先当数据被网卡接收后,会被保存到内核中,电脑进行拆包解析,看源端口号,会将此数据包保存到对应链接的tcp接收缓冲区(有的地方成为Socket缓冲区)。当此缓冲区满了后,会发生的动作是:通知对端TCP协议中的窗口关闭。这便是滑动窗口的实现。保证了TCP是可靠传输。因为对方不允许发出超过所通告窗口大小的数据。这就是TCP的流量控制。(刺猬本人自己理解如有不准确还望提出)

  虾面说一下聊天程序server端的简单实现逻辑。我们要实现私聊,当两个客户端与服务端建立连接后,服务端会保存所有建立连接的客户端信息:包括此客户端的name,Socket或SocketChannel(当建立连接后会返回)。其中A客户端向B客户端发送信息“B,hello!”。服务端收到消息后截取字符串“B”,get出与之对应的Socket(NIO的话为SocketChannel),往其中写入读到的内容。完成私聊。虾面贴出服务端的程序:(格式没了凑活看吧,过几天准备再用netty实现此功能)

package com.server;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;

import com.server.vo.UserInfo;

public class NioServer implements Runnable {

private static HashMap<String,UserInfo> users=new HashMap<String,UserInfo>();

private Socket client;

private Selector selector;

private ServerSocketChannel serverChannel;

private volatile boolean stop;

private UserInfo userinfo;

/**
* 初始化多路复用器,绑定监听端口
*/
public NioServer(int port) {
try {
selector = Selector.open();
serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.bind(new InetSocketAddress(port), 1024);
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (Exception e) {
e.printStackTrace();
}
}

public void stop() {
this.stop = true;
}

@Override
public void run() {
while (!stop) {
try {
selector.select(1000);
Set<SelectionKey> selectionKeys= selector.selectedKeys();
SelectionKey key=null;
Iterator<SelectionKey> it= selectionKeys.iterator();
while(it.hasNext()){
key=it.next();
it.remove();
try{
handleInput(key);
}catch(Exception e){
e.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
if(selector!=null){
try{
selector.close();
}catch(Exception e){
e.printStackTrace();
}
}
}

public void handleInput(SelectionKey key) throws IOException{
if(key.isValid()){
if(key.isAcceptable()){
ServerSocketChannel ssc=(ServerSocketChannel) key.channel();
SocketChannel sc= ssc.accept();
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ);
userinfo = new UserInfo();
userinfo.setUsername(sc.getRemoteAddress()+"");
userinfo.setSocket(sc.socket());
users.put(sc.getRemoteAddress()+"", userinfo);
System.out.println(users);
}
if(key.isReadable()){
SocketChannel sc= (SocketChannel) key.channel();
ByteBuffer readBuffer=ByteBuffer.allocate(1024);//ByteBuffer最核心的方法是put(byte)和get()。分别是往ByteBuffer里写一个字节,和读一个字节。
// 执行以上方法后,通道会从socket读取的数据填充此缓冲区,它返回成功读取并存储在缓冲区的字节数.在默认情况下,这至少会读取一个字节,或者返回-1指示数据结束.
int readBytes=sc.read(readBuffer);//写入缓冲器
if(readBytes>0){
readBuffer.flip();//写模式转换成读模式
byte[] bytes=new byte[readBuffer.remaining()];
readBuffer.get(bytes);//读取到bytes中
String body=new String(bytes,"UTF-8");
String name=body.substring(0,body.lastIndexOf(".")+8);
System.out.println(body);
SocketChannel soc= users.get(name).getSocket().getChannel();
doWrite(soc,body);
}
}
}
}

private void doWrite(SocketChannel channel,String response) throws IOException{
if(response!=null&&response.trim().length()>0){
byte[] bytes=response.getBytes();
ByteBuffer bf=ByteBuffer.allocate(bytes.length);
bf.put(bytes);//往缓冲器中写数据,将bytes写入缓冲器
bf.flip();//写模式转换成读模式
channel.write(bf);//读取出缓冲器数据写入到channel中
}
}

public static void main(String[] args){
NioServer ns=new NioServer(8088);
ns.run();
}

//byteBuffer = ByteBuffer.allocate(N);
////读取数据,写入byteBuffer
//readableByteChannel.read(byteBuffer);
////变读为写
//byteBuffer.flip();
////读取byteBuffer,写入数据
//writableByteChannel.write(byteBuffer);
// 看一段程序:
// 1:ByteBuffer buffer = ByteBuffer.allocate(5); position=0,limit=5,capacity=5
// 2:buffer.put((byte)1); position=1,limit=5,capacity=5
// 3:buffer.flip(); position=0,limit=1,capacity=5
// 4:buffer.get(); position=1,limit=1,capacity=5
//
// 第一行:创建一个ByteBuffer,初始化状态下,postion为0,limit=capacity=缓冲区大小
// 第二行:往缓冲区放一个字节,position就会往后移动一下,但是不会超过limit。如果超过会报错。
// 第三行: flip方法会把limit设为position的值,而position还原为0,为读缓冲区做准备。
// 第四行:从缓冲区内读取一个字节,position会往后移动一下,但是不会超过limit,如果超过会报错。

}

posted @ 2016-02-26 00:09  张-晓  阅读(365)  评论(0编辑  收藏  举报