欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java使用NioSocket手动实现HTTP服务器

程序员文章站 2024-02-24 22:33:40
niosocket简单复习 重要概念 niosocket里面的三个重要概念:buffer、channel、selector buffer为要传输的数据...

niosocket简单复习

重要概念

niosocket里面的三个重要概念:buffer、channel、selector

  1. buffer为要传输的数据
  2. channel为传输数据的通道
  3. selector为通道的分配调度者

使用步骤

使用niosocket实现通信大概如以下步骤:

  1. serversocketchannel可以通过configureblocking方法来设置是否采用阻塞模式,设置为false后就可以调用register注册selector,阻塞模式下不可以用selector。
  2. 注册后,selector就可以通过select()来等待请求,通过参数设置等待时长,若传入参数0或者不传入参数,将会采用阻塞模式直到有请求出现。
  3. 接收到请求后selector调用selectedkeys方法,返回selectedkey集合。
  4. selectedkey保存了处理当前请求的channel和selector,并提供了不同的操作类型。四种操作属性:selectedkey.op_accept、selectedkey.op_connect、selectedkey.op_read、selectedkey.op_write。
  5. 通过selectedkey的isacceptable、isconnectable、isreadable和iswritable来判断操作类型,并处理相应操作。
  6. 在相应的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头的字符串发送至客户端。

效果如图:

Java使用NioSocket手动实现HTTP服务器
Java使用NioSocket手动实现HTTP服务器

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。