运输层

运输层概述

1. 概念解释

运输层的多路复用与多路分解: UDP和TCP最基本的任务是,将两个端系统间IP的交付服务扩展为运行在两个端系统上的进程之间的交付服务,而这种交付服务就是运输层的多路复用与多路分解。
多路分解: 将运输层报文段中的数据交付到正确的套接字的工作。
多路复用: 从源主机的不同套接字中收集数据块,并为每个数据块封装上首部信息(这将在多路分解时使用)从而生成报文段,然后将报文段传递到网络层的工作。
TSAP(传输服务访问点): 表示传输层的一个特殊端点
NSAP(网络服务访问点): 表示网络层的一个特殊端点
差错控制: 确保数据传输具备所需的可靠性,通常指所有的数据均被无差错的传送到目的地。
流量控制: 防止快速发送端淹没慢速接收端。

2. 实例分析

1. 传输服务原语
一个简单的传输服务原语如下:
原语发出的包含义
LISTEN(无)阻塞,直到某个进程试图与之连接
CONNECTCONNECTION REQ主动尝试建立一个连接
SENDDATA发送信息
RECEIVE(无)阻塞,直到到达一个DATA包
DISCONNECTDISCONNECTION REQ请求释放连接
过程如下:
TCP套接字如下:
原语含义
SOCKET创建一个新通信端点
BIND将套接字与一个本地地址关联
LISTEN声明愿意接受连接;给出队列长度
ACCEPT被动创建一个入境连接
CONNECT主动创建一个连接
SEND通过连接发送一些数据
RECEIVE从连接上接收一些数据
CLOSE释放连接
前四个原语由服务器按照顺序执行:SOCKET创建一个新的端点,并且在传输实体中为它分配相应的表空间。此调用的参数说明了采用的地址格式,所需的服务类型(TCP/UDP),以及所用的协议。
新近创建的套接字没有网络地址。通过BIND可以为套接字分配地址。一旦服务器已经将一个地址绑定到一个套接字,则远程客户就能够与它建立连接。
LISTEN调用为入境呼叫分配队列空间,以便在多个客户同时发起连接请求时,将这些入境的连接请求队列依次处理。这里LISTEN并不是一个阻塞调用。
为了阻塞自己以便等待入境连接的到来,服务器执行ACCEPT。当一个请求连接的段到达时,传输实体创建一个新的套接字并返回一个与其关联的文件描述符,这个新套接字与原来的套接字具有同样的属性;然后服务器可以派生一个进程或者线程来处理这个新套接字上的连接,而服务器自身又回到原来的套接字上等待下一个连接请求的到来。
而客户端通过CONNECT来连接服务器,通过SEND/RECEIVE来发送/接收数据。
一个简单的文件服务器:
服务器代码:
#include <sys/types.h>
#include <sys/fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#define SERVER_PORT 12345
#define BUF_SIZE 4096
#define QUEUE_SIZE 10

int main( int argc, char *argv[] )
{
int s,b,l,fd,sa,bytes,on = 1;
char buf[BUF_SIZE];
struct sockaddr_in channel;

memset(&channel, 0, sizeof(channel));
channel.sin_family=AF_INET;
channel.sin_addr.s_addr=htonl(INADDR_ANY);
channel.sin_port=htons(SERVER_PORT);
s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(char*)&on, sizeof(on));
b=bind(s,(struct sockaddr*)&channel, sizeof(channel));
l=listen(s,QUEUE_SIZE);
while(1){
sa = accept(s,0,0);
read(sa,buf,BUF_SIZE);
fd=open(buf,O_RDONLY);
while(1){
bytes=read(fd,buf,BUF_SIZE);
if(bytes<=0) break;
write(sa,buf, bytes);
}
close(fd);
close(sa);
}
}
客户端代码:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#define SERVER_PORT 12345
#define BUF_SIZE 4096
int main( int argc, char **argv )
{
int c,s,bytes;
char buf[BUF_SIZE];
struct hostent *h;
struct sockaddr_in channel;
h=gethostbyname(argv[1]);
s=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
memset(&channel,0,sizeof(channel));
channel.sin_family=AF_INET;
memcpy(&channel.sin_addr.s_addr,h->h_addr,h->h_length);
channel.sin_port=htons(SERVER_PORT);
c = connect(s,(struct sockaddr*)&channel,sizeof(channel));
write(s,argv[2],strlen(argv[2])+1);
while(1){
bytes = read(s,buf,BUF_SIZE);
if(bytes<=0) exit(0);
write(1,buf,bytes);
}
}
在/usr/hello.c中包含字符串“hello world”,则运行界面如下(服务端和客户端在两个terminal中):



