2013-02-26 102 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(); 
相關問題