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