java - 多线程 Java Web 服务器 - java.net.SocketTimeoutException

标签 java multithreading sockets webserver

我实现了一个简单的符合 HTTP/1.1 的多线程 Web 服务器,用于处理 GET 和 HEAD 请求。当我通过 Web 服务器发出请求时,虽然它可以工作,但在我设置的 12 秒超时后,我收到了 SocketTimeoutException。

我正在测试我的网络服务器,方法是在 Eclipse 中运行它并将浏览器定向到 localhost:portnumber,然后尝试在本地打开文件。我只有超时值,因为如果没有它,任何读取不存在的文件的请求都不会返回,而它应该返回 404 Not Found 错误。

我收到的 SocketTimeoutException 数量等于为处理请求而打开的套接字数量。我怀疑我应该以某种方式处理这个异常,但我不确定在哪里或如何处理。任何有关如何处理此问题的示例或解释都会非常有用。

我的代码被分成一个简短的网络服务器组件,后面是一个单独的 ThreadHandler 类来处理请求。当我创建一个新的客户端套接字时,我使用一个新线程来处理请求。如果需要,我可以提供 ThreadHandler 类,但它要长得多。

这是 WebServer 组件:

public class MyWebServer
{
    public static void main(String[] args) throws Exception
    {

        int port = 5000;
        String rootpath = "~/Documents/MockWebServerDocument/";

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

        File testFile = new File(rootpath);

        //If the provided rootpath doesn't exist, or isn't a directory, exit
        if(!testFile.exists() || !testFile.isDirectory())
        {
            System.out.println("The provided rootpath either does not exist, or is not a directory. Exiting!");
            System.exit(1);
        }

        //Create the server socket
        ServerSocket serverSocket = new ServerSocket(port);

        //We want to process requests indefinitely, listen for connections inside an infinite loop
        while(true)
        {
            //The server socket waits for a client to connect and make a request. The timeout ensures that
            //a read request does not block for more than the allotted period of time. 
            Socket connectionSocket = serverSocket.accept();
            connectionSocket.setSoTimeout(12*1000);

            //When a connection is received, we want to create a new HandleRequest object
            //We pass the newly created socket as an argument to HandleRequest's constructor
            HandleThreads request = new HandleThreads(connectionSocket, rootpath);

            //Create thread for the request
            Thread requestThread = new Thread(request);

            System.out.println("Starting New Thread");

            //Start thread
            requestThread.start();
        }
    }

}

在 ThreadHandler 类中,我从套接字读取请求,解析请求并通过套接字以适当的响应进行响应。我已经实现了持久连接,因此只有当请求中包含“Connection: close” token 时,每个套接字才会关闭。但是,我不确定这种情况是否正确发生,特别是在我尝试打开不存在的文件并应返回 404 Not Found 错误的情况下。

有谁知道如何处理这些异常。我应该做些什么来关闭线程吗?

任何帮助将不胜感激。

编辑:这是我从 run() 中的 try catch 语句中调用的handleRequest()