2. 传输协议的要素
1) 寻址
进程通过端口来标识程序。存在一个称为端口映射器的特殊进程。为了找到一个给定服务名字相对应的TSAP地址,用户需要与端口映射器(它总是在侦听一个知名的TSAP)建立一个链接;然后,用户通过该连接发送一条消息指定它想要的服务名字;端口映射器返回相应的TSAP地址。之后,用户释放它与端口映射器之间的连接,再与所需的服务建立一个新的连接。
当一个新的服务被创建时,它必须向端口映射器注册,把它的服务名字和TSAP告诉端口映射器。端口映射器将该信息记录到它的内部数据库中。
2) 建立连接
建立连接运用了异常复杂的技术。最易于理解的一部分莫过于“三次握手”。
3) 释放连接
下图为连接释放的四种情形:
a) 正常的三次握手 b) 最后一个ACK被丢失了,通过超时来释放连接 c) 响应丢失了(超时时候,重新发送DR并启动定时器) d) 响应和随后的DR都丢失(超时释放连接)

1. 运输层的逻辑通信
2. 多路复用与多路分解
假设我的电脑上运行两个telnet进程,一个FTP进程和一个HTTP进程。当我的电脑中的运输层从底层的网络层接收数据时,它需要将所接收到的数据定向到这四个进程中的一个。那么如何完成?
进程有一个或多个套接字(socket),它相当于从网络向进程传递数据和从进程向网络传递数据的门户。因此,接收主机中的运输层实际上并没有直接将数据交付给进程,而是通过一个中间的套接字来传递。由于在任何一个时刻接收主机上可能有多个套接字,所以每个套接字都有唯一的标识符。如下图所示:

所以,主机上的每个套接字被分配一个端口号,当报文段到达主机时,运输层检查报文段中的目的端口号,并将其定向到相应的套接字。然后报文段中的数据通过套接字进入其所连接的进程。

3. 无连接运输:UDP
UDP的优势
1) 应用层能更好的控制要发送的数据和发送时间。
2) 无需连接建立
3) 无连接状态: TCP需要在端系统中维护连接状态。此连接状态包括接收和发送缓存,拥塞控制参数,序号与确认号的参数。而UDP则不需要!
4) 分组首部开销小:TCP有20字节的开销,而UDP则为8个字节。
以下是流行的因特网应用及其采用的运输层协议:

而UDP报文段结构如下:



面向连接的运输:TCP


1. 概念解释


2. 实例分析

1. 可靠数据传输的原理
下图为可靠数据传输:服务模型与服务实现

可靠数据传输协议的下层协议也许是不可靠的。通过rdt_send()函数,可以调用数据传输协议的发送方。它要将发送的数据交付给接收方的上层(这里rdt标示可靠数据传输协议)。在接收方,当分组从信道的接收端抵达时,将调用rdt_rcv()。当rdt协议想向较高层交付数据时,通过调用deliver_data()完成。

完全可靠信道上的可靠数据传输:rdt1.0
最简单的情况是:底层信道是完全可靠的,则实现模型如下:

具有比特差错信道上的可靠数据传输:rdt2.0系列
假设底层信道模型中分组中的比特受损。那我们需要让接收方返回确认来确定成功发送数据并接收数据。这里用到了 自动重传请求(ARQ)协议,包含三点:1)差错检测2)接收方反馈3)重传。
实现模型如下:(NAK: negative acknowledgment,否定确认; ACK: positive acknowledgment, 肯定确认)

但是,如果NAK或者ACK也丢失了,怎么办?我们可以通过在数据分组中添加一新字段,让发送方对其数据分组编号,即将发送的数据分组的序号放在该字段上.
rdt2.1模型如下:


