UDP 理解

news/2024/5/18 13:17:15 标签: udp

这里需要指出的一点是,伪首部完全是虚拟的,它并不会和用户数据报一起被发送出去,只是在校验和的计算过程中会被使用到,伪首部主要来自于运载UDP报文的IP数据报首部,将源IP地址和目的IP地址加入到校验和的计算中可以验证用户数据报是否已经到达正确的终点。

所以udp头部大小为8字节

tcp和udp可以同用一个端口。使用地址复用SO_REUSEADDR即可。

udp通用函数

// 接收缓冲区,udp有接收缓冲区,无发送缓冲区

int nRecvBuf=32*1024;//设置为32K

setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));

//发送缓冲区

int nSendBuf=32*1024;//设置为32K

setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));

//地址复用

BOOL bReuseaddr = TRUE;

setsockopt( s, SOL_SOCKET, SO_REUSEADDR, ( const char* )&bReuseaddr, sizeof( BOOL ) );

接收端

1.创建,socket()函数

2.绑定, bind()函数

3.接收 recvfrom

不用实际读取就能检测到数据的到来,可以调用MSG_PEEK标志的recv活recvfrom,或者调用ioctlsocket或select.

ssize_t recvfrom(int sockfd,void *buf,size_t len,unsigned int flags, struct sockaddr *from,socket_t *fromlen);

sockfd:标识一个已连接套接字的描述字。

buf:接收数据缓冲区。

len:缓冲区长度。

flags:调用操作方式。一般为0, 是一个或者多个标志的组合体,可通过“ | ”操作符连在一起:

from:指针,指向装有源地址的缓冲区。可选,如不关心,则为null

fromlen:指针,指向from缓冲区长度值。可选,如不关心,则为null

返回值

如果正确接收返回接收到的字节数,失败返回-1.返回0也是可行的。tcp的read返回0表示对端已关闭,而udp不是。

udp和tcp都有接收缓冲区,在没有收到数据时,默认是阻塞的,可通过设置超时返回(如select机制,默认最大描述符为1024)

延伸connect

1.没有3路握手,只是检查是否存在立即可知的错误(如网络不可达,但如果是网络可达,但应用程序没启动检查不到的,只有调sendto才返回)

2.如果udp调用connect后,不能使用sendto(或者不能在sendto制定目的地址)

3.如果udp调用connect后,只能接受connect所指定地址的数据报,不能接受别的套接字的数据。

相当于不能进行广播和组播了

4.好处:就是只显性连接一次,可以多次发送数据。传输效率更高。

不然调用sendto就是连接一次,发送一次,断开连接一次。再连接,发送,断开连接

5.多次调用connect,可以指定新的目的地址和端口和断开套接字

关闭closesocket() :

只是将socket的资源归还给协议栈。

发送端:

1.创建,socket()函数

2.发送 sendto ()函数:

适合在从同一个socket向不同的远程主机发送数据。

或还有一种办法是,调用connect()函数,再调用send()函数。适用于向同一个远程地址发送数据。

int sendto ( socket s , const void * msg, int len, unsigned int flags, const struct sockaddr * to , int tolen ) ;

send 和 sendto 函数在 UDP 层没有输出缓冲区,因此sendto不会阻塞。而是直接返回。

s 套接字

buff 待发送数据的缓冲区

size 缓冲区长度

Flags 调用方式标志位, 一般为0, 改变Flags,将会改变Sendto发送的形式

addr (可选)指针,指向目的套接字的地址

len addr所指地址的长度

成功则返回实际传送出去的字符数,失败返回-1,返回是0也是可以的。

udp并没有真正的发送缓冲区,和tcp还是有区别的。

对客户端的udp而言,进程首次调用sendto时,绑定一个临时端口,不能在修改了。而ip地址可以随客户发送的udp数据报而变动。

udp sendto输出操作成功返回仅仅表示在接口输出队列中具有存放所形成ip数据报的空间。

说明了udp套接字:由它引发的异步错误并不返回给它,除非它也连接(即如调用了connect函数)。这也是udp使用connect的初衷。

主要区别在于:udp获取目标ip地址的方法是recvmsg函数。

