Tcplistener随机重置连接

3
我正在尝试使用F#编写一个简单的Web服务器。我发现在Firefox中刷新页面时,经过几次尝试后,会出现一个错误页面,上面显示:

连接被重置

当页面正在加载时,与服务器的连接被重置了。

为什么这种情况似乎是随机发生的(将积压队列增加到1000并没有帮助)?此外,为什么Firefox是唯一显示我的Web服务器响应的浏览器?我猜我的响应不合法,是吗?

Web服务器的代码:

module Program

open System
open System.Net.Sockets
open System.IO

type TcpListener with
   member this.AsyncAcceptSocket =
    Async.FromBeginEnd(this.BeginAcceptSocket, this.EndAcceptSocket)

type Main =
    static member Init () =
        let tcpListener = new TcpListener(80)        
        tcpListener.Start()

        let rec loop =
            async{                                
                let! socket = tcpListener.AsyncAcceptSocket
                Async.Start(loop)
                let stream = new NetworkStream(socket)
                let streamWriter = new StreamWriter(stream)                
                
                streamWriter.WriteLine("HTTP/1.0 200 OK");
                streamWriter.WriteLine("Date: Fri, 15 Nov 2011 23:59:59 GMT");
                streamWriter.WriteLine("Content-Type: text/html");
                streamWriter.WriteLine("Content-Length: 13540");
                streamWriter.WriteLine("")
                streamWriter.WriteLine("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">");
                streamWriter.WriteLine("<html>");
                streamWriter.WriteLine("<body>");
                streamWriter.WriteLine("<h1>This is the title</h1>");
                streamWriter.WriteLine("</body>");
                streamWriter.Write("</html>");                
                streamWriter.Flush()                        
                stream.Close()
                socket.Close()
            }

        Async.Start(loop)

        Console.ReadLine()

Main.Init()

编辑

看起来问题与我在之前的解决方案中调用loop方式无关。我将程序简化为以下内容(但问题仍然存在):

module Program

open System
open System.Net.Sockets
open System.IO

let tcpListener = new TcpListener(80)        
tcpListener.Start()

while true do                            
    let socket = tcpListener.AcceptSocket()
    let stream = new NetworkStream(socket)
    let streamWriter = new StreamWriter(stream)                
                
    streamWriter.WriteLine("response");             
    streamWriter.Flush()                        
    stream.Close()
    socket.Close()

Console.ReadLine()

4
这个调用 Async.Start(loop) 确实看起来有问题。 - ildjarn
2
你的内容长度不正确,这可能是原因吗? - Jon Skeet
调用 Async.Start 看起来有点奇怪,但实际上我认为它在技术上没有任何问题(只是看起来有点令人困惑)。 - Tomas Petricek
@Tomas:我的意思是第一个,不是第二个。在loop的第2行调用Async.Start肯定不能按预期工作——那不是会尝试生成无限数量的套接字吗?或者我可能错过了关于TcpListener行为的一些细节…… - ildjarn
@ildjarn 我认为这是可以的,因为它会在套接字被接受后生成工作流程(意味着客户端已连接)。然后,其余的工作流程将处理当前客户端,并且生成的工作流程已准备好接受新的客户端... - Tomas Petricek
3个回答

4
这是一个可用的版本。建议使用TcpClient而不是socket,这样就不必管理其底层流。
module Program

open System
open System.Net
open System.Net.Sockets
open System.IO

type TcpListener with
   member this.AsyncAcceptTcpClient() =
    Async.FromBeginEnd(this.BeginAcceptTcpClient, this.EndAcceptTcpClient)

type Main =
    static member Init() =
        let tcpListener = new TcpListener(IPAddress.Loopback, 80)
        tcpListener.Start()

        let writeContent() =
            let stream = new MemoryStream()
            let streamWriter = new StreamWriter(stream)
            streamWriter.WriteLine("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">")
            streamWriter.WriteLine("<html>")
            streamWriter.WriteLine("<body>")
            streamWriter.WriteLine("<h1>This is the title</h1>")
            streamWriter.WriteLine("</body>")
            streamWriter.Write("</html>")
            streamWriter.Flush()
            stream

        let rec loop() =
            async {
                use! tcp = tcpListener.AsyncAcceptTcpClient()
                let stream = tcp.GetStream()
                use streamWriter = new StreamWriter(stream)
                use content = writeContent()

                streamWriter.WriteLine("HTTP/1.0 200 OK")
                streamWriter.WriteLine("Date: Fri, 15 Nov 2011 23:59:59 GMT")
                streamWriter.WriteLine("Content-Type: text/html")
                streamWriter.WriteLine("Content-Length: {0}", content.Length)
                streamWriter.WriteLine("")
                streamWriter.Flush()

                content.WriteTo stream
                stream.Flush()
                return! loop()
            }

        Async.Start(loop())

        Console.ReadLine() |> ignore

Main.Init()

1

你想要的是

type TcpListener with
   member this.AsyncAcceptSocket() =
    Async.FromBeginEnd(this.BeginAcceptSocket, this.EndAcceptSocket)

type Main =
    static member Init () =
        let tcpListener = new TcpListener(80)        
        tcpListener.Start()

        let rec loop() =
            async{                                
                let! socket = tcpListener.AsyncAcceptSocket()
                let stream = new NetworkStream(socket)
                let streamWriter = new StreamWriter(stream)                

                streamWriter.WriteLine("HTTP/1.0 200 OK");
                streamWriter.WriteLine("Date: Fri, 15 Nov 2011 23:59:59 GMT");
                streamWriter.WriteLine("Content-Type: text/html");
                streamWriter.WriteLine("Content-Length: 13540");
                streamWriter.WriteLine("")
                streamWriter.WriteLine("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">");
                streamWriter.WriteLine("<html>");
                streamWriter.WriteLine("<body>");
                streamWriter.WriteLine("<h1>This is the title</h1>");
                streamWriter.WriteLine("</body>");
                streamWriter.Write("</html>");                
                streamWriter.Flush()                        
                stream.Close()
                socket.Close()
                return! loop()
            }

        Async.Start(loop())

        Console.ReadLine()

Main.Init()

有几个更改:

  1. loop 是一个函数,因此在定义和调用时都需要接受 unit、()(与 AsyncAcceptSocket 相同)
  2. 您的异步循环需要以 return! loop() 结束
  3. AsyncAcceptSocket 返回后,您不需要调用 Async.Start

虽然这些更改是好的,但如果没有设置正确的内容长度,它仍然无法正常工作。 - gradbot
我从未手动生成过HTML响应;那些只是引起我注意的事情。 - Daniel
我不认为这些更改会有问题。1)只要它们不捕获任何状态,你可以多次启动异步工作流程;3)在接受套接字后调用“Async.Start”,您立即开始侦听新套接字,因此可以实现并行处理;2)由于他早先使用了“Async.Start”等待下一个套接字,所以“loop”函数在结尾处不需要循环。尽管如此,我认为OP使用的风格有点令人困惑。 - Tomas Petricek
没错。我在他的代码中没有发现明显的问题,但我认为这更好地表达了他的意图。因此我的引言是:“我认为这就是你想要的”。 - Daniel

