Java使用NioSocket手动实现HTTP服务器
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<selectionkey> 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("<html><head><title>show</title></head></body>"); sendstring.append("received:<br/>"); for (string s : requestmessage){ sendstring.append(s + "<br/>"); } sendstring.append("</body></html>"); buffer = bytebuffer.wrap(sendstring.tostring().getbytes(localcharset)); sc.write(buffer); sc.close(); } }
handleread开头先获取到对应的socketchannel和bytebuffer,就这两个最为关键,socketchannel负责与客户端的链接和传输数据,而bytebuffer充当数据运输的载体。
而后则是简单的判断连接状态,若是连接,将相关信息输出到控制台,并拼接出http头的字符串发送至客户端。
效果如图:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: AR9531 产品级适配方案(基础篇)
下一篇: Python正则表达式教程之二:捕获篇