TCP客户端连接

7

我有一个应用程序,分布在公司各处,通过我们的Windows 2003服务器(运行IIS 6.0)向我发送数据。小文本消息可以传递,但包含更多数据(约20 KB)的大型消息无法传递。

我将字节缓冲区设置为TCP客户端的缓冲区大小。我注意到我的数据已经在服务器上接收到了;然而,在服务器关闭套接字连接之前,它只循环执行一次接收例程,我的大文件始终是缓冲区大小或8 KB。换句话说,在服务器关闭套接字连接之前,我的代码只能执行一次循环。

考虑到可能存在填充整个缓冲区的问题,我尝试将读/写限制为仅1 KB,但这只会导致我们的服务器在接收1 KB后关闭套接字并关闭连接。

我将服务器的错误消息发送回客户端以便查看。从客户端接收到的具体错误消息如下:

“无法将数据写入传输连接:已建立的连接由主机软件中止。”

我更新了我的服务器应用程序,以便底层TCP套接字使用“保持活动状态”:

client.Client.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.KeepAlive, true);

现在,每当我尝试发送消息时,客户端会收到以下错误信息:
“无法将数据写入传输连接:现有连接被远程主机强制关闭。”
我们的网络管理员告诉我,在我们的内部服务器上没有防火墙或任何端口被阻止。
通过谷歌搜索这些错误,我发现有人建议尝试telnet到服务器。我按照他们的指示telnet到了服务器,但是我不确定如何解释响应:
C:> telnet Welcome to Microsoft Telnet Client Escape Character is ‘CTRL+]’ Microsoft Telnet> open cpapp 500 Connecting To cpapp…
这就是我得到的全部内容。我从未收到过错误提示,而且微软的Telnet屏幕最终会变成“按任意键继续…”——我想它已经超时了,但我的代码似乎仍然能够连接。
我已经尝试了代码和Telnet中的其他端口,包括25、80和8080。Telnet会退出25号端口,但是无论我告诉应用程序运行哪个端口,它似乎都能读取第一个循环。
以下是在客户端上运行的代码:
int sendUsingTcp(string location) {
  string result = string.Empty;
  try {
    using (FileStream fs = new FileStream(location, FileMode.Open, FileAccess.Read)) {
      using (TcpClient client = new TcpClient(GetHostIP, CpAppDatabase.ServerPortNumber)) {
        byte[] riteBuf = new byte[client.SendBufferSize];
        byte[] readBuf = new byte[client.ReceiveBufferSize];
        using (NetworkStream ns = client.GetStream()) {
          if ((ns.CanRead == true) && (ns.CanWrite == true)) {
            int len;
            string AOK = string.Empty;
            do {
              len = fs.Read(riteBuf, 0, riteBuf.Length);
              ns.Write(riteBuf, 0, len);
              int nsRsvp = ns.Read(readBuf, 0, readBuf.Length);
              AOK = Encoding.ASCII.GetString(readBuf, 0, nsRsvp);
            } while ((len == riteBuf.Length) && (-1 < AOK.IndexOf("AOK")));
            result = AOK;
            return 1;
          }
          return 0;
        }
      }
    }
  } catch (Exception err) {
    Logger.LogError("Send()", err);
    MessageBox.Show(err.Message, "Message Failed", MessageBoxButtons.OK, MessageBoxIcon.Hand, 0);
    return -1;
  }
}

这是我的运行在服务器上的代码:

SvrForm.Server = new TcpListener(IPAddress.Any, CpAppDatabase.ServerPortNumber);

