客户端关闭套接字后,服务线程中的循环不会中断

时间:2014-11-25 05:00:56

标签: java multithreading sockets

我是初学者学习套接字编程并尝试在Java中实现简单的类似Sinatra的库。 while循环以客户端套接字关闭或不关闭为止(!this.client.isClosed()),到目前为止,工作响应最小请求,如“GET / HTTP / 1.1”,直到客户端关闭连接并且循环进入无限远循环并采取所有cpu周期,我也曾尝试添加额外的条件检查可用性客户端的InputStream返回-1(这意味着关闭?),而(!this.client.isClosed()&&(reader.available ()!= -1))。

WebServer.java

public void start(){
    try{
        while(true){
            // accept and add incoming socket connection to client bulks
            this.clients.put(++requestNumber, this.server.accept());
            // service client request in Service thread
            Service service = new Service(this);
            service.start();
        }
    }
    catch(IOException ioe){
        System.out.println(ioe.getMessage());
    }
    finally{
        // TODO: close server socket here
    }
}// end start

Service.java

@Override
public void run(){
    while(!this.client.isClosed()){
        System.out.println("read request");

        // parse http request
        this.parser.parse();
        String method = this.parser.getMethod();
        String path = this.parser.getPath();
        String httpVer = this.parser.getHttpVersion();
        System.out.println("[METHOD] : "+method);
        System.out.println("[PATH] : "+path);
        System.out.println("[HTTP VERSION] : "+httpVer);

        // match request with specified handler in handlers bulk
        for(RequestHandler handler:this.server.handlers){
            if(handler.getMethod().equals(method)){
                if(handler.getPath().equals(path)){
                    System.out.println("send response\n=============");
                    // serve request
                    handler.handle(this.reader, this.writer);
                }
            }
        }// end for loop

        // debug only
        try{Thread.currentThread().sleep(3000);}catch(InterruptedException ie){}

    }// end while loop

    // not get executed after client disconnect
    System.out.println("exit service thread");
    // remove client socket in client sockets bulk
    server.clients.remove(requestId);

}// end run

HttpParser.java

// start HttpParser
public class HttpParser{
    private BufferedInputStream in;
    private String initial;
    private String header;

    public HttpParser(BufferedInputStream in){
       this.in = in;
    }

    public void parse(){
       System.out.println("parse request");
       // pattern to match both \r\n or \n
       Pattern pattern = Pattern.compile("\r?\n", Pattern.DOTALL);
       Matcher match = null;
       Scanner scanner = null;
       StringBuilder requestBuffer = new StringBuilder("");
       String request = "";
       int nread = 0, nlength = 0;
       try{
           scanner = new Scanner(in).useDelimiter(pattern);
           while((in.available() != -1) && scanner.hasNext()){
               // read line from request
               String line = scanner.next();

               // break while loop if find empty line "\r\n\r\n" or "\n\n"
               if(line.isEmpty()){break;}

               // append line to buffer
               requestBuffer.append(line+"\r\n");
           }

           // get full request from buffer
           request = requestBuffer.toString();
           System.out.println("[REQUEST]\n"+request);

           // get initial header from request
           match = Pattern.compile("^.*?\r\n").matcher(request);
           if(match.find()){
              // assign initial header to initial member
              this.initial = match.group();
              System.out.println("[INITIAL HEADER]\n"+this.initial);
           }
       }
       catch(IOException ioe){}
   }// end parse

   // get method from initial header
   public String getMethod(){
       Pattern pattern = Pattern.compile("(^.*?)\\s+");
       Matcher match = pattern.matcher(this.initial);
       if(match.find()){
           return match.group(1);
       }
       return "GET";
   }

   // get path from initial header
   public String getPath(){
       Pattern pattern = Pattern.compile("^.*?\\s+(.+?)\\s+");
       Matcher match = pattern.matcher(this.initial);
       if(match.find()){
           return match.group(1);
       }
       return "/";
   }

   // get http version from initial header
   public String getHttpVersion(){
       Pattern pattern = Pattern.compile("^.*?\\s+.+?\\s+HTTP/(\\d\\.\\d)\\s.*");
       Matcher match = pattern.matcher(this.initial);
       if(match.find()){
           return match.group(1);
       }
       return "1.1";
   }

}
// end HttpParser

主类Jciw.java

import java.io.*;

// start Jciw
public class Jciw{  