如果我们不发送NAK,而是发送一个对上次正确接收的分组的ACK,也能达到效果。接收方必须包含由一个ACK报文确认的分组序号。
rdt2.2模型如下:


具有比特差错的丢包信道上的可靠数据传输:rdt3.0
假设除了比特受损,底层信道还会丢包。这里得解决两个问题:如何检测丢包以及发生丢包后该做些什么。我们可以通过校验和,序号,ACK分组和重传等解决第二个问题,而通过 超时的概念来解决第一个问题。

下图为rdt3.0的运行,比特交替协议


流水线可靠数据传输服务协议
但存在一种普遍的情况,如果底层传输的时间永远高原运输层发送的时间(比如运输层发送时间为8us,而下层传输报文段需要30ms,而运输层为了收到ACK必须等待30ms,即使它忙碌的时间只有8us),那么我们就要通过流水线操作:即没收到ACK情况下继续发送报文段。流水线的技术对可靠数据传输协议带来如下影响:
1) 必须增加序号范围,因为每个传输的分组必须有一个唯一的序号,而且也许有多个在传输中的未确认的分组。
2) 协议的发送方和接收方也许必须缓存多个分组。发送方最低限度应当能缓冲那些已发送但没有确认的分组。
3) 所需序号范围和对缓冲的要求取决于数据传输协议处理丢失,损坏及过度延时分组的方式。
通过:回退N步和选择重传来解决流水线的差错恢复。
回退N步(GBN)

 上图中有8个分组已经成功发送,有6个分组未发送,头部是已经确认的6个分组,而尾部是不可用的7个分组。GBN的FSM如下:


但GBN有个非常大的问题是:如果分组为1000,而错了一个,则我们需要付出999的代价来修复1的错误。这里,就需要选择重传。
重传可以用一张图直接描述清楚:

这里注意到一点是:由于分组2的丢失,分组3,4,5均被缓存起来。当2重传成功后,分组2,3,4,5直接交付给上层。


2. TCP概述
TCP连接提供的是全双工服务,并且提供的是点对点的连接。TCP的缓存机制如下:

TCP将每块客户机数据加一个TCP首部,从而形成多个TCP报文段。这些报文段被下传给网络层,网络层将其分别封装在网络层IP数据包中。然后这些IP数据包被发送到网络中。所以,TCP连接的组成包括:一台主机上的缓存,变量和与一个进程连接的套接字,以及另一台主机上的一套缓存,变量和与一个进程连接的套接字。
TCP报文结构
1) 源端口号和目的端口号:用于多路复用/多路分解来自或送至上层应用的数据。
2) 32比特的序号字段和32比特的确认号字段:由于TCP把数据看作是字节流。所以假设数据流为500000字节组成,而MSS(最大报文段长)为1000,则第一个报文段的序号为0,第二个报文段的序号为1000,以此类推。主机A填充报文段的确认号是主机A期望从主机B收到的下一字节的序号。例如主机A已收到主机B编号为0~535的所有字节,现在需要536以后的字节流,则主机A向主机B发送的确认号字段中填上536.
3) 16比特的接收窗口:用于流量控制
4) 4比特的首部长度字段:TCP的首部长度
5) 6比特的标识字段: ACK比特用于确认字段中的值是有效的。RST,SYN和FIN比特用于连接建立和拆除。

TCP可靠数据传输
和前面提到的可靠数据传输原理一样,如下图:

第一个事件:从上层应用程序接收数据,将数据封装在一个报文段中,并该报文段交给IP。这里注意序号(NextSeqNum)的增加。
第二个事件:TCP通过重传引起超时的报文段来响应超时事件。然后TCP重启定时器。
第三个事件:TCP状态变量SendBase是最早未被确认的字节的序号(因此SendBase-1就是指接收方已正确按序接收到数据的最后一个字节的序号。)

流量控制
TCP连接的双方主机都为该连接设置了接收缓存。如果接收方读取数据过于缓慢,而发送方发送数据太多太快,则会造成缓存溢出。
这时候我们只要保证发送方未确认的数据量 < 接收方缓存的可用空间大小即可。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值