void Worker_Engine(object sender, DoWorkEventArgs e) {
  BackgroundWorker worker = sender as BackgroundWorker;
  string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), Application.CompanyName);
  if (Directory.Exists(path) == false) Directory.CreateDirectory(path);
  Thread.Sleep(0);
  string eMsg = string.Empty;
  try {
    SvrForm.Server.Start();
    do {
      using (TcpClient client = SvrForm.Server.AcceptTcpClient()) { // waits until data is avaiable
        if (worker.CancellationPending == true) return;
        client.Client.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.KeepAlive, true);
        string location = Path.Combine(path, string.Format("Acp{0:yyyyMMddHHmmssff}.bin", DateTime.Now));
        byte[] buf = new byte[client.ReceiveBufferSize];
        try {
          using (NetworkStream ns = client.GetStream()) {
            if ((ns.CanRead == true) && (ns.CanWrite == true)) {
              try {
                int len;
                byte[] AOK = Encoding.ASCII.GetBytes("AOK");
                using (FileStream fs = new FileStream(location, FileMode.Create, FileAccess.Write)) {
                  do {
                    len = ns.Read(buf, 0, client.ReceiveBufferSize);
                    fs.Write(buf, 0, len);
                    ns.Write(AOK, 0, AOK.Length);
                  } while ((0 < len) && (ns.DataAvailable == true));
                }
                byte[] okBuf = Encoding.ASCII.GetBytes("Message Received on Server");
                ns.Write(okBuf, 0, okBuf.Length);
              } catch (Exception err) {
                Global.LogError("ServerForm.cs - Worker_Engine(DoWorkEvent)", err);
                byte[] errBuf = Encoding.ASCII.GetBytes(err.Message);
                ns.Write(errBuf, 0, errBuf.Length);
              }
            }
          }
        }
        worker.ReportProgress(1, location);
      }
    } while (worker.CancellationPending == false);
  } catch (SocketException) {
    // See MSDN: Windows Sockets V2 API Error Code Documentation for detailed description of error code
    e.Cancel = true;
  } catch (Exception err) {
    eMsg = "Worker General Error:\r\n" + err.Message;
    e.Cancel = true;
    e.Result = err;
  } finally {
    SvrForm.Server.Stop();
  }
}

我的应用程序为什么不能继续从TCP客户端读取?我忽略了设置某些内容吗,以便告诉Socket保持开放直到我完成?服务器代码从未出现异常,因为TCP客户端从未停止,所以我知道没有错误。
我们的网络管理员尚未获得副学士学位,因此如果问题出在服务器上,请详细描述如何解决,因为我们可能不理解您的意思。
抱歉这里有点长,但我想确保您们知道我的做法 - 甚至可以从中获取一些信息!
谢谢你的帮助! ~Joe
3个回答

6

在发送的内容之前,您应该先加上该内容的长度。您的循环假设所有数据在循环执行之前都已被发送,但实际上,当数据被发送时,您的循环正在执行。有时候,在线路上没有等待的数据,所以循环终止;同时,内容仍在通过线路发送。这就是为什么您的循环只运行一次的原因。


真的吗?我猜现在有点明白了。我一直以为服务器上需要设置什么东西。这帮了我大忙!谢谢你! - user153923
我该怎么做?“在发送的内容前加上该内容的长度”,但我没有发送内容的长度? - mahboub_mo
如果你不知道长度,那么显然你就不会在传输过程中发送长度。 - user47589

1

如果我正确地阅读了您的代码,基本上是这样(抱歉采用C样式——我不擅长C#:

do
{
  socket = accept();
  read(socket, buffer);
}while(not_done);

如果我没看错的话,这意味着您需要在其中添加一些内容。 如果您想使它序列化,按顺序读取每个上传文件,则需要第二个循环:

do
{
  socket = accept();
  do { read(socket, buffer); not_done_reading=...; } while (not_done_reading);
}while(not_done);

如果您想同时读取多个上传文件,则需要像这样的东西:

do
{
  socket = accept();
  if( !fork() )
  {
    do { read(socket, buffer); not_done_reading=...; } while (not_done_reading);
  }
}while(not_done);

1

你的telnet示例与你描述的代码行为有些矛盾 - 如果你能够在服务器上得到任何东西,"telnet <hostname> <portnumber>" 应该很快就会让你进入一个空白屏幕(在CMD提示符下的Windows机器上)。所以,这是第一件奇怪的事情 - 最好使用wireshark进行调试。

从代码角度来看,我认为这可能是服务器上这个内部行的问题:

... while ((0 < len) && (ns.DataAvailable == true));

你说你想在读取到某些内容并且有数据可用时循环。

然而,可能第二个段落还没有到达服务器,因此还没有可用的数据,所以你从这个循环中退出了。

你应该在读取到某些内容并且没有发生任何读取错误的情况下循环接收数据 - 这可以保证即使在慢速链接上,你也能可靠地接收数据。

附注:

我注意到你的协议是请求-响应-请求-响应类型。在局域网上它运行良好,但是如果你将来需要让它在高延迟的链接上工作,这将成为一个巨大的性能瓶颈(用于文件传输或TFTP的MS SMB协议也是这样工作的)。

(免责声明:我没有编写过太多的C#代码,所以对“DataAvailable()”方法的解释可能有误,请谨慎参考。)

编辑:根据你的协议,我的上面的答案可能需要进行更正 - 也就是说,你需要先读取文件的长度,然后再读取文件 - 因为如果你直接采用原文,它会完全破坏你设计的方式。

话虽如此,使用TCP时,发送方的write()操作次数与接收方的read()操作次数不应该假定相同 - 在某些情况下可能是这样(没有数据包丢失,没有Nagle算法) - 但在一般情况下不会成立。


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