Linux网络编程笔记

概述

  1. TCP/IP 协议体系是分层的。在使用的时候需要经过 封装 和 分用 的过程。
  2. ARP协议提供任意的 网络地址 到物理地址之间的映射。但是其提供的都是可以直接访问到的,直接连接的网络地址对应的物理地址。那是因为在数据链路层,传输的范围只是仅仅限于直接连接的设备之间。在经过网络层的处理之后,我们在数据链路层需要的ip地址是可以一步访问到的ip,因此ip对应的物理地址可以由arp协议来获得。当需要使用的ip没有对应的物理地址,就启动arp协议来找。arp的协议格式为 2 2 1 1 2(操作,也就是类型,比如arp请求,arp应答) 6 4 6 4的结构
  3. 以太网帧只需要物理地址从其结构上就可以看出 6(目的物理地址) 6(源物理地址) 2 数据部分 4(crc校验)
  4. DNS和ARP:ARP工作在数据链路层,借助数据链路层的格式和ARP模块的功能,来为数据链路层的帧提供需要的目的物理地址。其工作在体系的底层,而且目光很短(只是相连的主机)。DNS则是一种应用程序。工作于应用层,主要的作用就是根据域名来查找相应的ip地址,方便网络层使用。当然,其也要依赖于下面的层次协议。
  5. DNS的linux使用:(1)隐式调用,比如我们在浏览器输入一个域名,首先需要解析为ip地址,那么隐式调用DNS服务。其会将查询到的进行缓存。DNS域名服务器的ip在/etc/resolv.conf 文件内 (2)显式调用:host命令来进行查询 域名 对应的ip
  6. socket和TCP/IP协议族的关系
    • socket可以提供了一组api,其可以实现 控制内核空间 中的网络通信数据结构,或者实现用户空间和内核空间的交互。

ip协议

  1. ip协议是一个 不可靠,无连接(不和对方建立连接和断开连接,而是每次都重新使用。比如 打电话 和 对讲机 的区别),无状态(两次连接之间没有关系) 的协议。
  2. ipv4 的头部 (单位 bit) 4(ip协议版本) 4(头部长度,单位为4B) 8(服务类型TOS,有效的只有4位。表示我们偏向于ip协议实现低延时还是大吞吐量) 16(总长度,实际由于MTU限制,远远不到最大值65535)
    16(标识,代表一个逻辑上的ip数据包id) 3(标志字段 保留 df mf) 13 (分片偏移,实际是x8) 8(TTL 一般64) 8 (上层协议) 16(头部校验和) 32(源ip) 32(目的ip) <=40B 选项
  3. 在实际的传输中,因为MTU为1500.也就是整个IP数据报(datagram)不可以超过1500.也就是数据部分最大为1480B。当IP datagram总体超过1500B之后,进行截断,调整ip头结构(设置MF为1)。然后将剩下的部分进行再次包装,也要调整头结构。
  4. 除了ip协议的头部需要注意,更重要的是ip的路由能力。也就是在ip输入队列中不是发给自己的(非自己的ip并且不是广播ip)的时候,怎么去完成数据转发任务。(主要任务是计算下一跳路由,其计算方法主要是根据路由表来给出的。而路由表又可以由rip bgp route 而更新)
  5. route命令可以查看路由表。路由表的destination 表示目标(网络或者主机。其实并不重要,因为判定规则就是 ip报文段中的 目标地址 & 路由表项的 Genmask 字段的值,然后和destination比较。如果相同,说明匹配上了。如果不相同,说明没有匹配上),Gateway字段表示下一跳的地址(* 或者全0代表本网络,直接发即可)。Genmask前面说过了。Flag用来说明一些这个项的性质。
  6. Gateway表示网关,网关顾名思义就是“网络的关口”,也就是像是本网络的一个大门,可以通向另外一个网络。而路由表中的下一跳地址,要么是本网络的,要么是其他网络的。想要转发到其他网络,那么就需要借助于Gateway。因此对于本网络的,Gateway为*,表示不需要借助网关。而Gateway可能由多个呀,因此,除了设置一个Default Gateway,还需要在路由项中设置一个Gateway字段来设置其值。
  7. 可以利用icmp协议来更新路由表。其直接告诉主机,对于原来的某一个ip数据报,其应该使用的ip地址是什么。不过一般,主机只允许接收icmp重定向报文。而路由器只允许发送icmp报文。