1

这并不是你问题的答案,只是关于循环和Async.Start的评论(我认为gradbot的版本可能可以回答你的主要问题)。

无论如何,与Daniel和gradbot相反,我认为你使用Async.Start并没有错,但可能有点令人困惑(命名也有点混乱)。

  • 你实现的方式是等待一个socket,然后启动一个新的异步(立即准备好接受新的socket),然后处理其余的工作。这意味着你可以并行处理请求。

  • gradbot和Daniel实现的方式是接受一个socket,向调用者发送回复,然后等待另一个socket。这意味着处理是顺序的!

我认为混淆来自于你的写法 - 我可能会将当前socket的处理作为新的异步工作流开始处理,然后等待下一个socket(我认为这更容易理解,但我认为你的版本也是正确的):

// Asynchronous function that handles communication with a single client
let handleClient (tcp:TcpClient) = 
  async { 
    try
      let stream = tcp.GetStream() 
      use streamWriter = new StreamWriter(stream) 
      use content = writeContent() 

      streamWriter.WriteLine("HTTP/1.0 200 OK") 
      // Some stuff omitted
      streamWriter.Flush() 

      content.WriteTo stream 
      stream.Flush() 
    finally
      tcp.Dispose() 
  } 

let mainLoop = 
  async {     
    while true do
      // Wait for a client and start async workflow to process it
      let! tcp = tcpListener.AsyncAcceptTcpClient() 
      Async.Start(handleClient tcp) }

谢谢。事实上,我最初的意图是并行处理请求,但您的解决方案比我的清晰得多。 - Attila Kun

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接