2013-02-26 76 views
0

我试图创建一个基本的HTTP/1.1兼容的Web服务器,它支持持久连接的简单GET请求。我得到一个SocketException:在第61行发生连接重置错误(如果(行== == || line.equals(“”))。我测试它通过运行它,然后将我的浏览器指向本地主机portnumber。当我用一个带有多个图像的页面测试它时,似乎只有一个请求正在异常发生前处理,但我不确定有什么问题,因为这是我第一次尝试在任何类型的套接字编程中使用。除去DataOutputStream类后,我更新的代码:基本的Java Web服务器 - 接收SocketException:连接重置

import java.io.BufferedReader; 
import java.io.ByteArrayOutputStream; 
import java.io.File; 
import java.io.FileInputStream; 
import java.io.InputStreamReader; 
import java.io.ObjectOutputStream; 
import java.io.PrintWriter; 
import java.net.ServerSocket; 
import java.net.Socket; 
import java.net.URI; 
import java.net.URISyntaxException; 
import java.text.SimpleDateFormat; 
import java.util.Date; 
import java.util.HashMap; 
import java.util.StringTokenizer; 

public class webserve 
{ 
    public static void main(String[] args) throws Exception 
    { 
     String rootPath = "~/Documents/MockWebServerDocument/"; 
     int port = 10000; 

     if(rootPath.startsWith("~" + File.separator)) 
     { 
      rootPath = System.getProperty("user.home") + rootPath.substring(1); 
     } 

     String requestLine=""; 
     StringTokenizer tokens=null; 
     String line, command; 
     Date date = new Date(); 
     String connectionStatus=""; 


     //Create new server socket listening on specified port number 
     ServerSocket serverSocket = new ServerSocket(port); 

     while(true) 
     { 
      //Wait for a client to connect and make a request 
      Socket connectionSocket = serverSocket.accept(); 
      System.out.println("Socket opened"); 

      //Input stream from client socket 
      BufferedReader incomingFromClient = new BufferedReader(new InputStreamReader(connectionSocket.getInputStream())); 
      //PrintWriter to send header to client socket 
      PrintWriter outgoingHeader = new PrintWriter(connectionSocket.getOutputStream(),true); 
      //OutputStream to send file data to client socket 
      ObjectOutputStream outgoingFile = new ObjectOutputStream(connectionSocket.getOutputStream()); 
      //Date format for HTTP Header 
      SimpleDateFormat HTTPDateFormat = new SimpleDateFormat("EEE MMM d hh:mm:ss zzz yyyy"); 


      //Create a HashMap to store the request header information 
      HashMap<String,String> requestHeader = new HashMap<String,String>(); 

      while(connectionSocket.isConnected()) 
      { 
       //requestHeader.clear(); 

       while((line = incomingFromClient.readLine()) != null) 
       { 
        if(line.isEmpty()) 
        { 
         break; 
        } 
        //If this is the first line of the request, i.e doesnt contain a colon 
        if(!(line.contains(":"))) 
        { 
         requestLine = line; 
         requestHeader.put("Request", requestLine); 
        } 
        else 
        { 
         //Otherwise, find the colon in the line and create a key/value pair for the HashMap 
         int index = line.indexOf(':')+2; 
         String header = line.substring(0,index-1); 
         line = line.substring(index).trim(); 

         requestHeader.put(header, line); 

         System.out.println(header + " " + line); 
        } 
       } 

       connectionStatus = (String)requestHeader.get("Connection:"); 
       requestLine = (String)requestHeader.get("Request"); 

       System.out.println("RequestLine: " + requestLine); 

       if(!requestLine.equals("")||!(requestLine.equals(null))) 
       { 
        tokens = new StringTokenizer(requestLine); 

        command = tokens.nextToken(); 
        String filename = tokens.nextToken(); 
        filename = cleanUpFilename(filename); 
        String fullFilepath = rootPath + filename; 
        System.out.println("Full FilePath: " + fullFilepath); 

        File file = new File(fullFilepath); 

        //Get the number of bytes in the file 
        int numOfBytes=(int)file.length(); 

        //Open a file input stream using the full file pathname 
        FileInputStream inFile = new FileInputStream(fullFilepath); 

        //Create byte array to hold file contents 
        byte[] fileInBytes = new byte[numOfBytes]; 

        inFile.read(fileInBytes,0,numOfBytes); 

        inFile.close(); 


        //Write the header to the output stream 
        outgoingHeader.print("HTTP/1.1 200 OK\r\n"); 
        outgoingHeader.print("Date: " + HTTPDateFormat.format(date)+"\r\n"); 
        outgoingHeader.print("Server: BC-Server\r\n"); 
        outgoingHeader.print("Last-Modified: " + HTTPDateFormat.format(file.lastModified())+"\r\n"); 
        outgoingHeader.print("Connection: keep-alive\r\n"); 
        outgoingHeader.print("Content-Length: " + numOfBytes); 
        outgoingHeader.print("\r\n\r\n");    

        //When the header has been printed, write the byte array containing the file 
        //to the output stream 
        outgoingFile.writeObject(fileInBytes); 

        if(!(connectionStatus.equals("keep-alive"))) 
        { 
         System.out.println("Closing: " + connectionStatus); 
         outgoingHeader.close(); 
         outgoingFile.close(); 
         break; 
        } 
        else 
         continue; 

       }  

      } 

     } 
    } 

    public static String cleanUpFilename(String filename) 
    { 
     //If there is a "/" at the start of the filename, then remove it 
     if(filename.charAt(0) == '/') 
     { 
      filename = filename.substring(1); 
     } 

     //If we are given an absolute URI request, strip all characters 
     //before the third "/" 
     if(filename.startsWith("http://")); 
     { 
      try 
      { 
       URI httpAddress = new URI(filename); 

       //Get the path from the supplied absolute URI, that is remove 
       //all character before the third "/" 
       filename = httpAddress.getPath(); 

       //Again, we may have to trim this modified address if there is an 
       //extra "/" at the start of the filename 
       if(filename.charAt(0) == '/') 
       { 
        filename = filename.substring(1); 
       } 
      } 
      catch (URISyntaxException e) 
      {     
       e.printStackTrace(); 
      } 
     } 

     return filename; 
    } 

}

这里是我的错误跟踪:

Exception in thread "main" java.net.SocketException: Connection reset 
    at java.net.SocketInputStream.read(SocketInputStream.java:185) 
    at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:282) 
    at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:324) 
    at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:176) 
    at java.io.InputStreamReader.read(InputStreamReader.java:184) 
    at java.io.BufferedReader.fill(BufferedReader.java:153) 
    at java.io.BufferedReader.readLine(BufferedReader.java:316) 
    at java.io.BufferedReader.readLine(BufferedReader.java:379) 
    at webserve.main(webserve.java:61) 