tcp

  1. tcp的面向字节流 的一个明显特征就是 发送方的write 操作次数和 接受方的 read操作次数没有明显的关系。不像udp,一个写一定对应一个读。
  2. tcp首部 (bit) 16 16 32 32 4(头部长度) 6(保留) 6 (标志为) 16(win) 16(校验和) 16(紧急指针) 选项(<=40B)
  3. tcp的首部选项部分按照 类别kind 长度length infor 的结构。
    kind = 2时,表示协商 MSS(Max Segment Size)的大小。也就是tcp可以承载的数据部分的最大长度。其一般时根据MTU而设置的,为MTU-40(ip和tcp的固定头部)。目的就是防止在本机发生ip分片。这样,ip数据包 就不会因为总体超过MTU而必须分片。
    kind = 3时表示窗口扩大因子。因为之前的tcp的头部有win字段,但是最大为65535.为了提高吞吐量,我们要更大。所以使用这个选项来扩大。为几就代表要向左移多少位。范围:0-14。
    kind = 4表示选择性确认 选项。只是在连接初始化的时候,表示是否支持这种SACK技术
    kind = 5是SACK的实际工作内容。包括两个32bit的数字。表示一个区间。这个区间的字节是没有收到的。而且是单独缺失的。
    kind = 8是表示时间戳,可以方便计算RTT
  4. tcp的建立连接和断开连接的过程(三次握手和四次挥手)。第一次握手为SYN也就,也就是同步报文,没有ACK。seq为ISN(initial sequence number),没有ack。第二次握手也为SYN,有ACK。seq为ISN,ack为第一次握手的seq+1.第三次握手为ACK,没有seq(为0),有ack,为1.表示希望接收的对方的 相对的位置 的数据。
    四次握手的时候,第一次握手,为FIN和ACK(后面每一个都有,因此不再说了) 其即使长度为0,但是从对面的表现来看,其占用了一个标号。因为对面会返回ack为 第一次握手报文的seq+1.对面返回一个报文,其为普通的应答报文(没有FIN),其ack 为第一次握手报文的seq+1。长度也为0. 之后,其返回一个FIN报文,情况和第一次挥手情况一样。之后是再返回一个确认。
  5. seq主要是为了发送用的。seq + length就是我们发送的内容在对面的缓存区的存储位置。有了这个就可以实现全双工通信。ack是为了可靠传输用的。接收到了ack,其说明我们上次的发送对方是不是收到了。当没有收到的时候,我们可以采用一些措施来保证可靠传输。
  6. 如果第一步的握手,服务器没有响应。系统会自动重连5次,加上第一次共6次。时间间隔分别为 1s 2s 4s 8s 16s (再等32s)因此,共64s的时间。如果没有回应就返回。这可以通过内核变量调整。
  7. tcp的状态转移。先发FIN的一方进入 主动关闭 状态转移子图。这个图的特点就是进入FIN_WAIT_1之后,可能收到对面的ACK,然后进入FIN_WAIT_2阶段。然后进入TIME_WAIT阶段。但是也可能收到对面的FIN+ACK。说明对面接收到我们的FIN之后,也没啥要发的。于是除了一个ACK,还发了FIN。那么就直接进入TIME_WAIT状态。如果收到 FIN,没有ACK。说明对面还没收到我们的FIN,但是也想结束连接。我们发送ACK,然后进入CLOSING状态,等待对面的ACK。收到ACK之后,进入TIME_WAIT阶段。
    如果是被动关闭的一方,那么会比较简单,进入CLOSE_WAIT之后,进入LAST_ACK,然后收到ACK之后直接结束。
    当端口不可用的时候(端口不存在或者被占用),会发送RST报文。此外,当一端想要异常终止连接的时候也会发送。当一方因为网络故障失去了其连接信息,另外一方直接向其发送信息的时候,但是并不认识它。此时发送RST。
  8. 之所以设置TIME_WAIT 有两个原因:1 可靠地关闭连接 2 防止在这个端口建立的新的连接收到污染(传来上一次的连接的信息)。TIME_WAIT时间为 2 x MSL(大约2分钟)
    这个时间对于客户端程序来说(往往进入TIME_WAIT状态),如果使用相同的端口来连接服务器,则会出错。导致不能重启。但是客户端一般是临时分配端口的。可以立即重启。如果对于服务器进去TIME_WAIT状态,并且需要重启的话,那就很麻烦了。则需要借助socket选项。
  9. tcp交互数据流和成块数据流。两者意味着交互方式的不一样。前者是很少的内容就作为一个包来进行发送。一来一回的交互方式。并且客户端对于服务器的ack往往不带任何内容。因为处理速度往往比较慢。而服务器对于客户端的ack往往携带着一些要发送的数据。叫做延迟确认。 一轮交互往往是 客户端发送–>服务器ack+发送 -->客户端只ack(仅仅用来ack的数据包的特征就是长度为0,seq其实并不重要了。)并且往往加P,让应用程序赶快接收。
    而成块数据流 往往是一者发送多次,多个大数据包(发送几次要取决于接受方的接收窗口值)。接收方可以一次对多个发送的包来进行 确认。
  10. tcp传输带外数据????
  11. tcp的超时重传:tcp会总共进行5次超时重传,分别间隔了0.2 0.4 0.8 1.6 3.2s 如果均失败,那么其会交给arp协议来进行处理,等待手动退出。其会在发送一个数据之后设置一个计时器。如果超过这个计数器还没有得到确认,就再次重传。
  12. 拥塞避免。我们前面提到成块数据流,其可以在没有收到确认报文的时候,继续发报文,着就是用到了一个概念:SWND(send window)。也就是当前可以还可以发多少个的空间。这个值其实取决于 接收端 的接收能力。也就是在对方告诉的自己的win值。但是也不仅仅是这样的。我们还可以通过控制这个值(因为其决定了发送方的发送能力),来完成拥塞避免(当swnd太大,会导致网络延迟大,也可能拥塞。当swnd太小,会导致网络拥堵)SWND = min(CWND, RWND)。CWND表示拥塞窗口,反应了当前网络情况允许发送的窗口值。RWND为接收方发来的。