    public static void main(String[] args){
        WebServer webServer = new WebServer(8000);
        webServer.handle_request("GET", "/", new RootGetHandler());
        webServer.handle_request("POST", "/", new RootPostHandler());
        webServer.start();
    }

    // start RootGetHandler
    public static class RootGetHandler extends RequestHandler{
        @Override
        public void handle(InputStream reader, OutputStream writer){
            try{
                writer.write(("Hello GET request\n").getBytes());
                writer.flush();
            }
            catch(IOException ioe){

            }
        }
    }
    // end RootGetHandler

    // start RootPostHandler
    public static class RootPostHandler extends RequestHandler{
        @Override
        public void handle(InputStream reader, OutputStream writer){
            try{
                writer.write(("Hello POST request\n").getBytes());
                writer.flush();
            }
            catch(IOException ioe){

            }
        }
    }
    // end RootPostHandler

}
// end Jciw

使用ant运行并从浏览器发出测试请求,记录:

run:
 [java] read request
 [java] parse request
 [java] [REQUEST]
 [java] GET / HTTP/1.1
 [java] Host: localhost:8000
 [java] User-Agent: Mozilla/5.0 (X11; Linux i686; rv:22.0) Gecko/20100101 Firefox/22.0 SeaMonkey/2.19
 [java] Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
 [java] Accept-Language: en-US,en;q=0.5
 [java] Accept-Encoding: gzip, deflate
 [java] Connection: keep-alive
 [java] 
 [java] [INITIAL HEADER]
 [java] GET / HTTP/1.1
 [java] 
 [java] [METHOD] : GET
 [java] [PATH] : /
 [java] [HTTP VERSION] : 1.1
 [java] send response
 [java] =============

关闭标签页或断开与客户端的连接后,它会进入无限循环,而客户端套接字已关闭,而(!this.client.isClosed())应评估为false,服务线程永不退出,日志:

 [java] read request
 [java] parse request
 [java] [REQUEST]
 [java] 
 [java] [METHOD] : GET
 [java] [PATH] : /
 [java] [HTTP VERSION] : 1.1
 [java] send response
 [java] =============
 [java] read request
 [java] parse request
 [java] [REQUEST]
 [java] 
 [java] [METHOD] : GET
 [java] [PATH] : /
 [java] [HTTP VERSION] : 1.1
 [java] send response
 [java] =============
 [java] read request
 [java] parse request
 [java] [REQUEST]
 [java] 
 [java] [METHOD] : GET
 [java] [PATH] : /
 [java] [HTTP VERSION] : 1.1
 [java] send response
 [java] =============
 [java] read request
 [java] parse request
 [java] [REQUEST]
 [java] 
 [java] [METHOD] : GET
 [java] [PATH] : /
 [java] [HTTP VERSION] : 1.1
 [java] send response
 [java] =============
 [java] read request
 [java] parse request
 [java] [REQUEST]
 [java] 
 [java] [METHOD] : GET
 [java] [PATH] : /
 [java] [HTTP VERSION] : 1.1
 [java] send response
 [java] =============
 [java] read request
 [java] parse request
 [java] [REQUEST]
 [java] 
 [java] [METHOD] : GET
 [java] [PATH] : /
 [java] [HTTP VERSION] : 1.1
 [java] send response
 [java] =============

2 个答案:

答案 0 :(得分:1)

  1. 您的循环条件不正确。 Socket.isClosed()仅告诉您是否已关闭套接字。要检测对等方的关闭,您必须检查您正在使用的任何读取方法的返回值:

    • read()返回-1
    • readLine()返回null
    • readObject()和所有其他readXXX()方法抛出EOFException.

    当你得到任何这些时,你必须关闭套接字并纾困。

  2. 毫无疑问,你的写作会得到IOException: connection reset,但是因为你忽略了所有IOExceptions你没有看到它们。不要忽视IOExceptions。只有一个对连接不致命,并且在读取时为SocketTimeoutException。当你得到任何其他人时,你必须关闭套接字并纾困。

  3. (in.available() != -1)的测试完全没有意义。它永远不会返回-1,并且它没有返回任何表示流结束的内容。

答案 1 :(得分:-1)

谢谢@EJP,解决了,我添加了辅助方法来检查while循环条件下的客户端连接

private boolean isClosed(){
    int nread = 0;
    try{
        this.reader.mark(1);
        nread = this.reader.read();
        this.reader.reset();
    }
    catch(IOException ioe){
        //TODO: when something goes wrong
    }
    return ( (nread != -1)?false:true );
}
...
while(!isClosed()){
    ...
}