网络学习八-UDP

UDP C/S的典型函数调用

Paste_Image.png

UDP没有像TCP那样的连接,客户端直接sendto向某服务器发送数据,服务器端一直recvfrom阻塞,以接收任何客户端发送的数据。

sendto和recvfrom函数

int sendto(int sockfd, const void* buff, size_t nbytes, int flag, const struct sockaddr* to, socklen_taddrlen);
int recvfrom(int sockfd, void* buff, size_t nbytes, int flag, struct sockaddr* from, socklen_t* addrlen); 
  1. 成功返回字节数,失败返回-1
  1. 这两个函数相比较于read和write多了三个参数
    (1) flag后面说,这里先置为0
    (2) sendto的地址结构指明发送目的地的套接字地址。addrlen指明地址长度,为整数型。相当于TCP的connect中的套接字地址。
    (3) recvfrom的地址结构指明发送此数据报的发送端的套接字地址。addrlen为此套接字地址,为整型地址。相当于TCP的accept中的套接字地址。
  2. 写一个长度为0的数据报是可行的,会形成一个只包含IP首部(20字节)和UDP首部(8字节)的IP数据报。所以recvfrom返回0,是可接受的。而不是像TCP那样read返回0表示关闭连接。
  3. recvfrom的套接字地址参数可以是NULL,表示不关心数据是谁发的。此时addrlen也必须是NULL。

使用UDP书写回射服务器

回射服务器代码 点击查看
回射服务器dg_echo代码 点击查看

  1. 首先正常情况下,函数永不会终止。它不像TCP连接那样,还有终止连接的四次。
  1. 处理函数str_echo,是一个迭代函数,不像TCP那样是并发的。一般TCP服务器都是并发的,而UDP服务器都是迭代的。为何?下面说
  2. 每个UDP套接字都会有一个接收缓冲区,类似于一个队列。多个数据报到达UDP服务器,则会排队,调用recvfrom函数,从这个队列头取出数据报给进程。而TCP是为每个客户一个连接fork一个子进程,并且每个连接一个套接字,每个套接字一个接收缓冲区,所以我们要并发监听每个接收缓冲区。而UDP是任何客户发送的数据报放入一个接收缓冲区,所以根本无需什么并发服务器,也不可能做成并发的。
  3. str_echo函数是协议无关的。

使用UDP重写回射客户端

回射客户端 点击查看
回射客户端dg_cli 点击查看

  1. 没有为客户端指定本地端口,则客户端第一次sendto的时候,内核自动分配。
  1. 这里recvfrom的套接字地址是空指针,这样做是非常危险的
    因为可能任何主机给此客户端的这个临时端口发送一个消息时,此客户端会认为这个消息是从服务器端发送过来的。造成消息混乱。所以这个是有问题的,下面解决。
  2. 数据报的丢失问题
    如果某次客户端发送给服务器端的数据报丢失了,或者服务器端发给客户端的数据包丢失了,则这都会引起客户端永远阻塞在recvfrom函数上。
    我们可以为recvfrom设置一个超时,但是超时还是不能完全解决这个问题。因为如果超时,我们不知道是客户端->服务器端数据报丢失了,还是服务器->客户端数据库丢失了。
  3. 针对上面2提到的问题,我们试着获取recvfrom的套接字地址和sendto发送的套接字地址是否一致,来决定此消息是否是来自对端服务器。

我们修改的str_cli函数如下:

void str_cli(int sockfd, FILE* fd, const struct sockaddr* servaddr, socklen_t addrlen)
{
    int nbytes;
    charbuff[MAXLINE],recvbuff[MAXLINE];
    struct sockaddr * fromaddr=new sockaddr();
    socklen_tfromaddrlen=addrlen;
    while(fgets(buff,MAXLINE,fd)!=NULL)
    {
        sendto(sockfd, buff,strlen(buff), 0, servaddr, addrlen);
        nbytes=recvfrom(sockfd,recvbuff, MAXLINE,0, fromaddr, &fromaddrlen );
        if(fromaddrlen !=addrlen || memcmp(servaddr, fromaddr, addrlen)!=0)
        {
            fputs("not from server, ignored",stdout);
            continue;
        }
        if(nbytes<0)
            err_sys("recvfrom error");
       recvbuff[nbytes]=0;
       fputs(recvbuff,stdout);
    }
}

可以看出,我们就是比较sendto时,服务器的套接字地址,和recvfrom得到的服务器套接字地址是否相等。这里我们先比较两者的长度,然后再逐字节比较。
这里还是有问题的:
如果服务器是多宿主机,即两个IP地址,如Ip1,Ip2。由于我们在写服务器端程序时,bind函数的参数是通配IP,所以当我们sendto时,是使用Ip1,而服务器回射时,内核自动选择了Ip2,则这会让我们客户端误判该回射消息不是来自服务器端。
两个解决办法:
1> recvfrom得到IP后,查询DNS获得主机的域名,以判断消息是否来自该主机。
2>服务器端为每个IP创建一个套接字,使用bind到每个IP地址。然后使用select监听这些套接字,等待其中一个变为可读,说明客户端使用的是这个IP,则服务器使用这个IP套接字回射就可以了。

服务器未运行