CWND一开始只有2-3个SMSS(sender max segment size一般等于mss)大小。然后是慢启动:收到前面几个报文的回复,就增加几。因此,成 指数型增长。到达慢启动门限的时候,开始进行拥塞避免算法。按照CWND += SMSS*SMSS/CWND 可见一次增加的不到一个SMSS。并且随着CWND的变大,增加的会越来越少(注意,实际的单位是B,不是SMSS)。

当发生网络拥堵:tcp回复超时 或者 收到两个ack。对于第一种情况。我们重传,并将CWND设置为< SMSS, ssthread 也调小。重新进入慢启动阶段. 对于第二种情况,启动 快速重传 和 快速恢复 的算法。快速重传和快速恢复会立即重传丢失的报文段,并重新设置ssthread,设置CWND为ssthread+3SMSS,会进入拥塞避免阶段。

访问Web服务器

  1. 代理服务器分为正向代理(和客户端在一个逻辑网络内,代表客户端去请求,去做事情) 和 反向代理(代表 服务器,和服务器在一个逻辑网络内。接收网络请求,将其转发给服务器,然后将结果进行返回)和 透明代理(设置在网关上,和正向代理类似)
  2. DNS服务是在不知道其域名对应的ip地址的时候进行的。/etc/hosts 文件中为对应的映射。会首先在这里找,如果找不到的话,就启动DNS服务。DNS借助于UDP进行查询,DNS服务器在/etc/resolv 文件中。包装为UDP之后,我们填ip。填完ip之后,查看路由表来找下一跳ip。如果没有ip对应的MAC,启动ARP协议。
  3. HTTP请求部分的头部字段部分可以有好几行,一行代表一个字段。第一行是请求行。HEAD,GET,OPTIONS(对某个url支持哪些方法),TRACE(将请求头包含进去,用于研究代理服务器对其影响)为安全方法,也就是不改变服务器内容的方法。 POST,PUT, DELETE,PATCH则会影响服务器资源。此外,头部字段还可以有User-Agent Host(请求的主机,必须要有,必须要和服务器的主机符合) Connection(长连接:keep alive 还是短连接 close)。
  4. HTTP请求部分 除了 头部字段 部分,还可能有可选的 消息体 部分。如果有的话,在头部字段中 应该有 Content-Length字段来表示消息体长度。此外,头部字段结束,用一个空行来表示,也就是一个 回车 和 换行。
  5. HTTP应答 也是有头部字段的。其第一行为状态行。为http版本信息,状态码,状态信息。除此之外,还有Server字段,Content-length(消息体的长度,这个一帮都会有,因为我们请求的就是消息体中的内容)Content-type Set-Cookie(设置Cookie,其分为Cookie部分和设置部分。Cookie部分类似变量赋值,中间用:分割。设置部分为设置此Cookie的expires,path,domain等信息)。在下一次发送Cookie的时候,就发送存起来的Cookie,客户端可以识别用户。自动登录就是这样的原理。

