niosocket简单复习
重要概念
niosocket里面的三个重要概念:buffer、channel、selector
buffer为要传输的数据
channel为传输数据的通道
selector为通道的分配调度者
使用步骤
使用niosocket实现通信大概如以下步骤:
serversocketchannel可以通过configureblocking方法来设置是否采用阻塞模式,设置为false后就可以调用register注册selector,阻塞模式下不可以用selector。
注册后,selector就可以通过select()来等待请求,通过参数设置等待时长,若传入参数0或者不传入参数,将会采用阻塞模式直到有请求出现。
接收到请求后selector调用selectedkeys方法,返回selectedkey集合。
selectedkey保存了处理当前请求的channel和selector,并提供了不同的操作类型。四种操作属性:selectedkey.op_accept、selectedkey.op_connect、selectedkey.op_read、selectedkey.op_write。
通过selectedkey的isacceptable、isconnectable、isreadable和iswritable来判断操作类型,并处理相应操作。
在相应的handler中提取selectedkey中的channel和buffer信息并执行相应操作。
实现http
创建httpserver类作为程序的主要入口
public class httpserver {
public static void main(string[] args) throws exception{
serversocketchannel serversocketchannel = serversocketchannel.open();
serversocketchannel.socket().bind(new inetsocketaddress((8080)));
serversocketchannel.configureblocking(false);
selector selector = selector.open();
// it must be accept, or it will throw exception
serversocketchannel.register(selector, selectionkey.op_accept);
while(true){
if (selector.select(3000) == 0){
continue;
}
iterator keyiter = selector.selectedkeys().iterator();
while (keyiter.hasnext()){
selectionkey key = keyiter.next();
new thread(new httphandler(key)).run();
keyiter.remove();
}
}
}
}
以上代码的逻辑大致遵循着niosocket的大概用法,其中serversocketchannel使用register方法注册到selector仅是op_accept,使用其他操作就会操作。但是并不是说不能进行其他操作,而是其他操作稍后实现。
在serversocketchannel.configureblocking(false)后,非阻塞模式启动。server接收到请求后就会将记录了请求信息的key交给httphandler做详细处理,处理完就把key从迭代器里面remove掉。可以看到出来,httpserver对请求里面的信息一概不知,这样才能成为一个出色的管理层,它管理着httphandler来处理请求。
既然选用了niosocket这样的new io,httphandler必然是多线程的实现(否则还有什么意义)。
创建httphandler来处理请求
对于来自httpserver的不加工信息,httphandler必须要做全套,因此需要httphandler自己考虑好有没有中文乱码、buffer大小是多少等等。httphandler大概框架如下即可:
class httphandler implements runnable{
private int buffersize = 1024;
private string localcharset = "utf-8";
private selectionkey key;
public httphandler(selectionkey key){
this.key = key;
}
public void handleaccept() throws ioexception{}
public void handleread() throws ioexception{}
@override
public void run() {
try {
if(key.isacceptable()){
handleaccept();
}
if(key.isreadable()){
handleread();
}
}catch (ioexception ex){
ex.printstacktrace();
}
}
}
如上框架简单明了,重载run实现多线程,handleaccept和handleread用于详细地处理相关操作,buffersize规定buffer大小,localcharset的设定提前防止中文乱码。
需要注意的是httpserver里面,我们只注册了op_accept这个操作,那么在httphandler里面只有isacceptable()判定为真,那么handleread()怎么办呢?我们会在handleaccept()注册好的:
public void handleaccept() throws ioexception{
socketchannel clientchannel =
((serversocketchannel)key.channel()).accept();
clientchannel.configureblocking(false);
clientchannel.register(
key.selector(), selectionkey.op_read, bytebuffer.allocate(buffersize)
);
}
在handleaccept里面,我们先取得key里面的请求信息,如对应客户端的socketchannel (socketchannel需要serversocketchannel接受了后才有),接着就可以为socketchannel注册op_read操作了,带上指定大小的buffer。注册后,key可是isreadable()了,接下来则是在handleread中对key进行解剖处理:(代码有点长,但大多是控制台输出和对字符串的拼接操作,看官可放心食用。)
public void handleread() throws ioexception{
socketchannel sc = (socketchannel)key.channel();
bytebuffer buffer = (bytebuffer)key.attachment();
buffer.clear();
if (sc.read(buffer) == -1){
sc.close();
}else {
buffer.flip();
string receivestring = charset.forname(localcharset).newdecoder().decode(buffer).tostring();
string[] requestmessage = receivestring.split("\r\n");
for (string s: requestmessage){
system.out.println(s);
if (s.isempty()){
break;
}
}
string[] firstline = requestmessage[0].split(" ");
system.out.println();
system.out.println("method:\t"+ firstline[0]);
system.out.println("url:\t"+firstline[1]);
system.out.println("http version:\t" + firstline[2]);
system.out.println();
stringbuilder sendstring = new stringbuilder();
sendstring.append("http/1.1 200 ok\r\n");
sendstring.append("content-type:text/html;charset="+localcharset+"\r\n");
sendstring.append("\r\n");
sendstring.append("
show");sendstring.append("received:
");
for (string s : requestmessage){
sendstring.append(s + "
");
}
sendstring.append("