UDP编程初识

2024-06-22 20:08
文章标签 初识 编程 udp

本文主要是介绍UDP编程初识,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

复习:
TCP
    每个TCP套接字都有一个发送区,我们可以使用SO_SNDBUF来更改缓冲区的大小,当进程调用write时,内核从该应用进程的缓冲区中复制所有数据到套接字的缓冲区。如果该套接字的发送缓冲区容不下该应用进程的所有数据(或是应用进程的缓冲区大于套接字的发送缓冲区,或是套接字的发送缓冲区已有其他数据),该应用进程将被投入睡眠(这里的套接字是阻塞的),内核将不从write系统调用返回,直到应用进程缓冲区的所有数据都被复制到套接字的发送缓冲区。
    因此, 从写一个套接字的write调用成功返回,仅仅表示我们可以重新使用原来的应用进程的缓冲区,并不表明对方已经接收到数据
     对端TCP必须确认收到的TCP数据,伴随对端不断到来的ACK,本端TCP才能从套接字发送缓冲区中丢弃已确认的数据。TCP必须为已发送的数据保留一份副本,直到它被对端确认为止


UDP
    套接字发送缓冲区实际上并不存在。任何UDP套接字都有发送缓冲区的大小(也可用SO_SNDBUF改变它),不过它 仅仅是可写到该套接字的UDP数据报的大小上限。如果一个应用进程发送一个大于套接字发送缓冲区大小的数据,内核将返回EMSGSIZE错误。
    既然UDP是不可靠的,就不必为发送的数据保留副本,因此无需一个真正的缓冲区
     从写一个UDP套接字的write调用成功返回表示所写的数据报或其所有片段已被加入数据链路层的输出队列。如果该队列放不下,会返回ENOBUFS错误(看系统实现是否返回错误)


udp套接字:
ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags, struct sockaddr *from, socklen_t *addrlen);
ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags, const struct sockaddr *to, socklen_t addrlen);



一般来说,大多数TCP服务器是并发的,而大多数UDP服务器是迭代的

每个UDP套接字都有一个接收缓冲区,到达该套接字的每个数据报都进入这个缓冲区。当进程调用recvfrom时,缓冲区中的下一个数据报以FIFO(先进先出)顺序返回给进程。这样,在进程能够读该套接字中任何已排好队的数据报之前,如果有多个数据报到达该套接字,那么相继到达的数据报仅仅加到该套接字的接收缓冲区中,然而这个缓冲区也是有大小限制的
    

    如果调用客户端时,服务器并没有运行,那么当我们发送一段文本后,客户将永远阻塞在recvfrom 调用上,例如:
$ ./udpcli01 127.0.0.1
damk



    此时我们用tcpdump观察到:
15:37:30.435580 IP localhost.55815 > localhost.echo: UDP, length 5
15:37:30.435637 IP localhost > localhost: ICMP localhost udp port echo unreachable, length 41



    服务器主机响应的是一个ICMP报文,但是这个ICMP报文并不返回给客户进程。
    我们称这个ICMP错误为异步错误。该错误由sendto引起,但是sendto本身却成功返回。通过之前的了解,我们知道UDP输出操作成功返回仅仅标志在接口输出队列中具有存放所形成IP数据报的空间。该ICMP直到后来才返回,即称其为异步。
    一个基本规则是: 对于一个UDP套接字,由它引发的异步错误并不返回给它,除非它已连接


之前提起过,客户的IP和端口号都由内核来选择,尽管我们可以使用bind来指定它们。
    在由内核选择的前提下,客户的临时端口是在第一次调用sendto时一次性选定,以后不再改;然而客户的IP地址却可以随客户发送的每个UDP数据报而变动,其原因是:
        如果客户主机是多宿的,客户有可能在两个目的地之间交替选择,一个由A数据链路外出,另一个由B外出。最坏情况下,由内核基于外出数据链路选择的客户IP地址将随每个数据报而改变