基础API

  1. socaddr结构体为通用socket地址结构。里面的sa_family_t 是一种 地址族类型。地址族和协议族是对应的。比如AF_INIT 和 PF_INIT 对应,两者的宏对应的值都一样,可以混用。此外,还定义了一些专用的socket结构 地址族又可以对应于地址的长度。
  2. IP地址转换函数,网络字节序和主机字节序转换函数
  3. soket的命名:使用bind来命名。也就是绑定本地的地址和端口号,来组成socket中的local 部分。对于客户端来说,不必bind,因为在connect时会自动分配local部分。对于客户端来说,监听socket一定要bind,这样才会在指定的端口进行工作。
  4. listen可以为一个socket创建一个等待队列,可以指定队列长度。这个长度表示 最多可以接受的同时的全连接数目(也就是established状态的连接数,往往是比传入的长度大一点)。也就是同时处于established状态的连接数。到目前为止,其已经可以接受 连接了。这完全是由内核来控制的。如果连接来了,如果队列没有满的话,内核建立连接,直接放入队列。如果满了的话,只是处于半连接的状态,也就是SYN_RCVD,就是回应ack之后,不进入连接状态。 而accept函数只是将 内核已经处理过的建立的连接返回给用户,没有accept,也会自动建立连接的。
  5. 一个执行过listen函数的socket叫做 监听 socket。其结构中有local,代表本机的地址,还有一个等待队列。队列可能很长。其可以生成其他的socket。而一般的连接socket是有local和foreign两个结构的,local地址结构对应于 发缓冲区,foreign地址结构对应于 接收缓冲区。没有等待队列。
  6. close其实只是将fd的引用数 减一。当引用数为0的时候,内核才会关闭。比如子进程创建的时候,会导致socket的引用数加1.必须,子进程和父进程都close,socket才会真正close。shutdown可以实现真正意义上的关闭。并且可以选择关闭 读 或者 写 或者 读写功能。
  7. 读写:recv和send是为socket设计的读写函数,比read和write更加强大,提供了flag选项,可以提供MSG_DONTWAIT 等标志,也就是功能。还可以使用这个来进行带外数据的提取。
    而UDP的读写采用recvfrom 和sendto。因为其不是面向连接的,发送的时候不知道应该发给谁,每次都要指定。接收的时候不知道应该接收人的信息,因此,每次都要传到结构体中查看一下。
  8. 可以使用MSG_OOB来发送或者接收带外标记。有带外标记 的数据报有 U标志,同时,有urg指针(相对于当前seq的偏移量,代表最后一个带外标记数据的后一个字节标记)。其urg指针前一个字节数据 不算是正常的数据。
  9. 地址信息函数 :getpeername 获得foreign端地址 getsockname 获得local端地址
  10. 此外还有gethostbyname gethostbyaddr (仅仅是主机信息,不包括服务信息), getservbyname 和 getservbyport (仅仅是服务信息) getaddrinfo getnameinfo
  11. pipe函数可以创建一个管道文件,参数为一个有两个元素的int数组。 只可以从fd[1]写,fd[0]读。管道文件默认大小为65535字节。当然可以使用fcntl来更改大小。使用socketpair函数可以创建一个本地的双向管道。
  12. dup和dup2可以复制一个file descriptor。并且返回一个新的file descriptor 指向和参数一样的内容。这个产生的file descriptor 是当前系统中最小的。因此,如果我们希望关掉stdout,然后调用dup,就会获得一个和stdout同名的file descriptor(都为1).那么标准输出就会将内容输出到其他地方。实现重定向。
  13. iovec结构体表示一个 io vector。这个结构体里面有两个成员,iov_base:表示内存的开始地方 iov_len:内存的长度。可以用来表示一段内存。可以使用io_vec数组来表示多段内存。
  14. writev函数和readv函数接受一个io_vec 指针(指向一个数组)作为参数,分别表示将io_vec数组所表示的多个内存段中的内容依次写入(在逻辑上是在一块内存中的,连续的)/ 读出。在http请求头解析之后,我们需要返回响应头和消息体,而响应头和消息体往往在不同的内存中。可以使用writev函数。
  15. sendfile函数可以直接将一个文件描述符 来 传递给一个socket文件描述符。只在内核中完成,避免了用户空间和内核中间的拷贝,效率比较高。
  16. mmap和munmap可以完成内存的映射(映射文件)和取消映射。可以设置进程之间的共享内存。
  17. splice用于在文件和管道之间 移动数据,而tee用于在管道和关内道之间 复制数据
  18. fcntl的使用。