在客户端udp中,调用connect并没有和tcp的三路握手,只能是内核检查存放立即可知的错误,记录对端的ip地址和端口,然后立即返回到进程。如果使用了connect函数,就只能使用read(recv)替代recvfrom ,write(send)替代sendto.

udp可以多次调用connect,其目的是指定新的ip地址和端口,或断开套接字。tcp只能一次调用connect。

调用connect并不给服务器发送任何信息,只是保存对端的ip地址和端口号。

tcp和udp可以同用一个端口。

对于已连接的udp套接字可以调用sendto,但不能指定目的地址。

对于已连接的udp套接字只能通过connect断开连接,而不能通过shutdown.

3.关闭closesocket

主要在udp中,可以扩展,从单播到组播,或广播。可以在同一应用程序同时接收和发送。

udp套接字显性地绑定一个本地ip接口,并发送数据。出现什么情况

udp套接字不会真正和网络接口绑定在一起,而是建立一种关联,即被绑定的ip接口地址成为发出去的udp数据报的源ip地址。

一个数据报即udp只要一打开就处于可写的状态,一经命名就处于可读的状态。

所以,应用程序下socket()调用后,马上可以发送数据。

在调用bind()显式命名或调用sendto函数隐式命名后,马上就可以接收数据。

对于udp多播,发送应用进程的套接字可以不必加入到多播组。

udp广播

一直都UDP层(通过端口号),才能确定是否要丢弃广播数据。

发送端:

1.创建udp套接字,无须绑定端口和地址。

2.设置udp套接字的广播标志。

int flag = 1;

setsockopt(server_sockfd , SOL_SOCKET , SO_BROADCAST , &flag , sizeof(flag) );

3.调用sendto函数,参数中需要明确广播地址和端口号。

#define BROADCAST_IP "192.168.1.255"
#define CLIENT_PORT 9000
 bzero(&clientaddr , sizeof(clientaddr));
clientaddr.sin_family = AF_INET;
inet_pton(AF_INET , BROADCAST_IP , &clientaddr.sin_addr.s_addr);
 clientaddr.sin_port = htons(CLIENT_PORT); 
sendto(server_sockfd , buf , strlen(buf) , 0 , (struct sockaddr *)&clientaddr , sizeof(clientaddr));

接收端:

  1. 创建udp套接字,必须绑定端口(该端口是发送端发送的端口)。绑定的IP不可以使用“127.0.0.1”,可以使用真实IP地址或者INADDR_ANY。否则接收失败。

 bzero(&localaddr , sizeof(localaddr));
 localaddr.sin_family = AF_INET;
inet_pton(AF_INET , "0.0.0.0" , &localaddr.sin_addr.s_addr);
 localaddr.sin_port = htons(CLIENT_PORT);
 int ret = bind(confd , (struct sockaddr *)&localaddr , sizeof(localaddr));

2.接收方的Socket不需要设置成广播属性。

3.调用recvfrom函数进行接收.

len = recvfrom(confd , buf , sizeof(buf),0 ,(struct sockaddr*)&from,(socklen_t*)&len);

//from为发送端的本地地址。

相关的广播知识点:

BOOL bBroadcast=TRUE;

setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(BOOL));

特点:

1.广播的数据在子网的所有主机都接收。直到传输层才决定是否丢弃。

2.不能够跨越不同的网络,被路由器所隔离开,即只能在局域网,不能应用到广域网

3.接收端的端口号要与广播端绑定的端口号一样。

udp组播

组播在网卡处就对接收地址进行判断,从而丢弃数据包。

一个ip地址可以加入到多个组播组。

1.地址范围:D类IP地址。范围:224.0.0.0~239.255.255.255

2.组播组:永久/临时。永久组播组一般由官方分配。

3.224.0.0.0~224.0.0.255为预留的组播地址,即永久组地址。地址224.0.0.0保留不做分配,其它地址供路由协议使用。

4.224.0.1.0~224.0.1.255是公用组播地址,可以用于Internet。

5.224.0.2.0~238.255.255.255为用户可用的组播地址(临时组地址),全网范围内有效。

6.239.0.0.0~239.255.255.255为本地管理组播地址,仅在特定的本地范围内有效。

发送端:

1.创建udp套接字,无须绑定端口和地址。

2.调用sendto函数,参数中需要明确组播地址和端口号。

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
 