UDP的connect函数:
    以上提到,"由它引发的异步错误并不返回给它,除非它已连接",我们确实可以给UDP套接字使用connect函数(但是没有三路握手)。    
     在UDP套接字上调用connect并不给对端主机发送任何消息,它完全是一个本地操作,只是保留对端的IP地址和端口号

    因此,UDP也可分为
        1、未连接的UDP
        2、已连接的UDP

    相较而言,已连接的UDP发生了三个变化:
        1、我们再也不给输出操作指定目的IP地址和端口号了    
        2、不需要使用recvfrom获取发送者信息
        3、由已连接的UDP套接字引发的异步错误会返回给其所在进程

    另外,拥有一个已连接的UDP套接字的进程可以为以下两个目的之一再次调用connect
        1、指定新的IP地址和端口号(对于TCP,connect只能调用一次)
        2、断开套接字(只要在再次调用时把地址族改为 AF_UNSPEC)

性能:
    一个未连接的UDP套接字发送两个数据报步骤:
        1、连接套接字
        2、输出第一个数据报
        3、断开连接
        4、连接套接字
        5、输出第二个数据报
        6、断开连接
        
    一个已连接的UDP数据报:
        1、连接
        2、输出第一个
        3、输出第二个

    当我们启动客户端时,此刻服务器并没有打开:

$ ./udpcli01 127.0.0.1
asd
read error: Connection refused


    
    可以发现,在启动客户端时并没有发生错误,但是在发送文本后跳出了错误

    tcpdump的输出如下:(下面两行皆是在发送文本后才显示的)
16:36:32.012542 IP localhost.45233 > localhost.13000: UDP, length 4
16:36:32.012600 IP localhost > localhost: ICMP localhost udp port 13000 unreachable, length 40




UDP缺乏流量控制
    书上举了个例子,客户向服务器发送2000个1400字节的数据报,最后服务器只接收到了30个,其他数据报都是因为缓冲区满而被丢弃。
    然而当我们使用 SO_RCVBUF 将缓冲区增大时,的确改善了,但并没有从根本上解决问题


以下程序是本章用的例子:
#include "unp.h"void dg_cli(FILE *fp, int fd, struct sockaddr *addr, socklen_t alen);int main(int ac, char *av[])
{int sockfd;struct sockaddr_in servaddr;if(ac != 2){fprintf(stderr, "Usage : %s ipaddress\n",av[0]);exit(1);}if((sockfd=socket(AF_INET,SOCK_DGRAM,0)) < 0)oops("socket error");bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_port = htons(13000);inet_pton(AF_INET,av[1],&servaddr.sin_addr);dg_cli(stdin, sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));return 0;
}//Here we set NULL in recvfrom,
//If we get data from other services,  it would still be considered as the response from the service which we are waiting for.
/*void dg_cli(FILE *fp, int fd, struct sockaddr *addr, socklen_t alen)
{char mesg[MAXLEN];ssize_t n;while(fgets(mesg,MAXLEN,stdin) != NULL){if(sendto(fd, mesg, strlen(mesg), 0, addr, alen) < 0)oops("sendto error");mesg[0] = '\0';if((n=recvfrom(fd,mesg,MAXLEN,0,NULL,NULL)) < 0)oops("recvfrom error");mesg[n] = 0;if(write(1,mesg,n) < 0)oops("write error");}
}*///try to match the right service through some methods
/*
void dg_cli(FILE *fp, int fd, struct sockaddr *addr, socklen_t alen)
{char mesg[MAXLEN];ssize_t n;struct sockaddr *temp;int tlen;if((temp=malloc(sizeof(alen))) == NULL)oops("Memory is out\n");while(fgets(mesg,MAXLEN,stdin) != NULL){if(sendto(fd, mesg, strlen(mesg), 0, addr, alen) < 0)oops("sendto error");mesg[0] = '\0';tlen = alen;if((n=recvfrom(fd,mesg,MAXLEN,0,temp,&tlen)) < 0)oops("recvfrom error");
//        if( tlen!=alen || memcmp(temp,addr,tlen-4)!=0 )
//        {
//            printf("This data is not from the service which we are waiting for\n");
//            continue;
//        }
//    mesg[n] = 0;if(write(1,mesg,n) < 0)oops("write error");}
}*///Here we try "connected UDP"
void dg_cli(FILE *fp, int fd, struct sockaddr *addr, socklen_t alen)
{char buf[MAXLEN];int n;if(connect(fd, addr, alen) < 0)oops("connect error");while(fgets(buf, MAXLEN, fp) != NULL){if(write(fd, buf, strlen(buf)) < 0)oops("write error");if((n=read(fd, buf, MAXLEN)) < 0)oops("read error");buf[n] = '\0';if(write(1,buf,n) < 0)oops("write error");}
}


