博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
linux网络编程之socket(十四):基于UDP协议的网络程序
阅读量:4681 次
发布时间:2019-06-09

本文共 6704 字,大约阅读时间需要 22 分钟。

一、下图是典型的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》

转载于:https://www.cnblogs.com/dyllove98/archive/2013/06/12/3132997.html

你可能感兴趣的文章
android WIFI
查看>>
常用的匹配正则表达式和实例
查看>>
小组成员及其git链接
查看>>
SQL case when else
查看>>
MVc Identity登陆锁定
查看>>
cdn连接失败是什么意思_关于CDN的原理、术语和应用场景那些事
查看>>
ultraedit26 运行的是试用模式_免费试用U盘数据恢复工具 – 轻松找回U盘丢失的各种数据!...
查看>>
plsql 查询存储过程死锁语句_插入语句/存储过程死锁
查看>>
bootstrap table 收缩_bootstrap-table方法之:expandRow-collapseRow,展开或关闭当前行数据...
查看>>
jsp跳转到本身页面_五种JSP页面跳转方法详解
查看>>
mysql r_mysql:’r’是什么意思?
查看>>
无法加载 mysql 扩展_请检查您的 php 配置. - 文档_无法载入 mysql 扩展 请检查 PHP 配置...
查看>>
非空 默认 男 mysql_MySQL进阶13--常见六大约束: 非空/默认/主键/唯一约束/检查约束/外键约束--表级约束 / 列级约束...
查看>>
mysql错误修改数据_mysql数据修改问题
查看>>
navicat忘记mysql密码_navicat连接My SQL时忘记root密码处理方法
查看>>
mysql 左连接 左外连接吗_什么是左外连接?左外连接在工作表查询中的应用
查看>>
python sum函数导入list_python sum函数iterable参数为二维list,start参数为“[]”该如何理解...
查看>>
docker 删除多余镜像_多余Basedisk删除和vDisk镜像反转技术简介
查看>>
mysqlin会使用索引吗_被面试官虐了,索引为何使用B+树,你知道吗
查看>>
mysql8单节点500m_Kubernetes 部署 Mysql 8.0 数据库(单节点)
查看>>