先明确一个问题,如果定义了一个数据结构,大小是,比方说 32 个字节,然后 UDP 客户端连续向服务端发了两个包。现在假设这两个包都已经到达了服务器,那么服务端调用 recvfrom 来接收数据,并且缓冲区开得远大于 64,例如,开了 1024 个字节,那么,服务端的 recvfrom 函数是会一次收到两个数据包呢,还是只能收到一个。

答案是只能收到一个。

来看代码:

struct.h

  1. #ifndef STRUCT_H
  2. #define STRUCT_H
  3.  
  4. typedef struct _UDP_MSG {
  5. int add1;
  6. int add2;
  7. int sum;
  8. char str1[];
  9. char str2[];
  10. char cat[];
  11. } UDP_MSG;
  12.  
  13. #endif /* STRUCT_H */
 

服务器的代码:

  1. #include <sys/types.h>
  2. #include <sys/socket.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <netinet/in.h>
  6. #include <stdio.h>
  7. #include <stdlib.h>
  8.  
  9. #include "struct.h"
  10.  
  11. #define MAX_LINE 1024
  12. #define SERV_PORT 8080
  13.  
  14. int udp_serv();
  15.  
  16. int main() {
  17. return udp_serv();
  18. }
  19.  
  20. int udp_serv() {
  21. int sockfd = socket(AF_INET, SOCK_DGRAM, );
  22. if (sockfd == -) {
  23. perror("socket");
  24. return -;
  25. }
  26.  
  27. struct sockaddr_in serv_addr;
  28. memset(&serv_addr, , sizeof(serv_addr));
  29. serv_addr.sin_family = AF_INET;
  30. serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  31. serv_addr.sin_port = htons(SERV_PORT);
  32.  
  33. if (bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -) {
  34. perror("bind");
  35. return -;
  36. }
  37.  
  38. while () {
  39. printf("sleeping\n");
  40. sleep();
  41. printf("akwaked\n");
  42. char buf[BUFSIZ];
  43. struct sockaddr_in cli_addr;
  44. memset(&cli_addr, , sizeof(cli_addr));
  45. socklen_t cli_addr_len = sizeof(cli_addr);
  46. int recvn = recvfrom(sockfd, buf, sizeof(buf), , (struct sockaddr*)&cli_addr, &cli_addr_len);
  47. printf("recv %d bytes\n", recvn);
  48. UDP_MSG msg;
  49. memset(&msg, , sizeof(msg));
  50. printf("UDP_MSG size is %d\n", sizeof(msg));
  51. memcpy(&msg, buf, sizeof(msg));
  52. msg.sum = msg.add1 + msg.add2;
  53. strcpy(msg.cat, msg.str1);
  54. strcat(msg.cat, msg.str2);
  55. printf("msg.add1 is: %d\n", msg.add1);
  56. printf("msg.add2 is: %d\n", msg.add2);
  57. printf("msg.sum is: %d\n", msg.sum);
  58. printf("msg.str1 is: %s\n", msg.str1);
  59. printf("msg.str2 is: %s\n", msg.str2);
  60. printf("msg.cat is: %s\n", msg.cat);
  61. sendto(sockfd, &msg, sizeof(msg), , (struct sockaddr*)&cli_addr, cli_addr_len);
  62. }
  63.  
  64. return ;
  65. }
 