int main()
{
    int server = 0;
    struct sockaddr_in saddr = {0};
    int client = 0;
    struct sockaddr_in remote = {0};
    socklen_t asize = 0;
    int len = 0;
    char buf[32] = "Software";
    int r = 0;
    //int brd = 1;
 
    server = socket(PF_INET, SOCK_DGRAM, 0);
 
    if( server == -1 )
    {
        printf("server socket error\n");
        return -1;
    }
 
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = htonl(INADDR_ANY); // 本机地址
    saddr.sin_port = htons(8888);
 
    if( bind(server, (struct sockaddr*)&saddr, sizeof(saddr)) == -1 )
    {
        printf("udp server bind error\n");
        return -1;
    }
 
    printf("udp server start success\n");
 
    remote.sin_family = AF_INET;
    remote.sin_addr.s_addr = inet_addr("224.1.1.168"); //设置一个多播地址
    remote.sin_port = htons(9000);
 
    while( 1 )
    {
        len = sizeof(remote);
 
        r = strlen(buf);
        
        sendto(server, buf, r, 0, (struct sockaddr*)&remote, len);
 
        sleep(1);
    }
    
    close(server);
 
    return 0;
}
 

接收端:

1.创建套接字,必须绑定端口(该端口是发送端发送的端口)。绑定的IP不可以使用“127.0.0.1”,可以使用真实IP地址或者INADDR_ANY。否则接收失败。

2.把当前本地的ip地址加人到组播地址。

3.调用recvfrom获取数据,参数为发送端的本地地址。

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
 
int main()
{
    int sock = 0;
    struct sockaddr_in addr = {0};
    struct sockaddr_in remote = {0};
    int len = 0;
    char buf[128] = {0};
    char input[32] = {0};
    int r = 0;
 
    //多播
    struct ip_mreq group={0};
 
    sock = socket(PF_INET, SOCK_DGRAM, 0);
 
    if( sock == -1 )
    {
        printf("socket error\n");
        return -1;
    }
 
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = htons(9000);
 
    if( bind(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1 )
    {
        printf("udp bind error\n");
        return -1;
    }
 
    //remote.sin_family = AF_INET;
    //remote.sin_addr.s_addr = inet_addr("127.0.0.1");
    //remote.sin_port = htons(8888);
 
    group.imr_multiaddr.s_addr=inet_addr("224.1.1.168");
    group.imr_interface.s_addr=htonl(INADDR_ANY); //local host
    //这里INADDR_ANY 为0.0.0.0 通过看ipconfig/ifconfig 可以看到有多个
    //网络ip地址,这个时候让操作系统选择哪一个端口进行多播数据收发。
    //在实际的工程中需要明确指定需要哪一个网络地址进行多播数据收发,
    //不能完全依赖操作系统,否者有时候能够收到数据,有时候收不到数据。
    
    setsockopt(sock,IPPROTO_IP,IP_ADD_MEMBERSHIP,&group,sizeof(group));
 
    while( 1 )
    {
        len=sizeof(remote);
 
        r = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr*)&remote, &len);
 
        if( r > 0 )
        {
            buf[r] = 0;
 
            printf("Receive: %s\n", buf);
        }
        else
        {
            break;
        }
    }
 
    close(sock);
 
    return 0;
}

多播(组播)相关知识点:

ip多播:采用的是“无根”的通讯方式,组内的成员可以相互之间发送和接收数据。IPv4 多播是一个D类的IP地址,范围:224.0.0.0 ----239.255.255.255 之间。

如果多播需要在多个网络断传输,需要考虑使用IGMP协议,该协议运行在主机和组播路由器之间。此外一个套接字注意TTL的值,其作用主要显示数据能传输多远。

1.多播服务端针对特定多播地址只发送一次数据,但是组内的所有客户端都能收到数据

也就是说如果自身不想接受数据,就不要把自己加入到组播地址去。

2.加入特定的多播组即可接收发往该多播组的数据。如果自己想接收数据,就把自己加入组播地址

3.与单播一样,多播是允许在广域网即Internet上进行传输的,而广播仅仅在同一局域网上才能进行;即可以跨网段,跨路由器.所以多播时,路由器能够复制数据并进行转发

常用函数

IPPROTO_IP

