Reliable UDP

OSI(Open System Interconnection,开放系统互联)模型通常划分为应用层、传输层、网络层、数据链路层、物理层,底下两层通常指随操作系统提供的驱动程序和网络硬件,传输层有TCP(Transmission Control Protocol,传输控制协议)、UDP(User Datagram Protocol,用户数据报协议)等协议,网络层有IPv4,IPv6等协议,网络应用所在的层即应用层。TCP/IP指传输层为TCP、网络层为IP的协议族。

UDP无连接,可靠性应由应用层保证。

超时重传,分组验证是要处理的两个基本问题。

考虑一问一答式的回射服务器。

要实现超时重传,就要在包中携带时间,这个时间用在计算RTT(Round-trip Timeout,回环时间)上,并在发包后alarm一个timer,它的参数是RTO(Retransmission Timeout,重传超时),这个值要根据RTT动态计算,然后pselect阻塞等待回应。如果超时到了,仍没收到回应,那么pselect就会被中断,这时候重新算RTO,再发包。

进行分组验证是在包中携带一个序列号。

怎么简单地向包中添加这些信息呢?使用sendmsg与recvmsg就可以做到。

 

int
main(int argc, char **argv)
{
    int sockfd;
    struct sockaddr_in servaddr;

    if(argc != 2)
    {
        printf("usage: udpcli <IPaddress>\n");
        return -1;
    }

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    servaddr.sin_addr.s_addr = inet_addr(argv[1]);

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    dg_cli(stdin, sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
}

 

void
dg_cli(FILE *fp, int sockfd, struct sockaddr *pservaddr, socklen_t servlen)
{
    int n;
    char sendline[MAXLINE], recvline[MAXLINE];

    while(fgets(sendline, MAXLINE, fp) != NULL)
    {
        n = dg_send_recv(sockfd, sendline, strlen(sendline),
                recvline, MAXLINE, pservaddr, servlen);

        if(n < 0)
            return;

        recvline[n] = 0;
        fputs(recvline, stdout);
    }
}

 

ssize_t
dg_send_recv(int fd, void *outbuff, size_t outbytes,
    void *inbuff, size_t inbytes,
    struct sockaddr *destaddr, socklen_t destlen)
{
    ssize_t n;
    fd_set rset;
    struct sigaction sa;
    sigset_t sigset_alarm, sigset_empty;
    struct iovec iovsend[2], iovrecv[2];

    if(rttinit == 0)
    {
        rtt_init(&rttinfo);
        rttinit = 1;
    }

    sendhdr.seq++;
    msgsend.msg_name = destaddr;
    msgsend.msg_namelen = destlen;
    msgsend.msg_iov = iovsend;
    msgsend.msg_iovlen = 2;
    iovsend[0].iov_base = &sendhdr;
    iovsend[0].iov_len = sizeof(sendhdr);
    iovsend[1].iov_base = outbuff;
    iovsend[1].iov_len = outbytes;

    msgrecv.msg_name = NULL;
    msgrecv.msg_namelen = 0;
    msgrecv.msg_iov = iovrecv;
    msgrecv.msg_iovlen = 2;
    iovrecv[0].iov_base = &recvhdr;
    iovrecv[0].iov_len = sizeof(recvhdr);
    iovrecv[1].iov_base = inbuff;
    iovrecv[1].iov_len = inbytes;

    sigemptyset(&sigset_alarm);
    sigemptyset(&sigset_empty);
    sigaddset(&sigset_alarm, SIGALRM); // 构造信号集
    
    sa.sa_handler = sig_alarm;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGALRM, &sa, NULL); // 注册alrm信号处理函数sig_alarm

    rtt_newpack(&rttinfo);
    FD_ZERO(&rset);
    while(1)
    {
        sendhdr.ts = rtt_ts(&rttinfo);
        sendmsg(fd, &msgsend, 0);
        
        alarm(rtt_start(&rttinfo));
        
loop:
        FD_SET(fd, &rset);
        sigprocmask(SIG_BLOCK, &sigset_alarm, NULL); // 临时阻塞alrm信号,否则将会?考虑这样一种情况,在调pselect之前,alrm信号被触发,如果pselect只等待中断,那么它将永远阻塞
        if(pselect(fd+1, &rset, NULL, NULL, NULL, &sigset_empty) < 0)
        {
            if(errno == EINTR)
            {
                if(rtt_timeout(&rttinfo) < 0) // 超时验证
                {
                    printf("dg_send_recv: no response from server, giving up");
                    rttinit = 0;
                    return -1;
                }
            }
        }
        if(FD_ISSET(fd, &rset))
        {
            if((n = recvmsg(fd, &msgrecv, 0)) < 0)
            {
                goto loop;
            }
            if(n < sizeof(struct hdr) || recvhdr.seq != sendhdr.seq) // 分组验证
            {
                goto loop;
            }
            alarm(0);
            rtt_stop(&rttinfo, sendhdr.ts);
            return (n - sizeof(struct hdr));
        }
    }
}

 

你可能感兴趣的:(UDP)