客户端的:

  1. #include <sys/types.h>
  2. #include <sys/socket.h>
  3. #include <string.h>
  4. #include <netinet/in.h>
  5. #include <stdio.h>
  6. #include <stdlib.h>
  7. #include <arpa/inet.h>
  8. #include <unistd.h>
  9.  
  10. #include "struct.h"
  11.  
  12. #define MAX_LINE 1024
  13. #define SERV_PORT 8080
  14.  
  15. int udp_cli(const char* serv_ip);
  16.  
  17. int main(int argc, char* argv[]) {
  18. if (argc < ) {
  19. printf("Usage: %s serv_ip\n", argv[]);
  20. return ;
  21. }
  22. return udp_cli(argv[]);
  23. }
  24.  
  25. int udp_cli(const char* serv_ip) {
  26. int sockfd = socket(AF_INET, SOCK_DGRAM, );
  27. if (sockfd == -) {
  28. perror("socket");
  29. return -;
  30. }
  31. struct sockaddr_in serv_addr;
  32. memset(&serv_addr, , sizeof(serv_addr));
  33. serv_addr.sin_family = AF_INET;
  34. serv_addr.sin_port = htons(SERV_PORT);
  35. if (inet_pton(AF_INET, serv_ip, &serv_addr.sin_addr) <= ) {
  36. perror("inet_pton");
  37. return -;
  38. }
  39. while () {
  40. UDP_MSG msg;
  41. memset(&msg, , sizeof(msg));
  42. msg.add1 = ;
  43. msg.add2 = ;
  44. scanf("%s%s", msg.str1, msg.str2);
  45. if (sendto(sockfd, &msg, sizeof(msg), ,
  46. (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -) {
  47. perror("write");
  48. return -;
  49. } else {
  50. printf("send success\n");
  51. }
  52. if (sendto(sockfd, &msg, sizeof(msg), ,
  53. (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -) {
  54. perror("write2");
  55. return -;
  56. } else {
  57. printf("send2 success\n");
  58. }
  59. socklen_t serv_addr_len = sizeof(serv_addr);
  60. if (recvfrom(sockfd, &msg, sizeof(msg), ,
  61. (struct sockaddr*)&serv_addr, &serv_addr_len) == -) {
  62. perror("read");
  63. return -;
  64. }
  65. printf("msg.add1 is: %d\n", msg.add1);
  66. printf("msg.add2 is: %d\n", msg.add2);
  67. printf("msg.sum is: %d\n", msg.sum);
  68. printf("msg.str1 is: %s\n", msg.str1);
  69. printf("msg.str2 is: %s\n", msg.str2);
  70. printf("msg.cat is: %s\n", msg.cat);
  71. break;
  72. }
  73. return ;
  74. }
 

运行起来后,服务端的输出如下:

  1. sleeping
  2. akwaked
  3. recv bytes
  4. UDP_MSG size is
  5. msg.add1 is:
  6. msg.add2 is:
  7. msg.sum is:
  8. msg.str1 is:
  9. msg.str2 is:
  10. msg.cat is:
  11. sleeping
  12. akwaked
  13. recv bytes
  14. UDP_MSG size is
  15. msg.add1 is:
  16. msg.add2 is:
  17. msg.sum is:
  18. msg.str1 is:
  19. msg.str2 is:
  20. msg.cat is:
  21. sleeping
 

客户端如下:

  1.  
  2. send success
  3. send2 success
  4. msg.add1 is:
  5. msg.add2 is:
  6. msg.sum is:
  7. msg.str1 is:
  8. msg.str2 is:
  9. msg.cat is:
 

这里涉及到一个边界的问题。 TCP 是流式的数据传输,消息没有边界,需要应用层自己去定义消息边界,而 UDP 是数据报传输,所以协议保证了一次只能接收一个数据报。

详细的解释看到这里,http://hi.baidu.com/chongerfeia/item/c38f91e075d742226dabb8a4

2010-08-11 14:56 有关TCP和UDP 粘包 消息保护边界在socket网络程序中,TCP和UDP分别是面向连接和非面向连接的。因此TCP的socket编程,收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。
对于UDP,不会使用块的合并优化算法,这样,实际上目前认为,是由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了

保护消息边界和流
那么什么是保护消息边界和流呢?

保护消息边界,就是指传输协议把数据当作一条独立的消息在网上
传输,接收端只能接收独立的消息.也就是说存在保护消息边界,接收
端一次只能接收发送端发出的一个数据包.
而面向流则是指无保护消息保护边界的,如果发送端连续发送数据,
接收端有可能在一次接收动作中,会接收两个或者更多的数据包.

我们举个例子来说,例如,我们连续发送三个数据包,大小分别是2k,
4k , 8k,这三个数据包,都已经到达了接收端的网络堆栈中,如果使
用UDP协议,不管我们使用多大的接收缓冲区去接收数据,我们必须有
三次接收动作,才能够把所有的数据包接收完.而使用TCP协议,我们
只要把接收的缓冲区大小设置在14k以上,我们就能够一次把所有的
数据包接收下来.只需要有一次接收动作.

这就是因为UDP协议的保护消息边界使得每一个消息都是独立的.而
流传输,却把数据当作一串数据流,他不认为数据是一个一个的消息.

所以有很多人在使用tcp协议通讯的时候,并不清楚tcp是基于流的
传输,当连续发送数据的时候,他们时常会认识tcp会丢包.其实不然,
因为当他们使用的缓冲区足够大时,他们有可能会一次接收到两个甚
至更多的数据包,而很多人往往会忽视这一点,只解析检查了第一个
数据包,而已经接收的其他数据包却被忽略了.所以大家如果要作这
类的网络编程的时候,必须要注意这一点.

结论:
根据以上所说,可以这样理解,TCP为了保证可靠传输,尽量减少额外
开销(每次发包都要验证),因此采用了流式传输,面向流的传输,
相对于面向消息的传输,可以减少发送包的数量。从而减少了额外开
销。但是,对于数据传输频繁的程序来讲,使用TCP可能会容易粘包。
当然,对接收端的程序来讲,如果机器负荷很重,也会在接收缓冲里
粘包。这样,就需要接收端额外拆包,增加了工作量。因此,这个特
别适合的是数据要求可靠传输,但是不需要太频繁传输的场合(
两次操作间隔100ms,具体是由TCP等待发送间隔决定的,取决于内核
中的socket的写法)

而UDP,由于面向的是消息传输,它把所有接收到的消息都挂接到缓冲
区的接受队列中,因此,它对于数据的提取分离就更加方便,但是,
它没有粘包机制,因此,当发送数据量较小的时候,就会发生数据包
有效载荷较小的情况,也会增加多次发送的系统发送开销(系统调用,
写硬件等)和接收开销。因此,应该最好设置一个比较合适的数据包
的包长,来进行UDP数据的发送。(UDP最大载荷为1472,因此最好能
每次传输接近这个数的数据量,这特别适合于视频,音频等大块数据
的发送,同时,通过减少握手来保证流媒体的实时性)

UDP TCP 消息边界的更多相关文章

  1. Mina、Netty、Twisted一起学(二):TCP消息边界问题及按行分割消息

    在TCP连接开始到结束连接,之间可能会多次传输数据,也就是服务器和客户端之间可能会在连接过程中互相传输多条消息.理想状况是一方每发送一条消息,另一方就立即接收到一条,也就是一次write对应一次rea ...

  2. Mina、Netty、Twisted一起学(三):TCP消息固定大小的前缀(Header)

    在上一篇博文中,有介绍到用换行符分割消息的方法.但是这种方法有个小问题,如果消息中本身就包含换行符,那将会将这条消息分割成两条,结果就不对了. 本文介绍另外一种消息分割方式,即上一篇博文中讲的第2条: ...

  3. 【转】关于TCP和UDP协议消息保护边界的介绍

    在 socket网络程序中,TCP和UDP分别是面向连接和非面向连接的.因此TCP的socket编程,收发两端(客户端和服务器端)都要有一一成对的 socket,因此,发送端为了将多个发往接收端的包, ...

  4. TCP和UDP的保护消息边界机制

    在socket网络程序中,TCP和UDP分别是面向连接和非面向连接的.TCP的socket编程,收发两端都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化 ...

  5. TCP和UDP的"保护消息边界" (经典)

    在socket网络程序中,TCP和UDP分别是面向连接和非面向连接的.因此TCP的socket编程,收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有 ...

  6. TCP和UDP的"保护消息边界”

    转自:http://blog.csdn.net/zhangxinrun/article/details/6721427 在socket网络程序中,TCP和UDP分别是面向连接和非面向连接的.因此TCP ...

  7. Learn day8 re正则表达式\search函数\反射\tcp发送消息(循环)\udp发送消息

    1.匹配单个字符 # ### 正则表达式 - 单个字符匹配 import re ''' findall 把匹配的结果直接返回到列表中 lst = re.findall("正则表达式" ...

  8. 【转】TCP协议的无消息边界问题

    http://www.cnblogs.com/eping/archive/2009/12/12/1622579.html   使用TCP协议编写应用程序时,需要考虑一个问题:TCP协议是无消息边界的, ...

  9. android发送udp,tcp消息

    发送方创建步骤: 1.  创建一个DatagramSocket对象 DatagramSocket socket = new  DatagramSocket (4567); 2.  创建一个 InetA ...

随机推荐

  1. equals()源代码及释义

    源代码: public boolean equals(Object anObject) {if (this == anObject) { return true;}if (anObject insta ...

  2. OpenJudge 666:放苹果

    总时间限制: 1000ms 内存限制: 65536kB 描述 把M个同样的苹果放在N个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法?(用K表示)5,1,1和1,5,1 是同一种分法. 输 ...

  3. 实验九--裸机LCD

    一.环境 系统:ubuntu12.04 开发板:jz2440 编译器:gcc 二.说明 有空补上 三.代码 Makefile: CC = arm-linux-gcc LD = arm-linux-ld ...

  4. php文本操作方法集合比较第2页

    fgets和fputs.fread和fwrite.fscanf和fprintf 格式化读写函数fscanf和fprintf fscanf函数,fprintf函数与前面使用的scanf和printf 函 ...

  5. XE5 ANDROID通过webservice访问操作MSSQL数据库

    上接XE5 ANDROID平台 调用 webservice 一.服务端 在ro里添加函数(在impl上添加阿东connection,adoquery,dataprovider) function TN ...

  6. #include< > 和 #include” ” 的区别

    一.#include< > #include< > 引用的是编译器的类库路径里面的头文件. 假如你编译器定义的自带头文件引用在 C:\Keil\c51\INC\ 下面,则 #i ...

  7. Linux /dev 自动创建设备节点

    #include <linux/module.h> #include <linux/module.h> #include <linux/kernel.h> #inc ...

  8. XCODE快捷键和功能汇总篇(不断更新)

    快捷键 command+b(build) 编译 command+r(run) 运行编译后程序鼠标放在代码元素上,按command然后单击,可以看到元素的属性

  9. DB天气app冲刺二阶段第八天

    今天突然感觉应该做收尾工作了 因为马上就要考试了,时间一下子就不够用了.. 今天主要修复了一下bug,然后天气基本能够实时准确了,就是多功能按钮还是没有弄好 准备简化一下功能. 明天看看还有什么需要改 ...

  10. MyEclipse 2013官网下载地址以及破解方法

    刚刚发布了MyEclipse 2013,我现在用的还是6.5的版本,6.5的版本是我觉得最好用的一个版本. 我装上了,还没感受到有哪些好用,就是感觉体积庞大,和IBM 的WID一样,是个多面手,啥事都 ...