当接收者加入到一个多播组以后,再向这个多播组发送数据,这个字段的设置是否允许再返回到本身。

int loop=1; //1:on 0:off

setsockopt(sock,IPPROTO_IP,IP_MULTICAST_LOOP,&loop,sizeof(loop));

IP_MULTICAST_TTL

默认情况下,多播报文的TTL被设置成了1,也就是说到这个报文在网络传送的时候,它只能在自己所在的网络传送,当要向外发送的时候,路由器把TTL减1以后变成了0,这个报文就已经被Discard了。

IP_MULTICAST_IF设置多播接口地址

struct in_addr addr;

setsockopt(s,IPPROTO_IP,IP_MULTICAST_IF,&addr,sizeof(addr))

IP_ADD_MEMBERSHIP 加入一个组播组

struct ip_mreq ipmr;

ipmr.imr_interface.s_addr = inet_addr("192.168.101.1");

ipmr.imr_multiaddr.s_addr = inet_addr("234.5.6.7");

setsockopt(s, IPPROTO_IP, IP_ADDR_MEMBERSHIP, (char*)&ipmr, sizeof(ipmr));

IP_DROP_MEMBERSHIP 离开一个多播组


http://www.niftyadmin.cn/n/52225.html

相关文章

GEE:下载研究区同一天的Landsat影像

本文记录了下载Landsat逐日数据的代码,包装成了函数。直接输入数据集合就可以直接使用。 并在下文中应用了该函数,以下载2022年逐日地表温度LST数据,和下载研究区多波段影像为例。 结果如图所示 文章目录 一、调用方法二、Landsat 逐日下载函数三、应用示例1——下载2022年研…

Windows10下安装git

文章目录安装环境&#xff1a;Windows10 64bit官网下载&#xff1a;https://git-scm.com/download/win国内下载源&#xff1a;https://npm.taobao.org/mirrors/git-for-windows/ 从官网下载Git安装包&#xff0c;双击下载后的安装包Git-2.32.0-64-bit.exe,开始安装 选择HTTPS…

【Java】int和Integer的区别?为什么有包装类?

int和Integer的区别&#xff1f;为什么有包装类&#xff1f; java是一种强类型的语言&#xff0c;所以所有的属性都必须要有一个数据类型。 PS&#xff1a;java10有了局部变量类型推导&#xff0c;可以使用var来代替某个具体的数据类型&#xff0c;但是在字节码阶段&#xff0…

配置类的几种写法

需求 通过java配置类实现一个数据库连接池。 以前xml中是这样写的&#xff1a; <!--配置德鲁伊数据库连接池--><bean id"ds" class"com.alibaba.druid.pool.DruidDataSource"><property name"driverClassName" value"${jdbc…

牛客NC14661--简单的数据结构

栗酱有一天在网上冲浪的时候发现了一道很有意思的数据结构题。 该数据结构形如长条形。 一开始该容器为空&#xff0c;有以下七种操作。 1 a从前面插入元素a 2 从前面删除一个元素 3 a从后面插入一个元素 4 从后面删除一个元素 5 将整个容器头尾翻转 6 输出个数和所有元…

Bert文本聚类实践

问题来源&#xff1a;先做的huggingface-bert文本分类&#xff08;参考text-classification&#xff0c;情感分类&#xff0c;数据集可以考虑SST2&#xff09;&#xff0c;但是数据量太大了&#xff0c;无法穷举所有的类别&#xff0c;故而先用分类来做&#xff0c;但这样也有一…

Power BI 筛选器函数---Index实例详解

一、Index函数 语法&#xff1a; INDEX ( <检索行号>, [<关系>], [<OrderBy>],[空白],[PartitionBy] ) 含义&#xff1a; 对指定分区(PartitioinBy)中的行&#xff08;关系表&#xff09;&#xff0c;按指定的列进行排序(OrderBy)后&#xff0c;根据&…

shell函数

文章目录六、函数和数组6.1 函数6.1.1什么是函数6.1.2 函数语法6.1.3 函数的调用6.1.4 函数的返回值6.1.5 函数案例6.1.6 函数库文件6.1.7 递归函数6.2 数组6.2.1 定义数组6.2.2 数组操作6.2.3 数组案例六、函数和数组 6.1 函数 函数几乎是学习所有的程序设计语言时都必须过的…