Linux服务器规范和框架

  1. 服务器一般要有 日志系统,服务器程序一般是 后台守护进程,一般以独有的普通用户名进行运行。
  2. 日志系统:Linux系统提供了日志系统,为rsyslogd守护进程来实现。log文件在/var/log下。当然可以设置。系统日志系统的架构要明白。用户日志可以通过syslog函数。
  3. 运行一个程序的时候,用户是很重要的。有uid和euid的概念。可以通过getuid和geteuid函数来获得对应的用户id。uid和euid中的euid代表 有效的uid。因为有的文件允许在使用的时候,使用文件拥有者的权限。这时可以用s代替x权限。
  4. 进程之间的关系:进程组:进程组有一个首领进程,其PID和PGID相同。**会话:**由多个不同的进程组构成。某个进程创建一个会话,会导致,这个进程成为这个会话的首领,并且这个进程创建一个进程组,其也作为进程组的首领进程。
  5. 资源限制:不同的系统会有不同的资源限制,比如文件名长度,栈大小,虚拟内存大小等等。获得这些信息可以做到充分利用,并且不会超过资源限制。
  6. 目录:getcwd用来返回当前工作目录 和 chdir 用来改变当前工作目录。不过一般的服务器工作根目录是在/var/www
  7. 阻塞式IO和非阻塞式IO:任何的文件描述符都有这两种模式。可以通过fcntl的setflg来进行控制。对socket来说,也可以通过调用socket的时候就设置。阻塞式IO执行的系统调用可能会因为需要等待某一个 条件,或者说事件而被操作系统挂起。非阻塞式IO一定不会被挂起,如果不满足条件,那么直接作为失败处理。非阻塞式IO一般搭配IO复用函数使用(IO复用函数其实就是可以同时检测多个文件描述符是否可以进行IO操作,也就是是否可读或者可写的函数。对于检测的文件描述符的要求式NON_BLOCK的,因为只有这样,其才可以一次检测多个IO。其自身可以式阻塞的,也可以式非阻塞的。阻塞,也就是至少有一个为可以IO的情况下才返回。阻塞的时候,也可以设定一个timeout事件,如果超时就返回。 通过 多个非阻塞IO + 一个IO复用函数 可以实现 在单线程的情况下,同时维持多个连接。也就是不管哪一个客户端发来信息,都可以快速相应)
  8. 阻塞IO和非阻塞IO都属于是同步IO。因为IO操作最终还是由用户程序完成的。而异步IO则是不需要管 满不满足 条件,将任务交给操作系统,然后通知操作系统,让其在完成任务之后给我发信号通知。
  9. reactor和proactor : reactor代表的是在 主线程监听不同的socket上的事件(比如使用epoll或者select来进行监听),当可读的时候,我们启动工作线程来完成 读请求–>逻辑处理–>向用户缓冲区写响应数据–> 向epoll或者select中添加可写监听事件 当主线程发现socket可写的时候,启动工作线程来完成 将用户缓冲区中的内容 写进socket的操作
    proactor:当主线程发现可读的时候,不是直接通知工作线程去读,而是交给内核让其去读,当内核完成读工作的时候,也就是内容已经在用户空间的缓冲区内了,给主线程发信号,然后主线程会启动 工作线程 来进行处理。工作线程完成根据用户缓冲区内容进行逻辑处理–>向用户缓冲区写响应数据–> 向内核发送写任务。当内核完成写任务,那么通知主线程,主线程需要再次进行响应。
  10. 前面所说的proactor和reactor的区别在于:reactor 是将 IO过程(将数据从内核空间中读到用户缓冲区 和 从用户缓冲区 写到内核空间)交给子线程来完成。proactor只是将这个IO过程交给内核来完成。(读到用户空间的任务是主线程下发的,而写到用户空间的任务是子线程下发的)。
  11. proactor和reactor都属于 半同步/半异步模式 也就是主线程是异步的,但是子线程是同步的。
  12. 领导者/追随者模式:线程池中总是有一个领导者线程。领导者线程的任务就是监听Handle set(Handle 就是可以代表一个io资源的东西,这里代表一个文件描述符)中是否有事件发生(也就是是否有读写事件发生)。当有事情发生的时候,指定或者推选出新的领导线程,然后自己去处理IO连接。这样就减少了线程之间的通信负担,也不需要有请求队列。完成事件处理依靠的是 绑定事件处理器
  13. 有限状态机:专门用于处理哪些有状态,并且需要通过不同的状态来进行转移的实体。
  14. 当程序需要同时处理多个IO的时候可能会用到IO复用技术。IO复用是 解决同时处理多个IO的一种解决方法。当然,还有其他的方法。一个服务器几乎必定要处理一个以上的IO(至少有一个监听socket和连接socket,除此之外,有的还需要有其他的数据库连接等等)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值