从 Android 发送文件时从 WCF 服务重置连接

Posted

技术标签:

【中文标题】从 Android 发送文件时从 WCF 服务重置连接【英文标题】:Connection reset from WCF service when sending file from Android 【发布时间】:2021-01-31 06:16:27 【问题描述】:

我正在与连接重置问题作斗争,我希望有人可以帮助我诊断它。我们有一个 WCF 服务,它具有接收文件(SQLite 数据库)并将其保存到服务器的功能。客户端在上传文件时会定期收到连接重置异常。

我们从未能够在开发中重现此问题。它只在生产中发生过,即便如此,也是不一致的。这将在某一天发生在客户身上,但不会在下一天发生。我们已手动从客户端中取出相同的文件并将其移至开发中的一个以调试发送到相同的生产服务器,然后让它成功而没有问题。它会在某些生产服务器上发生,但不会在其他服务器上发生 - 再次不一致。

文件相对较小,通常在 500kb 到 10mb 之间。

这里是服务功能:

[OperationContract]
[WebInvoke(Method = "POST",
           UriTemplate = "SendData",
           ResponseFormat = WebMessageFormat.Json,
           RequestFormat = WebMessageFormat.Json)]
public GenericObject SendData(Stream data)

    GenericObject go = new GenericObject();
    
    try
    
        string fileName = "SomePathToFile.db";

        FileStream fs = new FileStream(fileName, FileMode.Create);

        int offset = 0;
        byte[] buffer = new byte[16*1024];
        
        while ((offset = databaseStream.Read(buffer, 0, buffer.Length)) != 0)
            fs.Write(buffer, 0, offset);
        
        fs.Close();
        
        go.Item = "Send successful";
    
    catch (Exception ex)
    
        go.Error = "Error sending data\n\n" + ex.Message;
        
        //Do some error logging
        ...
    
    
    return go;

它只是获取文件流并将其本地保存到服务器。如果出现问题,它会返回一个具有成功消息或错误消息的通用 JSON 对象。注意:客户端不会取回对象,服务器上也不会记录任何异常。

这是发送文件的 android 端函数(我们添加了一些重试逻辑以帮助减少错误):

public void sendData(String serviceURL, int attemptNumber)


    //Declare all streams/disposable objects so that we can reset them if an error occurs
    FileInputStream fs = null;
    DataOutputStream os = null;
    InputStreamReader isr = null;
    BufferedReader in = null;
    HttpURLConnection conn = null;

    boolean connectionResetError = false;

    File tempFile = null;

    try
    
        //Do some stuff to write the db file to the cache directory for temporary storage
        tempFile = new File(getCacheDir(), "SomeFile.db");

        ...
        

        //Open the cache copy of the file for reading
        fs = new FileInputStream(tempFile);

        //Establish URL to the service
        URL url = new URL(serviceURL);

        // Open an HTTP  connection to the URL
        conn = (HttpURLConnection) url.openConnection();
        conn.setDoInput(true);      // Allow Inputs
        conn.setDoOutput(true);     // Allow Outputs
        conn.setUseCaches(false);   // Don't use a Cached Copy
        conn.setChunkedStreamingMode(1024);
        conn.setRequestMethod("POST");
        
        //Tried both of these options based on other Stack Overflow suggestions
        //conn.setRequestProperty("Connection", "Keep-Alive");
        conn.setRequestProperty("Connection", "close"); 

        //Open a stream on the connection to write the file
        os = new DataOutputStream(conn.getOutputStream());

        int bytesRead, bytesAvailable, bufferSize;
        byte[] buffer;
        int maxBufferSize = 1 * 1024;

        //Determine file size and if a buffer is needed
        bytesAvailable = fs.available();
        bufferSize = Math.min(bytesAvailable, maxBufferSize);
        buffer = new byte[bufferSize];

        // Read from the file into the buffer
        bytesRead = fs.read(buffer, 0, bufferSize);

        //Continue reading while there is still more to be read from the file
        while (bytesRead > 0)
        
            //write the buffer to the connection
            os.write(buffer, 0, bufferSize);

            //See if there is more to be read from the file and then read
            bytesAvailable = fs.available();
            bufferSize = Math.min(bytesAvailable, maxBufferSize);
            bytesRead = fs.read(buffer, 0, bufferSize);
        

        //Get the response and message from the connection
        int serverResponseCode = conn.getResponseCode();
        String serverResponseMessage = conn.getResponseMessage();

        //Flush the output stream
        os.flush();

        if (serverResponseCode == HttpURLConnection.HTTP_OK)
        
            //If response is "OK", read the data that was sent

            //Open a reader for the response and buffer it
            isr = new InputStreamReader(conn.getInputStream());
            in = new BufferedReader(isr);

            //String to hold the response
            String responseString;
            StringBuilder sb = new StringBuilder();

            //Read the response
            while ((responseString = in.readLine()) != null)
                sb.append(responseString);

            //Return the response that was read
            jResponse = new JSONObject(sb.toString());
        
        else
        
            //If any other response is received, return the message
            response.putString("Error", serverResponseMessage);
        

    
    catch(Exception ex)
    
        //An error occurred

        //Check to see if it's the connection reset error and if we've tried less than 3 times
        if(ex instanceof java.net.SocketException && ex.getMessage().equals("Connection reset") && attemptNumber < 3)
        
            //If so, make a note so we can try again
            connectionResetError = true;
        
        else
        
            //If not, just pass along the error
            response.putString("Error", ex.getMessage());
        
    
    finally
    
        //Dispose of everything before we quit or try again

        try  if (fs != null) fs.close();  catch(IOException e)
        try  if (in != null) in.close();  catch(IOException e)
        try  if (isr != null) isr.close();  catch(IOException e)
        try  if (os != null) os.close();  catch(IOException e)
        try  if (conn != null) conn.disconnect();  catch(Exception e)
        
        if(tempFile != null && tempFile.exists())
            tempFile.delete();
    

    //If we're going to try again, do so and increment the attempt number
    if(connectionResetError)
    
        sendData(serviceURL, attemptNumber+1);
    

