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");
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值