这篇关于UDP编程初识的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1085289

相关文章

Go语言数据库编程GORM 的基本使用详解

《Go语言数据库编程GORM的基本使用详解》GORM是Go语言流行的ORM框架,封装database/sql,支持自动迁移、关联、事务等,提供CRUD、条件查询、钩子函数、日志等功能,简化数据库操作... 目录一、安装与初始化1. 安装 GORM 及数据库驱动2. 建立数据库连接二、定义模型结构体三、自动迁

Python 异步编程 asyncio简介及基本用法

《Python异步编程asyncio简介及基本用法》asyncio是Python的一个库,用于编写并发代码,使用协程、任务和Futures来处理I/O密集型和高延迟操作,本文给大家介绍Python... 目录1、asyncio是什么IO密集型任务特征2、怎么用1、基本用法2、关键字 async1、async

Java并发编程之如何优雅关闭钩子Shutdown Hook

《Java并发编程之如何优雅关闭钩子ShutdownHook》这篇文章主要为大家详细介绍了Java如何实现优雅关闭钩子ShutdownHook,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起... 目录关闭钩子简介关闭钩子应用场景数据库连接实战演示使用关闭钩子的注意事项开源框架中的关闭钩子机制1.

shell编程之函数与数组的使用详解

《shell编程之函数与数组的使用详解》:本文主要介绍shell编程之函数与数组的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录shell函数函数的用法俩个数求和系统资源监控并报警函数函数变量的作用范围函数的参数递归函数shell数组获取数组的长度读取某下的

揭秘Python Socket网络编程的7种硬核用法

《揭秘PythonSocket网络编程的7种硬核用法》Socket不仅能做聊天室,还能干一大堆硬核操作,这篇文章就带大家看看Python网络编程的7种超实用玩法,感兴趣的小伙伴可以跟随小编一起... 目录1.端口扫描器:探测开放端口2.简易 HTTP 服务器:10 秒搭个网页3.局域网游戏:多人联机对战4.

Java并发编程必备之Synchronized关键字深入解析

《Java并发编程必备之Synchronized关键字深入解析》本文我们深入探索了Java中的Synchronized关键字,包括其互斥性和可重入性的特性,文章详细介绍了Synchronized的三种... 目录一、前言二、Synchronized关键字2.1 Synchronized的特性1. 互斥2.

Python异步编程中asyncio.gather的并发控制详解

《Python异步编程中asyncio.gather的并发控制详解》在Python异步编程生态中,asyncio.gather是并发任务调度的核心工具,本文将通过实际场景和代码示例,展示如何结合信号量... 目录一、asyncio.gather的原始行为解析二、信号量控制法:给并发装上"节流阀"三、进阶控制

C#多线程编程中导致死锁的常见陷阱和避免方法

《C#多线程编程中导致死锁的常见陷阱和避免方法》在C#多线程编程中,死锁(Deadlock)是一种常见的、令人头疼的错误,死锁通常发生在多个线程试图获取多个资源的锁时,导致相互等待对方释放资源,最终形... 目录引言1. 什么是死锁?死锁的典型条件:2. 导致死锁的常见原因2.1 锁的顺序问题错误示例:不同

PyCharm接入DeepSeek实现AI编程的操作流程

《PyCharm接入DeepSeek实现AI编程的操作流程》DeepSeek是一家专注于人工智能技术研发的公司,致力于开发高性能、低成本的AI模型,接下来,我们把DeepSeek接入到PyCharm中... 目录引言效果演示创建API key在PyCharm中下载Continue插件配置Continue引言

C#反射编程之GetConstructor()方法解读

《C#反射编程之GetConstructor()方法解读》C#中Type类的GetConstructor()方法用于获取指定类型的构造函数,该方法有多个重载版本,可以根据不同的参数获取不同特性的构造函... 目录C# GetConstructor()方法有4个重载以GetConstructor(Type[]