CS 144 计算机网络
UNIX 自带socket
int socket(domain, type, 0)
domain 值可取AF_INET、 AF_INET6 、 AF_LOCAL 。 AF_INET决定了要用 ipv4 地址 ( 32 位) 与端口号( 16 位)的组合 、 AF_LOCAL 决定了要用一个绝对路径名作为地址 。 TYPE决定了协议类型,SOCK_STREAM表示TCP,SOCK_DGRA表示UDP。成功返回一个文件描述符。getaddrinfo
可以通过主机名和服务名来获取ip和端口号,例如:
int getaddrinfo(const char restrict nodename, / host 或者IP地址 */
const char restrict servname, / 十进制端口号 或者常用服务名称如"ftp"、"http"等 */
const struct addrinfo restrict hints, / 获取信息要求设置 */
struct addrinfo *restrict res); / 获取信息结果 */
可以通过DNS把服务器名称转换成ip地址和端口(sockaddr类型),sockaddr作为属性保存在addrinfo结构体中。生成addrinfo时内部申请了堆内存,需要通过freeaddrinfo()释放掉。writev
将多块分散的内存数据一并写入文件描述符中。
ssize_t writev (int __fd, const struct iovec *__iovec, int __count)
其中iovec中每个元素为
struct iovec{void *iov_base; /* Pointer to data. */size_t iov_len; /* Length of data. */};
shutdown
shutdown 类似于文件描述符的close,不同的是shutdown只针对socket,并且可通过参数控制分别关闭读和写。sockepair
nt socketpair(int domain,int type,int protocol,int fd[2] ); 不同于管道可以在本地两个文件之间创建双工的socket
LAB 0
webget
拼接一个请求头std::stringstream stream; stream << "GET " << path; stream << " HTTP/1.1\r\n"; stream << "Host: "<< host << "\r\n\r\n";
http协议请求头每一行末尾需要加\r\n,最后一行需要两个\r\n。
byte_stream
测试框架将待测对象执行的每一个操作都被封装到不同的步骤对象中,步骤对象有针对当前步骤的预期输出值,如果不符合预期值则抛出特定异常对象。通过重载操作符可以将步骤对象转换为字符串输出。harness对象用来管理和执行所有的步骤对象,并持有唯一的待测对象。harness对象通过execute函数执行不同的步骤对象,并在内部用try_catch包裹并执行步骤对象的execute函数。用vector来实现环形缓冲区。因为缓冲区满的判断条件是next_pos(head)==tail,浪费了一个数组元素,所以实际vector的容量需要是初始化时提供的容量大小加1. end_input 是停止向缓冲区写入,相当于固定head。读到EOF()的判断标准是,已利用end_input()停止向缓冲区写入,并且缓冲区里的内容已近读取完毕。
LAB1
实现一个滑动窗口
需要缓存没有按照顺序输入的数据段,每个数据段有一个编号Index。
能够按照由0开始的编号排好,并且首尾相连的数据段,输入到LAB0中的byte_stream缓冲区中。如果因为缓冲区大小限制只能把数据的段的一部分输入到缓冲区,则把剩余的部分保留在乱序区。
允许输入的数据段有重叠。
输入的数据段如果带有eof标志并且编号为index长度为size,表示输入完毕后,输入到缓冲区的数据总量必须为index+size。
用红黑树来保存乱序的数据段,插入一个数据段时要进行合并操作,也就是删除红黑树中那些能够被当前插入数据段完全包含的节点。
用_unassembled_byte来记录乱序的数据量,如果_unassembled_byte==0并且已经接收了eof信号,则关闭byte_stream缓冲区。int start=index; int len=data.size(); int end=start+len; if(_tree.getRoot().get()!= nullptr){shared_ptr<RBTNode<int,string>> pre;shared_ptr<RBTNode<int,string>> cur=_tree.search(index);if(cur->_key==-1){pre=cur;cur=_tree.predecessor(cur);}while(cur.get()!= nullptr&&cur->_key!=-1){pre=cur;int ori_key=cur->_key;if(rel(cur,start,end)==INCLUDED){_buffer_size-=cur->_value->size();_tree._remove(cur);}else if(rel(cur,start,end)==CONTAIN){return THROW;}else{cur=_tree.successor(cur);break;}if(cur->_key<ori_key && cur->_key!=-1){continue;}else{cur=_tree.predecessor(cur);}}if(cur.get()== nullptr){cur=pre;}if(cur->_key==-1){cur=_tree.successor(cur);}while(cur.get()!= nullptr&&cur->_key!=-1){int ori_key=cur->_key;if(rel(cur,start,end)==INCLUDED){_buffer_size-=cur->_value->size();_tree._remove(cur);}else if(rel(cur,start,end)==CONTAIN){return THROW;}else{break;}if(cur->_key>ori_key){continue;}else{cur=_tree.successor(cur);}} } _tree.insert(index,make_shared<string>(data)); _buffer_size+=len; return ACCEPT;
首先在待插入位置的前继节点中寻找待删除节点。 要点:在调用_remove(cur)之后,cur的值可能变为-1(哨兵节点)或者其前继以及后继节点的值,对于第一种情况要求predecessor(cur)必须能接受以哨兵节点为参数。对于第二种情况,如果cur的值正好变为其前继节点的值,则不需要调用predecessor(cur)。predecessor只返回空节点或内部节点,不返回哨兵节点。在进入每个循环之前,必须尽力使cur为内部节点。如果cur变成了空节点,通过cur=pre进行回滚。
改进:以上只考虑了待插入节点和红黑树中节点的包含与被包含关系,如果考虑交叉关系,在遍历时,如果当前节点包含待插入节点,则用当前节点代替待插入节点,然后从树中删除当前节点;如果当前节点和待插入节点交叉,则用合并后的节点替换待插入节点。例如:树中节点为 A[1,2],B[5,6],待插入节点为C[3,4],遍历之后待插入节点变为C[1,6]。性能分析
clion中配置 profiler并运行程序
调用profiler之后会生成火焰图,y轴是调用栈,x轴是抽样中命中的次数。
所以如果火焰图顶端出现一个平台,那么这个平台对应的调用就是性能瓶颈。
通过火焰图发现,瓶颈在于从环形缓冲区中写入和读取,因为环形缓冲区每次只能操作一个字节。
随后把byte_stream类中的缓冲数据结构改为BufferList,它的好处是能把散落在内存中各处的小string组织成一个虚拟的大string,并且还可以通过remove_prefix(size_t n)函数来模拟字符串的截取操作最后的结果是滑动窗口每秒可以写入240mb, 主要的瓶颈在于随机数据生成函数,说明性能还可以进一步提高。
lab2
序列号
WrappingInt32 wrap(uint64 t n, WrappingInt32 isn)和
uint64 t unwrap(WrappingInt32 n, WrappingInt32 isn, uint64 t checkpoint)负责TCP消息头的序列号,和绝对序列号之间的转换。TCP中的序列号是32位的,也就是说一旦超过了32位无符号数能表示的最大值就要对2^32取模。序列号可以从任意值开始(ISN),而绝对序列号必须从0开始。两者都包含SYN和FIN.
在unwarp中,由于一个序列号可能对应多个可能的绝对序列号,所以还需要提供一个chekpoint,它是最近一次由序列号转换而来的绝对序列号,unwrap返回的结果必须和checkpoint最接近的那一个,不管是大于还是小于它。uint64_t unwrap(WrappingInt32 n, WrappingInt32 isn, uint64_t checkpoint) {WrappingInt32 wrap_check= wrap(checkpoint,isn);int32_t off=n-wrap_check;std::cout<<off<<std::endl;if(off<0){return checkpoint- (1ul << 32) + static_cast<uint32_t>(off);}else{return checkpoint+off;} }
转换的思路是先计算在序列号空间下checkpoint和序列号n的差off,然后再加到chekoint上。
虽然n和wrap_check都是无符号32位,但是它们相减得到的off相当于两个有符号数相减然后对2^32取模,所以可以直接加到checkpoint上。如果off的首位为1(有符号数下为负),说明其大于等于2 ^31, 为了更接近checkpoint,最终的值应该是checkpoint应该减去(2 ^32-off)。 坑点: 还需要考虑使用减法时会不会得到一个负数,如果是的话就转而使用加法。TCPreciever
滑动窗口的下界是最后一个可以按序排好的序列号的下一个序列号(ackno),窗口的上界是第一个不能接收的序列号。窗口大小就是上界减去下界。_reassembler中的head_index就是下界,其初始值为0.
TCP头中和数字有关的属性都使用大端编码。接收数据
图中1-3行是伪首部,并不存在于真正的ip或TCP报文中。parse类用来解析ip数据报中除去ip头以外的数据部分,数据保存在一个Buffer类中,它的好处是可以记录读取的位置,类似于文件指针。tcp_segment类利用parser类来读取包含在ip数据报中TCP报文。在解析tcp头和读取数据前需要检验checksum。
在发送端,首先把tcp头部的checksum字段置零,然后把伪首部,tcp首部,数据部分划分成16进制数,求和并保存在一个32位数值中。接着,把高16位加到低16位上,不断重复直到结果为16位,最后的结果取反。把最后的结果保存在待发送TCP报文的checksum字段中。接收端按照接收的tcp计算checksum,如果为0(0xffff取反)说明报文正确。
伪首部是为了增加TCP校验和的检错能力:如检查TCP报文是否收错了(目的IP地址)、传输层协议是否选对了(传输层协议号)等,需要调用tcp_segment者提供。
checksum和CRC区别: checksum是ip协议用来校验其头部,TCP协议用来校验头部和数据的。而CRC是数据链路层用来对帧的其他部分提供循环冗余校验。伪首部需要ip层传给tcp层。TCP数据报占用的序列号等于其数据部分大小加上SYN标识或FIN标识占用的一个序列号。
坑点:_reassembler中的index应该是绝对序列号减1,但是这样就有可能使得index为负数,要通过判断避免这种情况。SYN和FIN只能接收一次。window_size和byte_stream的剩余容量有关。在计算ackno时,不仅需要考虑syn还要考虑FIN。
lab3
lab3的任务是实现TCPsender。 TCPsender的作用是从输入bytestream中组装segment然后压入队列segment_out中。TCP会根据接收到ackno和window_size发送新的segement,释放待重发的segment,改变window大小。
测试时,用另一个队列接收segment_out中的segment,用来模拟发送过程。之后测试框架通过调用ack_received,告诉TCPsender新的ackno和window_size。 segment中的数据储存在Buffer,而Buffer内部用智能指针指向真正的数据,所以segment可以随意的复制和删除。
三次握手
客户端A先发送syn+seqno, 服务端回复ack和syn+seqno,最后客户端返回ack。超时重传
思路:使用一个LRU队列,队列中保存的是待重传segment的absolute ackno(最后一个字节+1),所以需要一个额外的map记录ackno和segment的对应关系。
重传时间可以根据采样RTT来设置,但是会产生多义性:
所以tcp协议采取一个简单的形式:一旦发生重传,就加倍重传时间。坑点: 如果ack_received返回false(ackno超出发送的序列号)时,调用者会使用end_empty_segment()。fill_window在发送过fin报文后,就不再产生新的报文了。RTO和重传次数最多的报文有关。一旦接受到新的ackno后,就把RTO置零并且把所用的重传次数置零,并重新开始计时。每一次调用tick()当函数只重发一个segment, 并让所有的segment重新开始计时,不需要把重发的segment重新加入队尾。收到FIN的ack报文后,TCPsender中应该没有需要重传的报文。FIN占用一个序列号,当不超过windowsize时,可以将fin和数据一起发送。
lab4
一个TCPconnection 由TCPsender和reciever组成。 外界只能同过两个函数改变TCPconnection的状态,分别是segment_recieved和write以及read,前者负责接收网络层传输过来的tcpsegment,write负责将bytestream中的数据打包成tcpsegment,read负责读取接收到的数据。
同时打开
SYN_RCVD状态:发送syn并受到了对方的syn,只需要接收ack就能进入连接建立状态。
SYN_SENT: 发送syn后没有接收到任何报文的状态。
同时打开是指,两端同时向对方发syn,两端同时进入SYN_RCVD状态。四次挥手
和开始连接时的三次握手不同,关闭连接主动给对方发送FIN后(FIN_WAIT1),对方不能同时返回FIN和ACK,一般是先返回ACK,此时进入(FIN_WAIT2),然后经过一段时间之后再返回FIN。所以是四次挥手同时关闭
两端同时给对方发送FIN,双方同时进入closing状态,然后都只要等待对方返回ack旧可以真正进入closed状态。被动关闭
接收到对方发来的FIN后进入close_waiting状态,随后结束bytestream的输入,然后把其中剩余的数据发送出去,并发送FIN(last_ack)。等到收到最后一个ack后就可以真正进入closed状态。TimeWait状态
发送完最后一个ack之后,不能马上进入closed状态,还需要在timewait状态等待一段时间(2msl),因为对方可能会因为没收到ack重发fin,如果此时已经处在closed状态,只能回应复位报文段。另外,等待两个报文段最大生存时间,可以确保连接再次建立时,不会收到上一次连接的数据。TCP半打开连接
当对方因为断电失去了状态信息,当前端检测到后,需要向对方发送RST报文。带外数据
带外数据只有一个字节,当向TCPsender的bytestream中写入一个带外数据后,所有待发送的tcp头中的URG字段都设置为1,并且紧急指针是一个正的偏移量,它和序号字段的值相加表示最后一个紧急数据的下一字节的序号。
接收端在发现URG标志后,根据紧急指针读取带外数据。拥塞控制
发送端实际的发送窗口由是拥塞窗口和接收窗口的较小值。拥塞窗口慢启动和拥塞避免:
慢启动时拥塞窗口指数增长, CWND+=min (N, SMSS)其中N是ACK新确认的字节数。
当到达慢启动门限时,进入拥塞避免阶段,每过一个rtt,窗口+1SMSS。拥塞控制:
分为两种情况,第一是当TCP重传定时器溢出,即传输超时时,使拥塞窗口重新等于一个SMSS,ssthresh=max(FlightSize/2,2*SMSS),其中flightSIZE是定时器中字节数。
二种情况是收到重复的ack,说明此时网络并没有出现拥塞,发送端应该执行快速重传和快速恢复。快速重传要求接收端在收到失序报文后立马返回重复的ACK,当发送端收到三个重复ACK后立马重传该条报文,不管其定时器的计时。 然后重新计算门限,令拥塞窗口等于门限。状态机实现
把TCPconnection的状态分解为sender和reciever的状态。string TCPState::state_summary(const TCPReceiver &receiver) {if (receiver.stream_out().error()) {return TCPReceiverStateSummary::ERROR;} else if (not receiver.ackno().has_value()) {return TCPReceiverStateSummary::LISTEN;} else if (receiver.stream_out().input_ended()) {return TCPReceiverStateSummary::FIN_RECV;} else {return TCPReceiverStateSummary::SYN_RECV;} }string TCPState::state_summary(const TCPSender &sender) {if (sender.stream_in().error()) {return TCPSenderStateSummary::ERROR;} else if (sender.next_seqno_absolute() == 0) {return TCPSenderStateSummary::CLOSED;} else if (sender.next_seqno_absolute() == sender.bytes_in_flight()) {return TCPSenderStateSummary::SYN_SENT;} else if (not sender.stream_in().eof()) {return TCPSenderStateSummary::SYN_ACKED;} else if (sender.next_seqno_absolute() < sender.stream_in().bytes_written() + 2) {return TCPSenderStateSummary::SYN_ACKED;} else if (sender.bytes_in_flight()) {return TCPSenderStateSummary::FIN_SENT;} else {return TCPSenderStateSummary::FIN_ACKED;} }
例如连接建立的状态可以分解为reciever的SYN_RECV和sender的SYN_ACKED
ACK
每次当reciever成功接收一个segment后,sender会组装出新的待发送的segment, 由TCPconnection负责填充新segment中的ack和window_size。 当接收的segment包含数据时,必须回复ack. 当segment为空ack(rst位不为1)时,只有当reciever接收失败(不在窗口内)或sender状态更新失败(ack超出已发送的序列号)时,才发送空ack。只有收到对方的syn后,才能用接下来收到的ack和windowsize更新sender的状态。
由TCPconnection 根据reciever的状态填充sender中待发送segment的ack和window_sizeFIN
主动关闭:先关闭sender的bytestream,当bytestream的剩余容量被用完后,sender会自动发送fin报文,并等待FIN的ACK, 其能清空重发队列。reciever收到对方的FIN后,等到乱序的数据量为0就关闭reciever的bytestream,并回复最后一个ACK。
被动关闭:reciever收到对方的FIN后,关闭sender的bytestream,等到乱序的数据量为0就关闭reciever的bytestream,回复最后一个ACK. 当sender的bytestream的剩余容量被用完后,sender会自动发送fin报文,并等待FIN的ACK。
time_wait状态可以分解为receiver的FIN_RECV和sender的FIN_ACKED;
进入TIME_WAIT状态后,local端还可以接受segment和发送重复的ack,当接受到最后一个segment并且经过10个初始重传时间后,conncection真正关闭。四次握手的原因,在建立连接的时候ack和syn可以一起发,但是关闭连接时,本地发送fin后,对方只能先发ack再发fin。
RST
RST报文不需要被回复。
RST的作用是使两端的reciever和sender的bytestream都进入错误状态。
主动RST: 1) 当sender的重发次数大于阈值2)当TCPconnection销毁时,连接还处在活跃状态(active)。由TCPconncection负责填充RST。3)reciver收到SYN, sender发送SYN之前收到ack。 4)sender发送syn后,reciever收到错误的ack,并且
需要按照对方发来的ackno,设置rst报文的seqno。
被动RST: 所有的RST必须在sender发送syn后才能成功接收,除此之外,在reciever接收syn之前,RST报文必须满足ackno正确,在reciver接收syn之后,RST报文必须满足seqno正确。坑点
string_view和string的转换,假设a是string_view,b是string。不能使用a=b.data()。因为b.data()返回的是char指针,它默认的结束位置是第一个‘\0’的位置,而b中可能包含多个’\0’测试
NAT
nat是一个网关,把内网的地址转成外网的地址
比如内网A要给外网的服务器发ip报文,首先发送给NAT网关,然后网关会把源地址改为网关地址。服务器返回报文时要把目的地址改回内网地址。 因为涉及地址转换所以内部需要维护一个转换表。如果多个请求的目的地址不同,直接用目的地址和端口作为键值。如果目的地址和端口相同,则需要进行端口映射:
dhcp
刚加入子网的主机没有ip地址,它只能通过广播通知dhcp服务器为其保留一个ip地址,然后dhcp会广播这个ip地址,新主机收到后将其作为自己的ip。sock5
sock5服务在本地监听1080端口,浏览器访问外网前先和sock5服务建立连接,然后用tcp的数据段告诉sock5服务要访问的目的地址和端口,然后等待sock5返回确认信息后就可以利用当前的连接正常的和外网通信了。注意一个端口可以监听多个请求。如果需要某个终端的所有tcp都走代理可以通过export http_proxy=http://proxyAddress:port
虚拟机
虚拟机想利用主机的sock5代理的话,直接在设置里填写主机的ip和TCP和HTTP代理端口即可。注意sock5只能代理tcp和http,而ping指令使用的是icmp协议,所以ping 外网是不会成功的。UDPsocket
继承链:UDPsocket->Socket->FileDescriptor。FileDescriptor用智能指针指向FDwrapper的实例,用来保存真正的fd。FileDescriptor还负责记录读和写的次数和设置fd的阻塞和非阻塞状态。Socket类负责接收domain 和type参数进行socket系统调用,并把返回的文件描述符保存在FileDescriptor中。
TCP和UDP的最大报文长度: TCP的最大报文长度可以通过TCP头中的MSS字段协商(可选)。而UDP的最大报文段等于IP协议的最大报文段2^32-1。但是超过MTU的数据报都要分片传输,所以TCP和UDP的最大长度一般都设为MTU减去头部长度。 在UDP中send和recv都把数据当做一个完整地数据报进行发送和接收。
UDP和TCP的区别:UDP中并没有连接的 概念,也就是说从UDPsocket(文件描述符)中读取的数据报可能来自不同的IP地址,需要应用层自己进行区分。 UDP和TCP一样都包含端口号,所以在使用前都需要绑定一个端口
TCPoverUDP
上图是一个TCP客户端或服务端进程运行时所需要的所有文件与缓冲区的示意图。 主进程的主循环中通过poll轮询out,input和this三个文件,其中input和output是输入输出文件,this是tcp_sponge_socket用来和外部交互的文件,inbound和outbound是缓冲区。
子线程的主循环中轮询thread_data,data_adapter两个文件。 对图中的连线来说,如果开头的文件或缓冲区在eof或error状态,会使得连线末端的文件或缓冲区调用end_input,shutdown或close。 this和thread_data是一对本地socket, 在this上调用shutdown,会使得thread_data出现eof状态,反之亦然。由datagram_adapter负责接收UDP或IP层的数据报,如果数据报的源地址符合连接的目的地址就解析成TCPsegent传递给TCPconnection, 否则就丢弃数据报。在连接建立的时候会设置datagram_adapter的目的地址。
坑点: 在对sender调用end_input时,应该确保连接已建立。 在发送或接收时,如果对方或自己的窗口的大小为0,应该将其实际大小看成是1。 假设客户端的输入文件为空,那么客户端的TCP很快就会触发主动关闭,使得服务端进入被动关闭,服务端的sender也随之被end_input,如果end_input后不能写入新数据将导致服务端大量数据发送不出去,所以TCP 缓冲区被设计成在end_input后还能写入新的数据。但是这样做的弊端是会导致服务端进入LAST_ACK状态后仍然有数据待发送,而这时客户端可能已经关闭了。 接收到对方的fin报文后,一定要确保在本次调用过程中对_sender进行end_input, 并且检查其eof()状态。
结果
CPU-limited throughput : 0.45 Gbit/s CPU-limited throughput with reordering: 0.41 Gbit/sProcess finished with exit code 0
坑点
因为连接互联网时,借用的是tun网卡来运行ip协议,所以要确保tun网卡正常运行。
lab 5
ARP
发送者 ip层的datagram传递给数据链路层之后,如果链路层发现找不到下一跳ip对应的mac地址,则使用ARP协议给所在网络内的所有机器发送arp请求,请求中目的mac地址是空的。如果网络内有机器符合下一跳ip(ARP请求中的目的ip)就填充请求中的目的mac,并交换两个目的端地址和发送端地址,把arp应答返回给发送者。注意对应同一个ip地址的arp请求5秒内只发送一次。
使用arp协议时应该设置以太网帧的类型字段,请求时物理地址应该设置成ff:ff:ff:ff:ff
待发送的ip数据报中的源ip和目的ip不一定对应以太网帧中的源mac和目的mac, 并且也不一定对应arp协议请求中的源ip和目的ip。只要接收到ip类型的太网帧,并且帧的目的mac和本机mac地址相同,就把帧传给ip层。arp在转发ip报文时,需要额外地指定下一跳ip地址。实现
将所有不知道目的mac地址的以太网帧按照下一跳ip地址分类存储,这样做的好处是当ARP回复到达时,可以一次性将某一类中所有以太网帧都发送出去。具体来说,就是维护一个ip地址到以太网帧的队列的map数据结构。TCPoverEthernet
当TCP建立在以太网帧之上时,接收到一个以太网帧之后,首先用NetworkInterface判断,如果这不是一个ARP请求并且目的mac地址和本机的mac地址一致的话就解析成一个ip数据报。 之后再利用unwrap_tcp_in_ip解析该ip数据报,如果本机不是Listen状态(已经通过TCP建立了连接), 则需要该ip数据报的目的ip地址以及源ip地址和本连接的源ip以及目的ip相对应, 当解析成tcp数据报后,还要求端口号相对应(直接在unwrap_tcp_in_ip中检验)。
如果本机在listen状态,则需要该ip数据报解析成TCP数据报后,syn字段被置1,然后根据该TCP数据报重新设置源ip(??),目的ip以及源端口,目的端口。如果ip数据报不满足上述条件,就会被直接忽略。
lab 6
路由表的作用是为ip协议计算下一跳ip地址,利用表中的目的地址和子网掩码和ip数据报中的目的地址进行匹配,匹配成功后gateway字段对应的就是下一跳ip地址,星号代表可以直接发送给ip数据报中的目的地址,不需要路由中转。defaut表示默认路项。
- 路由算法
在传统的rip算法中,路由器每隔30秒通过udp协议向邻居节点发送rip报文,里面包含了自身的路由表。 如果180秒内没有收到邻居节点的rip报文,则认为该邻居是不可达的。 RIP 用于自治系统(AS)内部的选路,而AS之间的选路利用BGP协议。
广播broadcast 和 多播 multicast
广播选路算法首先定义一个中心节点,然后其它节点向其单播一个加入最小生成树的报文,直到这个报文到达一个树节点。加入树报文经过的路径定义了一个新的分支。当一个源节点要发送一个广播分组时,它向所有属于该生成树的特定链路发送分组。
在多播协议中,首相向(Internet Assigned Number Authority,IANA)申请一个组播地址, 然后主机通过IGMP协议告知其下一跳路由要加入这个多播组,然后和广播路由选路算法一样,所有多播组内的路由器向中心节点发送加入报文。实现routing table
每一个router都会保存多个interface实例,每一个interface对应不同的传输层,每个传输层都用自己的ARP协议来发现某个ip是否可达。
用一个元组保存table中每一条记录的匹配前缀,前缀长度(32位中的多少位),下一跳ip(转换成u_int32), 以及将要使用的interface实例的序号。
当从任意一个interface中接收到一条ip数据报后,通过匹配routing table 来判断下一跳地址,以及应该使用的interface。
如果匹配到的的记录中没有下一跳地址,说明可以直接把下一跳地址设置为ip数据报中的目的地址。
使用u_int_32形式的ip地址和移位操作进行匹配。
每经过一跳,ip数据报中的ttl都应该减1,当ttl为0时应该丢弃该数据报。
坑点: 一个32位的无符号数右移32位保持不变,应该先移动31位再移剩下的一位。
CS 144 计算机网络相关推荐
- Stanford CS 144 Note 17 - TCP Setup and teardown
TCP建立连接 3-way handshake 一边是active opener,一边是passive opener Client:Syn/Sa Server:Syn/Sp + Ack/Sa + 1 ...
- Stanford CS 144 Note 20 - Playback Buffer
什么是playback buffer 视频预加载的部分 Playback buffers Horizontal distance:amount of delay Vertical distance:S ...
- 小齐是如何提高自己的「编程能力」的?
上周的投票,大家最关心的还是提高技术,那我们今天就来聊聊这个话题. 之前主要分享的多是算法和数据结构方面(年后继续),因为我认为算法和数据结构是程序员最最最重要的内功,但是并不是唯一. 我自己作为一个 ...
- 计算机网络自顶向下方法知识点整理(部分)
1-2物理媒体 1.双绞铜线 由两根绝缘的铜线组成,以规则的螺旋状排列. 无屏蔽双绞线(UTP)10Mbps~10Gbps 最终作为高速LAN联网的主导性解决方案也常用于住宅因特网接入 2.同轴电缆 ...
- cs python课程 加州大学_加州大学伯克利分校是如何培养计算机学生的
Photo by Anthony Hall / Unsplash 加州大学伯克利分校电子工程和计算机科学系(EECS)是世界知名的院系,计算机领域在2020 USNews排名第一.EECS的使命是教育 ...
- GitHub 上这个「计算机科学」自学指南火了!
公众号关注 "GitHubPorn" 设为 "星标",每天带你逛 GitHub! 作者:Ozan Onay.Myles Byrne 译者:Keith Null ...
- 自学计算机科学,你需要这份指南
相信经常看我的Github排行榜的朋友,最近会发现有一个项目经常上热门,TeachYourselfCS-CN,看名字就知道,这是一个教你自学计算机的项目,原作者为Ozan Onay和Myles Byr ...
- 国内高校计算机教育存在哪些问题?
最近我在逛知乎的时候看到一个问题: 随手写了个回答,没想到很多读者都表示说出了心声,所以也同步发到公众号. 以下是原回答 我自己是 CS 科班的,读者里也有很多各大高校计算机的同学,覆盖了上交.北邮. ...
- Stanford University courses of computer science department(斯坦福计算机系课程设置)
斯坦福学科目前分为7个department:Business, Earth, Education, Engineering, Humanities & Sciences, Law, Medic ...
最新文章
- 2014公积金新政策对你买房有什么好处?
- go语言游戏编程-Ebiten使用矩阵实现对图的缩放和移动
- P1311 选择客栈
- vscode安装和使用
- 前端学习(1313):get请求参数
- springboot整合rocketmq_面试官:简单说一下RocketMQ整合SpringBoot吧
- 基于QItemDelegate的例子2 trackeEditorDelegate
- [golang]如何看懂调用堆栈
- paddlepaddle的使用
- [SHELL进阶] (转)最牛B的 Linux Shell 命令 (三)
- 【转载】实用的人际关系经验
- 混合选择集的坐标提起lisp_晓东CAD家园-论坛-A/VLISP-[LISP程序]:请教如何对选择集进行排序-我有(setq ss(ssget _w p0 p1 (list (0 . CIRC...
- java映射的概念_Java之路:映射(Map)
- 【STC单片机】STC15串口收发示例程序模板
- 计算机相关的著名的期刊和会议
- Python中字典(dict)和集合(set)区别与联系
- 增量型编码器与绝对值编码器
- 双向链表的插入和删除
- Android JetPack架构篇,一个实战项目带你学懂JetPack
- ffmpeg加水印、logo等