linux网络编程之socket(十四):基于UDP协议的网络程序
来源:互联网 发布:乐乎城市青年社区 亦庄 编辑:程序博客网 时间:2024/05/17 23:29
原文 http://blog.csdn.net/jnu_simba/article/details/9077455
一、下图是典型的UDP客户端/服务器通讯过程
下面依照通信流程,我们来实现一个UDP回射客户/服务器
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
当套接字处于“已连接”的状态时,才可以使用send,当flags = 0 时 send 与 write 一致。
且 send(sockfd, buf, len, flags); 即 sendto(sockfd, buf, len, flags, NULL, 0);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
recv 与 recvfrom 的关系与 send 与 sendto 的关系一致。
echoser_udp.c:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
> File Name: echoser_udp.c
> Author: Simba
> Mail: dameng34@163.com
> Created Time: Sun 03 Mar 2013 06:13:55 PM CST
************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<string.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
void echo_ser(int sock)
{
char recvbuf[1024] = {0};
struct sockaddr_in peeraddr;
socklen_t peerlen;
int n;
while (1)
{
peerlen = sizeof(peeraddr);
memset(recvbuf, 0, sizeof(recvbuf));
n = recvfrom(sock, recvbuf, sizeof(recvbuf), 0,
(struct sockaddr *)&peeraddr, &peerlen);
if (n == -1)
{
if (errno == EINTR)
continue;
ERR_EXIT("recvfrom error");
}
else if(n > 0)
{
fputs(recvbuf, stdout);
sendto(sock, recvbuf, n, 0,
(struct sockaddr *)&peeraddr, peerlen);
}
}
close(sock);
}
int main(void)
{
int sock;
if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
ERR_EXIT("socket error");
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(5188);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
ERR_EXIT("bind error");
echo_ser(sock);
return 0;
}
echocli_udp.c:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
> File Name: echocli_udp.c
> Author: Simba
> Mail: dameng34@163.com
> Created Time: Sun 03 Mar 2013 06:13:55 PM CST
************************************************************************/
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
void echo_cli(int sock)
{
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(5188);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret;
char sendbuf[1024] = {0};
char recvbuf[1024] = {0};
while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{
sendto(sock, sendbuf, strlen(sendbuf), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
ret = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, NULL, NULL);
if (ret == -1)
{
if (errno == EINTR)
continue;
ERR_EXIT("recvfrom");
}
fputs(recvbuf, stdout);
memset(sendbuf, 0, sizeof(sendbuf));
memset(recvbuf, 0, sizeof(recvbuf));
}
close(sock);
}
int main(void)
{
int sock;
if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
ERR_EXIT("socket");
echo_cli(sock);
return 0;
}
编译运行server,在两个终端里各开一个client与server交互,可以看到server具有并发服务的能力。用Ctrl+C关闭server,然后再运行server,此时client还能和server联系上。和前面TCP程序的运行结果相比较,我们可以体会无连接的含义。udp 协议来说,server与client 的界限更模糊了,只要知道对等方地址(ip和port) 都可以主动发数据。
二、UDP编程注意点
1、UDP报文可能会丢失、重复
2、UDP报文可能会乱序
3、UDP缺乏流量控制
4、UDP协议数据报文截断
5、recvfrom返回0,不代表连接关闭,因为udp是无连接的。
6、ICMP异步错误
7、UDP connect
8、UDP外出接口的确定
9、太大的UDP包可能出现的问题
由于UDP不需要维护连接,程序逻辑简单了很多,但是UDP协议是不可靠的,实际上有很多保证通讯可靠性的机制需要在应用层实现,即123点所提到的。比如 如果发送端速度较快,而接收端较慢,很可能会产生 ICMP Source Quench Error,丢弃一些数据包。
对于第4点,可以写个小程序测试一下:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int main(void)
{
int sock;
if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
ERR_EXIT("socket");
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(5188);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
ERR_EXIT("bind");
sendto(sock, "ABCD", 4, 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
char recvbuf[1];
int n;
int i;
for (i = 0; i < 4; i++)
{
/* udp是报式协议,即若一次性接收的空间小于发来的数据,有可能造成报文截断,
* 但一定没有tcp的粘包问题 */
n = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, NULL, NULL);
if (n == -1)
{
if (errno == EINTR)
continue;
ERR_EXIT("recvfrom");
}
else if(n > 0)
printf("n=%d %c\n", n, recvbuf[0]);
}
return 0;
}
上述程序是自己发送数据给自己,发送了4个字节,但我们只提供1个字节的缓冲区recvbuf,第一次recvfrom 读取一个字节,但接下去循环却读不到剩下的数据了,因为udp 是报式协议,如果一次性接收的缓冲区小于发来的数据,有可能造成报文截断,反观tcp流式协议,可以一次读取一个数据包的一部分,也可以一次性读取多个数据包,但这也正是其会造成粘包问题的来源,所以也说udp 协议不会有粘包问题,因为一次就接收一个消息。输出如下:
simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./trunc
n=1 A
............
接收了一个字符之后,再次recvfrom 就阻塞了。
对于第5点,如果我们使用sendto 发送的数据大小为0,则发送给对方的是只含有各层协议头部的数据帧,recvfrom 会返回0,但并不代表对方关闭连接,因为udp 本身没有连接的概念。
第678点合起来一起讲,可以看到我们的客户端程序现在没有调用connect,不运行服务器程序,直接运行客户端程序,查看现象:
simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./echocli_udp
dfsaf
................
当我们在键盘敲入几个字符,sendto只是把Buf的数据拷贝到sock对应的缓冲区中,此时服务器未开启,协议栈返回一个ICMP异步错误,但因为前面没有调用connect“建立”一个连接,则recvfrom时不能收到这个错误而一直阻塞。现在我们在while 循环的外面添加一句:connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)); 再次测试一下:
simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./echocli_udp
dfsaf
recvfrom: Connection refused
此时recvfrom 就能接收到这个错误而返回了,并打印错误提示。
其实connect 并没有真正建立一个连接,即没有3次握手过程,只是维护了一种状态,绑定了远程地址,因为如此在调用sendto 时也可以不指定远程地址了,如 sendto(sock, sendbuf, strlen(sendbuf), 0, NULL, 0); 甚至也可以使用send 函数
send(sock, sendbuf, strlen(sendbuf), 0);
假设现在客户端有多个ip地址,由connect 或 sendto 函数提供的远程地址的参数,系统会选择一个合适的出口,比如远程ip 是192.168.2.10, 而客户端现在的ip 有 192.168.1.32 和 192.168.2.75 那么会自动选择192.168.2.75 这个ip 出去。
关于第9点。假设现在我们发送一个8192B 的IP数据报,必须分片传输,如果此时目的地址arp 并没有缓存,那么每一片都会发起arp 请求,此时会造成 arp flooding(RFC 建议的最大发送速率是每秒一次)。此时每一片都在等待arp reply,系统实现只会将最后一片发送到目的地(ARP input queue was LIFO ),也就是说,其他片都被丢弃了。对等方的IP层当接收到第一个到来的片时(不一定是偏移为0的片)会启动定时器,如果在30~60s 内的超时时间内没有接收到所有的片,则会丢弃所有接收到的片。但需要注意的是不一定会产生 ICMP "time exceeded during reassembly" error (ICMP 超时错误类型为11,code为0表示是TTL为0超时,code为1表示对方重组分片超时),只有在已经接收到偏移为0的片,即包含udp头部的片时才会产生此种错误,因为这个时候ICMP报文的接收方通过头部才知道是哪个进程发送的这个IP报文被丢弃了。实际上有没有产生ICMP超时报文并不是那么重要,因为系统假设TCP层 或者使用UDP的应用程序最终会timeout 导致重传。
参考:
《Linux C 编程一站式学习》
《TCP/IP详解 卷一》
《UNP》- linux网络编程之socket(十四):基于UDP协议的网络程序
- linux网络编程之socket(十四):基于UDP协议的网络程序
- linux网络编程之socket(十四):基于UDP协议的网络程序
- linux网络编程之socket(十四):基于UDP协议的网络程序
- linux网络编程之socket(十四):基于UDP协议的网络程序
- linux网络编程之socket(十四):基于UDP协议的网络程序
- 基于UDP协议的Socket网络编程
- 网络编程(5)—— 基于Linux系统的UDP协议socket服务器和客户端
- 网络编程之基于UDP协议的网络编程
- 3.Socket网络编程之基于UDP协议的Socket(图解与源码)
- linux网络编程之socket(UDP)
- 网络编程-基于UDP的Socket网络编程(补充)
- linux网络编程之用socket实现简单客户端和服务端的通信(基于UDP)
- 基于Udp的Socket网络编程聊天程序
- 基于Udp的Socket网络编程聊天程序
- 网络编程之基于UDP的Socket编程
- Linux网络编程之UDP Socket程序示例
- 网络协议(三)Socket编程之UDP
- iOS - NSFileManager的读写操作
- hadoop context.write
- asp.net-水印文本框/字符过滤文本框
- Linux权限
- python 函数参数传递和参数匹配
- linux网络编程之socket(十四):基于UDP协议的网络程序
- Facade模式
- HDU 4864 Task
- go Stack 简单实现
- 深入剖析- java 单例模式
- 字符串和多维数组
- 多线程编程模型之流水线模型
- 视频到图片序列,图片序列到视频(代码)
- 奇葩的Tomcat环境,localhost可以正常访问本地的Webservice,用自己机器Ip则不行