//This method handles any requests received through the client socket
    private void handleRequest() throws Exception
    {
        //Create outputStream to send data to client socket
        DataOutputStream outToClient = new DataOutputStream(clientsocket.getOutputStream());
        //Create BufferedReader to read data in from client socket
        BufferedReader inFromClient = new BufferedReader(new InputStreamReader(clientsocket.getInputStream()));
        //Create SimpleDateFormat object to match date format expected by HTTP
        SimpleDateFormat HTTPDateFormat = new SimpleDateFormat("EEE MMM d hh:mm:ss zzz yyyy");

        //Keep running while the socket is open
        while(clientsocket.isConnected())
        {

                String line = null;
                //HashMap to record relevant header lines as they are read
                HashMap<String,String> requestLines = new HashMap<String,String>();
                String ifModSince = null;
                Date ifModifiedSince = null;
                Date lastModifiedDate = null;

                //Keep reading the request lines until the end of the request is signalled by a blank line
                while ((line = inFromClient.readLine()).length() != 0) 
                {
                    //To capture the request line
                    if(!line.contains(":"))
                    {
                        requestLines.put("Request", line);
                    }

                    //To capture the connection status
                    if(line.startsWith("Connection:"))
                    {
                        int index = line.indexOf(':');
                        String connectionStatus = line.substring(index + 2);
                        requestLines.put("Connection", connectionStatus);
                    }

                    //To capture the if-modified-since date, if present in the request
                    if(line.startsWith("If-Modified-Since"))
                    {
                        int index = line.indexOf(':');
                        ifModSince = line.substring(index + 2);
                        requestLines.put("If-Modified-Since", ifModSince);
                    }

                    System.out.println(line);

                }

                //Get the request line from the HashMap
                String requestLine = (String)requestLines.get("Request");
                //Create Stringtokenizer to help separate components of the request line
                StringTokenizer tokens = new StringTokenizer(requestLine);

                //If there are not 3 distinct components in the request line, then the request does
                //not follow expected syntax and we should return a 400 Bad Request error
                if(tokens.countTokens() != 3)
                {
                    outToClient.writeBytes("HTTP/1.1 400 Bad Request"+CRLF);
                    outToClient.writeBytes("Content-Type: text/html"+CRLF);
                    outToClient.writeBytes("Server: BCServer/1.0"+CRLF);
                    outToClient.writeBytes("Connection: keep-alive"+CRLF);
                    outToClient.writeBytes(CRLF);
                    outToClient.writeBytes("<html><head></head><body>Error 400 - Bad Request</body></html>"+CRLF);

                    outToClient.flush();
                }
                else
                {
                    //Get the specific request, whether "GET", "HEAD" or unknown
                    String command = tokens.nextToken();
                    //Get the filename from the request
                    String filename = tokens.nextToken();

                    //Tidy up the recovered filename. This method can also tidy up absolute
                    //URI requests
                    filename = cleanUpFilename(filename);

                    //If the third token does not equal HTTP/1.1, then the request does 
                    //not follow expected syntax and we should return a 404 Bad Request Error
                    if(!(tokens.nextElement().equals("HTTP/1.1")))
                    {
                        outToClient.writeBytes("HTTP/1.1 400 Bad Request"+CRLF);
                        outToClient.writeBytes("Content-Type: text/html"+CRLF);
                        outToClient.writeBytes("Server: BCServer/1.0"+CRLF);
                        outToClient.writeBytes("Connection: keep-alive"+CRLF);
                        outToClient.writeBytes(CRLF);
                        outToClient.writeBytes("<html><head></head><body>Error 400 - Bad Request</body></html>"+CRLF);
                        outToClient.flush();                    
                    }
                    else
                    {
                        //Add the supplied rootpath to the recovered filename
                        String fullFilepath = rootpath + filename;

                        //Create a new file using the full filepathname
                        File file = new File(fullFilepath);

                        //If the created file is a directory then we look to return index.html
                        if(file.isDirectory())
                        {
                            //Add index.html to the supplied rootpath
                            fullFilepath = rootpath + "index.html";

                            //Check to see if index.html exists. If not, then return Error 404: Not Found
                            if(!new File(fullFilepath).exists())
                            {
                                outToClient.writeBytes("HTTP/1.1 404 Not Found"+CRLF);
                                outToClient.writeBytes("Content-Type: text/html"+CRLF);
                                outToClient.writeBytes("Server: BCServer/1.0"+CRLF);
                                outToClient.writeBytes("Connection: keep-alive"+CRLF);
                                outToClient.writeBytes(CRLF);
                                outToClient.writeBytes("<html><head></head><body>Error 404 - index.html was not found</body></html>"+CRLF);
                                outToClient.flush();
                            }
                        }
                        //If the created file simply does not exist then we need to return Error 404: Not Found
                        else if(!file.exists())
                        {
                            System.out.println("File Doesn't Exist!");
                            outToClient.writeBytes("HTTP/1.1 404 Not Found"+CRLF);
                            outToClient.writeBytes("Content-Type: text/html"+CRLF);
                            outToClient.writeBytes("Server: BCServer/1.0"+CRLF);
                            outToClient.writeBytes("Connection: keep-alive"+CRLF);
                            outToClient.writeBytes(CRLF);
                            outToClient.writeBytes("<html><head></head><body>Error 404 - " + filename + " was not found</body></html>"+CRLF);
                            outToClient.flush();

                        }
                        //Otherwise, we have a well formed request, and we should use the specific command to
                        //help us decide how to respond
                        else
                        {
                            //Get the number of bytes in the file
                            int numOfBytes=(int)file.length();

                            //If we are given a GET request
                            if(command.equals("GET"))
                            {
                                //Open a file input stream using the full file pathname
                                FileInputStream inFile = new FileInputStream(fullFilepath);

                                //Create an array of bytes to hold the data from the file
                                byte[] fileinBytes = new byte[numOfBytes];

                                //We now check the If-Modified-Since date (if present) against the file's
                                //last modified date. If the file has not been modified, then return 304: Not Modified
                                if(ifModSince != null)
                                {
                                    //Put the string version of If-Modified-Since data into the HTTPDate Format
                                    try
                                    {
                                        ifModifiedSince = HTTPDateFormat.parse(ifModSince);
                                    }
                                    catch(ParseException e)
                                    {
                                        e.printStackTrace();
                                    }

                                    //We now need to do a bit of rearranging to get the last modified date of the file
                                    //in the correct HTTP Date Format to allow us to directly compare two date object

                                    //1. Create a new Date using the epoch time from file.lastModified()
                                    lastModifiedDate = new Date(file.lastModified());
                                    //2. Create a string version, formatted into our correct format
                                    String lastMod = HTTPDateFormat.format(lastModifiedDate);

                                    lastModifiedDate = new Date();
                                    //3. Finally, parse this string version into a Date object we can use in a comparison
                                    try
                                    {
                                        lastModifiedDate = HTTPDateFormat.parse(lastMod);
                                    }
                                    catch (ParseException e)
                                    {
                                        e.printStackTrace();
                                    }

                                    //Comparing the last modified date to the "If-Modified Since" date, if the last modified
                                    //date is before modified since date, return Status Code 304: Not Modified
                                    if((ifModifiedSince != null) && (lastModifiedDate.compareTo(ifModifiedSince) <= 0))
                                    {
                                        System.out.println("Not Modified!");
                                        //Write the header to the output stream 
                                        outToClient.writeBytes("HTTP/1.1 304 Not Modified"+CRLF);
                                        outToClient.writeBytes("Date: " + HTTPDateFormat.format(new Date())+CRLF);
                                        outToClient.writeBytes("Server: BCServer/1.0"+CRLF);
                                        outToClient.writeBytes("Last-Modified: " + lastModifiedDate+CRLF);
                                        outToClient.writeBytes("Content-Length: " + (int)file.length()+CRLF);
                                        outToClient.writeBytes(CRLF);
                                    }                                   

                                }
                                else
                                {
                                    //Read in the data from the file using the input stream and store in the byte array
                                    inFile.read(fileinBytes);

                                    //Write the header to the output stream 
                                    outToClient.writeBytes("HTTP/1.1 200 OK"+CRLF);
                                    outToClient.writeBytes("Date: " + HTTPDateFormat.format(new Date())+CRLF);
                                    outToClient.writeBytes("Server: BCServer/1.0"+CRLF);
                                    outToClient.writeBytes("Connection: keep-alive"+CRLF);
                                    outToClient.writeBytes("Last-Modified: " + HTTPDateFormat.format(file.lastModified())+CRLF);
                                    outToClient.writeBytes("Content-Length: " + numOfBytes +CRLF);
                                    outToClient.writeBytes(CRLF);

                                    //Write the file
                                    outToClient.write(fileinBytes,0,numOfBytes);
                                    outToClient.flush();                                    
                                }

                            }   
                            //If we are given a HEAD request
                            else if(command.equals("HEAD"))
                            {
                                //Write the header to the output stream 
                                outToClient.writeBytes("HTTP/1.1 200 OK"+CRLF);
                                outToClient.writeBytes("Date: " + HTTPDateFormat.format(new Date())+CRLF);
                                outToClient.writeBytes("Server: BCServer/1.0"+CRLF);
                                outToClient.writeBytes("Connection: keep-alive"+CRLF);
                                outToClient.writeBytes("Last-Modified: " + HTTPDateFormat.format(file.lastModified())+CRLF);
                                outToClient.writeBytes("Content-Length: " + numOfBytes +CRLF);
                                outToClient.writeBytes(CRLF);

                                outToClient.flush();
                            }
                            //If the command is neither GET or HEAD, then this type of request has
                            //not been implemented. In this case, we must return Error 501: Not Implemented
                            else
                            {
                                //Print the header and error information            
                                outToClient.writeBytes("HTTP/1.1 501 Not Implemented"+CRLF);
                                outToClient.writeBytes("Date: " + HTTPDateFormat.format(new Date())+CRLF);
                                outToClient.writeBytes("Server: BCServer/1.0"+CRLF);
                                outToClient.writeBytes("Connection: keep-alive"+CRLF);
                                outToClient.writeBytes("Content-Type: text/html"+CRLF);
                                outToClient.writeBytes(CRLF);
                                outToClient.writeBytes("<html><head></head><body> Desired Action Not Implemented </body></html>"+CRLF);

                                outToClient.flush();

                            }
                        }                                               
                    }
                }

                //Get the connection status for this request from the HashMap
                String connect = (String)requestLines.get("Connection");
                //If the connection status is not "keep alive" then we must close the socket
                if(!connect.equals("keep-alive"))
                {
                    // Close streams and socket.
                    outToClient.close();
                    inFromClient.close();
                    clientsocket.close();                   
                }
                //Otherwise, we can continue using this socket
                //else
                //{                 
                    //continue;
                //}
            }
    }   

最佳答案

设置读取超时的原因是为您准备等待对等方向您发送数据的时间设置上限。只有您知道该限制应该是多少,以及您准备重试读取的频率(如果有的话:很可能一次超时就足够了),以及多长时间才算太长,但在某些时候您会决定并关闭连接。大多数 HTTP 服务器都将其配置为可配置,以便用户可以决定。

关于java - 多线程 Java Web 服务器 - java.net.SocketTimeoutException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15148917/

相关文章:

java - 如何在 Java 中装饰一个对象

java - 为什么 ObjectOutputStream.writeObject 不采用可序列化?

java - 谷歌地图 Android 缩放到国家

multithreading - 使用clEnqueueMapBuffer和 'querying whether the command has finished'的OpenCL主机的内存可见性

sockets - 我可以在我的计算机上获取其他主机的 TCP 数据包吗?

java - 覆盖 xml 文件中的 CDI 配置

c - 互斥锁和解锁的一些解释

android - 在特定的时间间隔内无限期地在单独的线程中运行代码 iOS

c++ - 套接字的有效性

c# - 在 .net 中处理大量 TCP 客户端连接