任何帮助将不胜感激,因为我在完全损失。

+0

61行是哪一行? – 2013-02-26 23:02:54

+0

我的歉意,我是新来的。 第61行是:if(line == null || line.equals(“”))在while循环内 – JCutz 2013-02-26 23:13:17

+0

不,它不是。它必须是'readLine()'调用。查看堆栈跟踪。 – EJP 2013-02-27 00:50:58

回答

0

尝试使用telnet,wget或curl而不是chrome来测试连接,因为您可以控制双方的TCP/IP连接。

我认为您的网络客户端正在关闭它的连接,并且您尝试再次从该套接字读取(是的,即使isConnected()在远程方关闭连接时也会引发此错误)。我也很抱歉地说,没有简单的方法来对付这个问题,而是抓住例外情况并优雅地处理它。

这是一个经常发生在同步套接字上的问题。尝试使用java.nio通道和选择器。

+0

我试过在telnet中运行,只要我通过GET请求传递“Connection:keep-alive”标头,并且如果通过“Connection:close”,它就会正确关闭。 – JCutz 2013-02-26 23:12:13

+0

使用telnet时,如果在传递保持活动状态并关闭telnet会话后会发生什么情况?换句话说,您的服务器如何处理在第一次初始响应之后打开由客户端关闭的连接? – RudolphEst 2013-02-26 23:43:59

+0

我刚刚尝试过这一点,它在我尝试将文件写入客户端的位置产生了SocketException:Broken Pipe异常。任何想法为什么这样做? – JCutz 2013-02-27 00:45:20

-1

您不能使用DataOutputStream,它用于Java-Java通信。尝试Writer用于编写标题,以及用于编写文件内容的原始OutputStream。

发生什么事是浏览器看到无效的响应,并关闭连接。服务器仍在写信给客户端,客户端响应RST,因为连接已经结束。

+0

感谢您的信息。我上面编辑了我的原始代码。这似乎消除了我原来的异常,但导致浏览器挂起。当我停止服务器的执行时,我要测试的网页的html外壳就会出现。 – JCutz 2013-02-27 00:50:04

+0

你也不能使用ObjectOutputStream。直接写文件内容到'connectionSocket.getOutputStream()' – irreputable 2013-02-27 00:54:39

+0

@JCutz这个答案是不正确的。只要不使用除write()和writeBytes()方法以外的其他任何东西,或者*对等体都以网络字节顺序理解相同的二进制协议,您就可以使用'DataOutputStream'。唯一的特定于Java的方法是'writeUTF()'。原始代码中没有任何东西会导致对等体出现故障。 – EJP 2013-02-27 01:03:17

0

同时使用多个输出流是非常成问题的。在这种情况下,您不应创建ObjectOutputStream,直到您确定要写入对象并且已经写入并刷新标题为止,因为ObjectOutputStream会将写入标头写入输出中,在您的当前代码中它将出现在任何标题之前,可能会导致客户bar喝。

通常,SocketException: Connection Reset通常表示您已写入已由对等方关闭的连接。在这种情况下,对等端是客户端,而客户端是Web浏览器,它可以表示任何东西,例如,用户停止加载页面,他浏览了一下,退出浏览器,关闭了标签页。这不是你的问题。只需关上插座并忘记它。

出于同样的原因,你的服务器也应该设置一个合理的读取超时时间,比如说10-30秒,并且如果它触发,就进行保护。

+0

好吧,有没有什么办法可以让我编写头文件并发送文件字节。我是否应该使用单独的输出流,但只在打算写对象时才打开ObjectOutputStream? – JCutz 2013-02-27 01:15:28

+0

这就是我所说的,但是如果你的客户端是一个浏览器,你将不会发送对象,所以你根本不需要一个'ObjectOutputStream'。 – EJP 2013-02-27 02:09:01

0

你的服务器最明显的问题是它不是多线程的。重新阅读你对问题的描述后,这似乎是根本原因。每个连接需要一个线程。在serverSocket.accept()之后,创建一个新的线程来处理connectionSocket。

while(true) 
    { 
     //Wait for a client to connect and make a request 
     Socket connectionSocket = serverSocket.accept(); 

     new Thread() 
     { 
      public void run() 
      { 
       //Input stream from client socket 
       BufferedReader incomingFromClient = ... 

       etc 

      } 
     }.start(); 
相关问题