TCP 客户端连接
Posted
技术标签:
【中文标题】TCP 客户端连接【英文标题】:TCP Client Connection 【发布时间】:2010-11-18 09:56:40 【问题描述】:我有一个为我的应用程序编写的应用程序,它分布在整个公司,通过我们的 Windows 2003 服务器(运行 IIS 6.0)向我发送数据。小短信可以通过,但包含更多数据(约 20 KB)的大短信无法通过。
我将字节缓冲区设置为 TCP 客户端的缓冲区大小。我注意到我的数据正在服务器上接收;但是,它只循环了一次接收例程,而且我的大文件总是正好是缓冲区大小的大小,或者我们服务器上的 8 KB。换句话说,我的代码只在服务器关闭套接字连接之前通过一个循环。
考虑到填充整个缓冲区可能存在问题,我尝试将我的读/写限制为仅 1 KB,但这只会导致我们的服务器在关闭连接之前收到 1 KB 后关闭套接字。
我将服务器的错误消息发送回客户端,以便查看。我从客户端收到的具体错误消息是:
“无法将数据写入传输 连接:已建立的连接 被您的软件中止 主机。”
我更新了我的服务器应用程序,以便底层 TCP 套接字将使用“keep alives”这一行:
client.Client.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.KeepAlive, true);
现在,每当我尝试发送消息时,客户端都会收到错误消息:
“无法将数据写入传输 连接:现有的连接是 被远程主机强行关闭。”
我们的网络管理员告诉我,他在我们的内部服务器上没有防火墙或任何端口。
谷歌搜索错误,我发现有人建议人们尝试 telnet 进入服务器。我使用他们的指示 telnet 进入服务器,但我不知道如何处理响应:
C:> telnet 欢迎来到微软 Telnet 客户端
转义字符是‘CTRL+]’
Microsoft Telnet> 打开 cpapp 500 正在连接 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("Acp0: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 Client 永远不会停止,所以我知道没有错误。
我们的网络管理员尚未获得副学士学位,因此如果证明是服务器问题,请在您的描述中详细说明如何解决此问题,因为我们可能不明白您在说什么。
我为这么长时间道歉,但我想确保你们那里知道我在做什么 - 甚至可能从我的技术中获得一些信息!
感谢您的帮助! 〜乔
【问题讨论】:
【参考方案1】:您应该在发送的内容之前加上该内容的长度。您的循环假定所有数据都在循环执行之前发送,而实际上您的循环是在发送数据时执行的。有时线路上没有数据等待,因此循环终止;同时,内容仍在通过网络发送。这就是为什么你的循环只运行一次。
【讨论】:
不开玩笑?我想现在说得通了。我一直在想需要在服务器上设置一些东西。这对很多有帮助!谢谢! 我该怎么做? “在发送的内容之前加上该内容的长度”我没有发送内容的长度? 如果您不知道长度,那么显然您不会在线发送长度。【参考方案2】:如果我正确阅读了您的代码,那么您基本上已经明白了(对不起 c 风格 - 我不擅长 c#:
做 套接字 = 接受(); 读取(套接字,缓冲区); while(not_done);如果我是正确的,那么这意味着你需要……多一点。如果您希望它被序列化,按顺序读取每个上传,您将需要第二个循环:
做 套接字 = 接受(); 做读取(套接字,缓冲区); not_done_reading=...; 而 (not_done_reading); while(not_done);如果你想同时阅读多个上传,你需要更多类似的东西:
做 套接字 = 接受(); 如果(!叉()) 做读取(套接字,缓冲区); not_done_reading=...; 而 (not_done_reading); while(not_done);【讨论】:
【参考方案3】:您的 telnet 示例与您描述的代码的行为有些矛盾 - 如果您能够在服务器上获取任何内容,“telnet
在代码方面,我认为服务器上的这条内线可能有问题:
...while ((0
您说您想在能够读取某些内容并且有一些可用数据时进行循环。
但是,可能是第二个段还没有到达服务器,所以还没有可用的数据,所以你退出了这个循环。
您应该在读取某些内容并且没有任何读取错误的情况下循环接收数据 - 这可以保证即使在慢速链接上您也能可靠地接收数据。
附注:
我注意到您的协议是 request-response-request-response 类型。它在 LAN 上运行良好,但是如果您将来需要使其在高往返时间链路上运行,这将成为一个很大的性能瓶颈(用于文件传输或 TFTP 的 MS SMB 协议以这种方式工作) .
(免责声明:我在 C# 中编写的代码不多,因此对“DataAvailable()”方法的解释可能有误,请使用此 FWIW)。
编辑:可能我上面的答案需要根据您的协议进行更正-即您需要先读取文件的长度,然后再读取文件-因为如果您逐字阅读它会破坏方式你完全设计了它。
也就是说,对于 TCP,永远不应假设发送方的 write() 操作数与接收方的 read() 操作数相同 - 在某些情况下可能是这种情况(没有丢包, 没有 Nagle) - 但在一般情况下它不会是真的。
【讨论】:
以上是关于TCP 客户端连接的主要内容,如果未能解决你的问题,请参考以下文章