转帖:粘包、丢包及TCP信息收发

粘包、丢包及TCP信息收发 

转载自 http://blog.vckbase.com/arong/archive/2010/01/03/40672.html

初涉socket编程的朋友经常有下面一些疑惑:
1. 为什么我发了3次,另一端只收到2次?
2. 我每次发送都成功了,为什么对方收到的信息不完整?

这些疑惑往往是对send和recv这两个函数理解不准确所致。send和recv都提供了一个长度参数。对于send而言,这是你希望发送的字节数,而对于recv而言,则是希望收到的最大字节数。

1。 send

send函数的原型是:int send(SOCKET sd, const char * buffer, int len, int flag). 其中len指出buffer中包含的实际字节数,也是程序员希望发出的最大字节数。而这个函数的返回值是实际发送出去的字节数。在网络程序中,正常情况是这个返回值小于len,也就是说buffer中的内容没有完全被发送出去。

为了确保一个缓冲区内的内容被完全被发送出去,我们需要如下代码:

int res;
int pos = 0; //下一次发送需要开始的位置:
while(pos < len)
{
res = send(sd, buffer + pos , len - pos, 0);
if(res <=0) goto err_handler; //去错误处理
pos += res;
}

这样经过多次send,可以确保buffer内的内容都别发送出去。

为了避免发送线程被阻塞,应该考虑把上述代码放到一个子线程中,并通过队列来缓冲所有收发。

2. recv

recv的原型是:int recv(SOCKET sd, char * buffer, int len, int flag),其各个参数的含义同前面send。需要注意的是,系统并不会等待bufer被填满了再返回,而是一旦有数据被收到,就立刻返回。因此不要期望实际收到的数据长度就等于len。

你可以使用前面send的循环算法确保收到len个字节,也可以使用内容驱动的方法实现分段数据分析。不过这个就超出本文内容,也就不再赘述。

3. 粘包
所谓粘包,是指发送端发送的两个报文,在接收端被拼在一起。由于TCP是面向流的协议,报文与报文之间是没有分界符号的。在接收端,所有的数据都逻辑上拼在一起给你。举例来说,你分10次发送10个长度为10的报文,在接收端,你可能只收到一个长度为100的报文,而不会收到10个消息。

为了解决这个问题,你必须在接收端有能力把这些报文分隔开来。如果消息长度总是固定的,这就比较容易,只要按长度取出即可。如果长度不固定,一般有两种方法解决:

a)使用特征字节。例如:如果是聊天程序,发送的是普通文本,一些字符是绝对不可能出现在正文中的,你可以使用这些字符做分隔符隔离不同消息。我们可以使用'\0'做分隔,一般对于全文本传输是比较安全的。

b) 在发送方发送正文前,先发送一个长度。例如你要发送2345字节的内容,你可以先发送一个2字节的长度给对方,然后再发正文。接收放只要收到这个长度信息,就可以正确的分包。需要注意的,到底用多少字节来发送长度是应该预先约定的,一般2字节就足够,不过你约定4字节也是可以的。还要注意的是,如果接收一次报文后,解包完毕还剩下一部分内容,这些内容应该留给下次报文分包使用,而不能扔掉。

4. 丢包
丢包一般都是由于对上面说的理解不足引起的,因为TCP本身是确保不丢包的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 C# 中,网络编程中常见的问题可以通过以下方式解决: 1. 问题 问题通常是由于发送方在一段时间内连续发送了多个数据,而接收方在接收数据时没有及时处理导致的。解决问题的一种方法是在发送的数据中添加特定的分隔符,在接收方根据分隔符将数据拆分成多个消息。 以下是一个简单的示例,演示了如何使用特定的分隔符解决问题: ```csharp using System; using System.Net.Sockets; using System.Text; class Program { static void Main(string[] args) { // 创建一个 Socket 对象 Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // 连接服务器 socket.Connect("127.0.0.1", 8888); // 发送数据 string message1 = "Hello,"; string message2 = "World!"; byte[] buffer1 = Encoding.UTF8.GetBytes(message1 + "|"); byte[] buffer2 = Encoding.UTF8.GetBytes(message2 + "|"); socket.Send(buffer1); socket.Send(buffer2); // 接收数据 byte[] buffer = new byte[1024]; int count = socket.Receive(buffer); string response = Encoding.UTF8.GetString(buffer, 0, count); Console.WriteLine(response); // 关闭 Socket 连接 socket.Shutdown(SocketShutdown.Both); socket.Close(); } } ``` 在上面的示例中,我们在发送的数据中添加了分隔符 `|`,在接收方根据分隔符将数据拆分成多个消息。这样可以保证每个消息都是完整的,避免问题。 2. 问题 问题通常是由于网络传输中数据失导致的。解决问题的一种方法是在发送的数据中添加序号,接收方在接收数据时根据序号判断是否有数据失并进行重传。 以下是一个简单的示例,演示了如何使用序号解决问题: ```csharp using System; using System.Net.Sockets; using System.Text; class Program { static void Main(string[] args) { // 创建一个 Socket 对象 Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // 连接服务器 socket.Connect("127.0.0.1", 8888); // 发送数据 int seq1 = 1; int seq2 = 2; string message1 = "Hello,"; string message2 = "World!"; byte[] buffer1 = Encoding.UTF8.GetBytes(seq1 + "|" + message1); byte[] buffer2 = Encoding.UTF8.GetBytes(seq2 + "|" + message2); socket.Send(buffer1); socket.Send(buffer2); // 接收数据 byte[] buffer = new byte[1024]; int count = socket.Receive(buffer); string response = Encoding.UTF8.GetString(buffer, 0, count); Console.WriteLine(response); // 关闭 Socket 连接 socket.Shutdown(SocketShutdown.Both); socket.Close(); } } ``` 在上面的示例中,我们在发送的数据中添加了序号,接收方可以根据序号判断是否有数据失并进行重传。这样可以保证数据的完整性,避免问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值