当我们先启动客户端,不启动服务器端时,发生了什么:
我们从控制台输入一行数据回车,然后客户端将永远阻塞在recvfrom函数上。
底层机制:
数据发送到服务器主机上,发送主机的目的端口并没有开启,所以返回一个端口不可达的ICMP消息,这个消息是不会返回客户端进程的(为何?)。所以客户端用于阻塞在recvfrom上。
异步错误:本例中错误由sendto函数引起,但是sendto成功返回,ICMP到后来才返回错误。这就是异步错误。
一个基本规则:对于一个UDP套接字,由它引发的异步错误不返回给它,除非它已经连接。(注意UDP也有connect函数)。
为何ICMP消息不会返回客户端进程?Unix这样设计的道理是什么?
假设我们使用客户端连续发送3个消息,2个消息的目的服务器正常,最后一个服务器未启动,则会有一个ICMP消息,假设这个消息被recvfrom获取,recvfrom返回一个负值表示错误,然后errno为错误类型。但是注意此时客户端并不知道是哪个目的服务器,哪个目的套接字出错,内核无法告知进程,因为recvfrom此时返回的信息只是errno,所以Unix规定,不返回给进程。

给UDP套接字使用connect

上面提到未连接UDP套接字发生的异步错误,不会返回给进程,这里我们可以使用connect对一个UDP套接字进行连接。
Connect函数的调用和TCP一样,参数指定目的服务器的套接字地址。注意没有三次握手,只是检查对端是否存在立即可知的错误(如目的主机不可达)。
已连接UDP套接字和未连接UDP套接字的不同:
(1) 已连接套接字,直接使用send、write,发送数据报给connect的目的服务器套接字。而不使用sendto。
(2)已连接套接字,直接使用read、recv或者recvmsg,接收来自connect目的服务器套接字的数据报,来自其他目的服务器套接字的数据报不会递交给该套接字、也就意味着,已连接套接字只能和一个对端进行通信。
而未连接套接字显然可以和任何多个对端通信。
(3) 已连接套接字发生异步消息会返回给进程,因为此时已经知道目的套接字。而未连接套接字不会返回给进程。
注意:一般对客户端的UDP套接字进行connect,而服务器端还是sendto和recvfrom,connect只会影响本地套接字。
(1) 指定新的IP地址和端口号
(2) 断开套接字。此时把套接字地址结构的地址族(IPv4的sin_family)设为AF_UNSPEC就可以了。

性能

当我们对一个未连接的UDP套接字连续sendto两次,看看具体步骤:
连接套接字
发送第一个数据报
断开套接字
连接套接字
发送第二个数据报
断开套接字
如果两个数据报是同一个目的套接字,则我们应该使用显然connect,之后会提高效率。因为这样只需要一个连接和断开。
Unix中一个连接需要耗费一次UDP传输的三分之一的开销。
调用connect后调用两次write涉及内核执行如下步骤:
1、连接套接字
2、输出第一个数据报
3、输出低二个数据报

我们修订上面的客户端str_cli函数

void str_cli( int sockfd, FILE* fd,const struct sockaddr* servaddr, socklen_t addrlen)
{
    int nbytes;
    char buff[MAXLINE],recvbuff[MAXLINE];
       conect(sockfd,servaddr,addrlen);
    while(fgets(buff,MAXLINE,fd)!=NULL)
    {
        write(sockfd, buff, strlen(buff));
        nbytes=read(sockfd,recvbuff, MAXLINE );
        if(nbytes<0)
            err_sys("read error");
       recvbuff[nbytes]=0;
       fputs(recvbuff,stdout);
    }
}

如果为启动服务器,此时我们再运行客户端,输入一个未启动的服务器程序的主机Ip地址,然后从控制台输入一行数据,输出的结果就是readerror。这样异步错误返回给进程了。
注意:此时我们connect时,并没有发生错误,直到我们发送一个消息时才返回错误。而如果此时TCP的话,在connect时就会发生错误。原因?
因为UDP的connect不会触发三次握手,而TCP的connect会触发三次握手,发现目的端口不可达,则服务器会返回RST分组。

UDP缺乏流量控制

假设一个客户端连续发送大量的数据,则服务器端使用套接字接收缓冲区排队接收这些数据,但当发送来的数据超出套接字接收缓冲区时,服务器端就会自动丢弃到来的数据报,而此时客户端和服务器端不会有任何的错误。
所以说UDP是没有流量控制的。

UDP中的IP地址和端口号

  1. 未连接的UDP套接字,如果我们没有bind,
    则当sendto时,内核选择一个本地IP地址和端口号,所以同一主机上两次连续的sendto,两个消息的源IP地址和端口号可能都不一样。
    而且,服务器端接收recvfrom后,回射消息,sendto时,可能造成回射消息的源IP地址和端口号和recvfrom消息的目的IP地址和端口号不一样。
  1. 已连接UDP套接字,如果没有bind
    同一个客户端,多次connect同一个目的IP端口号时,已连接套接字的本地IP和端口号可能都是不一样的。
    我们可以使用getsockname来获取已连接UDP套接字的本地IP和端口号。
    我们使用recvmsg来获取未连接的UDP套接字的本地IP和端口号。

我们使用select的TCP和UDP来重写回射服务器和客户端程序

点击查看代码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,585评论 18 139
  • 1)OSI与TCP/IP各层的结构与功能,都有哪些协议。 OSI分层 (7层):物理层、数据链路层、网络层、传输层...
    ldlywt阅读 2,304评论 0 26
  • iOS网络HTTP、TCP、UDP、Socket 知识总结OSI 七层模型我们一般使用的网络数据传输由下而上共有七...
    蜗牛也有梦想阅读 2,393评论 0 3
  • 转。。。。。。。。 SOCKET,TCP/UDP,HTTP,FTP (一)TCP/UDP,SOCKET,HTTP,...
    zeqinjie阅读 3,266评论 1 53
  • 女神,现含义网络泛指自己心仪的女性,同时也引申指容貌漂亮、有智慧以及综合素质高的女性。女神原意是女性的神明或至尊,...
    助教Shirley阅读 610评论 0 0