您的位置:首页 > 理论基础 > 计算机网络

linux网络编程之socket(十四):基于UDP协议的网络程序

2013-06-27 21:43 971 查看


linux网络编程之socket(十四):基于UDP协议的网络程序

分类: linux网络编程2013-06-12
09:03 6360人阅读 评论(14) 收藏 举报

udpsendtorecvfrom

一、下图是典型的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 的关系一致。

C++ Code
1

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;

}

C++ Code
1

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编程注意点

1、UDP报文可能会丢失、重复

2、UDP报文可能会乱序

3、UDP缺乏流量控制

4、UDP协议数据报文截断

5、recvfrom返回0,不代表连接关闭,因为udp是无连接的。

6、ICMP异步错误

7、UDP connect

8、UDP外出接口的确定

由于UDP不需要维护连接,程序逻辑简单了很多,但是UDP协议是不可靠的,实际上有很多保证通讯可靠性的机制需要在应用层实现,即123点所提到的。

对于第4点,可以写个小程序测试一下:

C++ Code
1

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 <unistd.h>

#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 出去。

参考:

《Linux C 编程一站式学习》

《TCP/IP详解 卷一》
《UNP》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: