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

相关文章

零基础STM32单片机编程入门(一)初识STM32单片机

文章目录 一.概要二.单片机型号命名规则三.STM32F103系统架构四.STM32F103C8T6单片机启动流程五.STM32F103C8T6单片机主要外设资源六.编程过程中芯片数据手册的作用1.单片机外设资源情况2.STM32单片机内部框图3.STM32单片机管脚图4.STM32单片机每个管脚可配功能5.单片机功耗数据6.FALSH编程时间,擦写次数7.I/O高低电平电压表格8.外设接口

16.Spring前世今生与Spring编程思想

1.1.课程目标 1、通过对本章内容的学习,可以掌握Spring的基本架构及各子模块之间的依赖关系。 2、 了解Spring的发展历史,启发思维。 3、 对 Spring形成一个整体的认识,为之后的深入学习做铺垫。 4、 通过对本章内容的学习,可以了解Spring版本升级的规律,从而应用到自己的系统升级版本命名。 5、Spring编程思想总结。 1.2.内容定位 Spring使用经验

IPython小白教程:提升你的Python交互式编程技巧,通俗易懂!

IPython是一个增强的Python交互式shell,它提供了丰富的功能和便捷的交互方式,使得Python开发和数据分析工作更加高效。本文将详细介绍IPython的基本概念、使用方法、主要作用以及注意事项。 一、IPython简介 1. IPython的起源 IPython由Fernando Pérez于2001年创建,旨在提供一个更高效的Python交互式编程环境。 2. IPyt

从《深入设计模式》一书中学到的编程智慧

软件设计原则   优秀设计的特征   在开始学习实际的模式前,让我们来看看软件架构的设计过程,了解一下需要达成目标与需要尽量避免的陷阱。 代码复用 无论是开发何种软件产品,成本和时间都最重要的两个维度。较短的开发时间意味着可比竞争对手更早进入市场; 较低的开发成本意味着能够留出更多营销资金,因此能更广泛地覆盖潜在客户。 代码复用是减少开发成本时最常用的方式之一。其意图

C语言入门系列:初识函数

文章目录 一,C语言函数与数学函数的区别1,回忆杀-初中数学2,C语言中的函数 二, 函数的声明1,函数头1.1,函数名称1.2,返回值类型1.3,参数列表 2,函数体2.1,函数体2.2,return语句 三,main函数四,函数的参数与传递方式1,实参和形参1.1,函数定义(含形参)1.2,函数调用(使用实参) 2,参数传递方式2.1,值传递2.2,引用传递 五,函数原型与预声明1,

Java并发编程—阻塞队列源码分析

在前面几篇文章中,我们讨论了同步容器(Hashtable、Vector),也讨论了并发容器(ConcurrentHashMap、CopyOnWriteArrayList),这些工具都为我们编写多线程程序提供了很大的方便。今天我们来讨论另外一类容器:阻塞队列。   在前面我们接触的队列都是非阻塞队列,比如PriorityQueue、LinkedList(LinkedList是双向链表,它实现了D

剑指offer—编程题7(用两个栈实现一个队列)

题目:用两个栈实现一个队列。队列的声明如下,请实现它的两个函数appendTail 和deleteHead,分别完成在队列尾部插入结点和在队列头部删除结点的功能。 代码如下: [java]  view plain copy print ? public class Test07 {       /**       * 用两个栈模拟的队列       *

剑指Offer—编程题4 ( 替换空格)

一、题目:替换空格 题目:请实现一个函数,把字符串中的每个空格替换成"%20"。例如输入“We are happy.”,则输出“We%20are%20happy.”。    在网络编程中,如果URL参数中含有特殊字符,如空格、'#'等,可能导致服务器端无法获得正确的参数值。我们需要将这些特殊符号转换成服务器可以识别的字符。转换的规则是在'%'后面跟上ASCII码的两位十六进制的表示。

剑指Offer—编程题56(链表中环的入口地址)

题目:一个链表中包含环,如何找出环的入口结点? 解题思路   可以用两个指针来解决这个问题。先定义两个指针P1和P2指向链表的头结点。如果链表中环有n个结点,指针P1在链表上向前移动n步,然后两个指针以相同的速度向前移动。当第二个指针指向环的入口结点时,第一个指针已经围绕着环走了一圈又回到了入口结点。    剩下的问题就是如何得到环中结点的数目。我们在面试题15的第二个相关题目时用到