我们已经在其中一台服务器上设置了 Wireshark 以尝试调试,但我对它还不够熟悉,因此它没有意义。我所知道的是我看到了请求,一些正在传输的数据,然后是重置。

您可能有的任何建议或调试指导都会非常有帮助。

【问题讨论】:

只是未注册的 cmets:bytesAvailable = fs.available(); 干什么?你应该写bytesRead字节并读bufferSize离开......接下来DataOutputStream的目的是什么?你可以直接写信给OutputStream @Selvin。谢谢。这些问题的答案就是我们在网上的样本中发现的。我们这个函数的原始版本是在 2013 年编写的,直到今年年初一直没有问题。当我们开始遇到问题时,我们尝试过的一件事是查看更新的样本,因为框架在这 7 年中发生了一些变化。我们原来的函数没有你指出的问题:OutputStream os = conn.getOutputStream();byte[] buffer = new byte[1024];int offset = 0;while ((offset = fs.read(buffer)) != -1) os.write(buffer); 【参考方案1】:

异常的连接重置通常仅表示连接已关闭。这可能是由网络问题引起的。我建议你在配置文件中设置一个超时来避免这个问题:

<configuration>
  <system.serviceModel>
    <bindings>
      <wsHttpBinding>
        <binding openTimeout="00:10:00"
                 closeTimeout="00:10:00"
                 sendTimeout="00:10:00"
                 receiveTimeout="00:10:00">
        </binding>
      </wsHttpBinding>
    </bindings>
  </system.serviceModel>
</configuration>

您可以在客户端启用 WCF 跟踪以从错误客户端收集信息。 WCF追踪的详细信息,请参考这篇文章:

https://docs.microsoft.com/en-us/dotnet/framework/wcf/diagnostics/tracing/configuring-tracing?redirectedfrom=MSDN

【讨论】:

感谢您的回复。我了解连接意外关闭。如果这是一个孤立的事件,我同意这只是一个网络问题。但是,有些客户每天都在经历这种情况,有些客户每周都在经历这种情况,还有一些则根本没有。在多年没有之后突然开始在不同的地方发生这种情况似乎很奇怪。错误发生在发送数据 10-30 秒后,所以我不认为超时,尤其是 10 分钟,可以解决这里的问题。感谢您提供有关追踪的信息。我将启用它并查看日志。

以上是关于从 Android 发送文件时从 WCF 服务重置连接的主要内容,如果未能解决你的问题,请参考以下文章

从 Xamarin Android 应用程序崩溃将大文件上传到 WCF

如何使用 WCF 接收从 Android 发送的图像?

使用从 STS 分配的令牌调用 WCF 服务

将图像从 Android 上传到 WCF C#/.网

从 Android 发送 JSON HTTP POST 请求

当 BodyStyle = WebMessageBodyStyle.Wrapped 时从 C# 调用 Rest WCF 服务操作