unix网络编程-笔记

unix网络编程

建立一个tcp连接时

  1. 服务器提前做好准备,具体通过调用socket、bind、listen函数,这个过程 被称为被动打开
  2. 客户端通过 connect 函数发起 主动打开, 并且发送一个SYN segment 告诉服务端该分节的序号,包含一些必要头部选项但不包含数据
  3. 服务器确立客户的SYN,同样发送一个SYN,还有一个ACK序号用来确认对方发来的SYN
  4. 客户端确认服务端发来的SYN,上述过程又称 三次握手

具体其中涉及到tcp连接时的其他细节,比如ack,seq, syn等头部选项的变化,以及三次握手的目的。。不在此

终止一个tcp连接时

  1. 客户端/服务端 调用 close 函数,执行 主动关闭,发送一个FIN segment
  2. 对方接受到 FIN 执行 被动关闭, 发送对方一个ACK, 同时将这个FIN作为一个 EOF(文件结束符)传递给当前发送缓冲区中的末尾,这就意味着自己不用在接受其他数据了
  3. 一段时间后, 发送缓冲区中的数据发送完毕,相应应用程序调用 close 关闭套接字, 并且向对方发送一个 FIN
  4. 接受到这个 FIN 后, 发送ACK来确认这个FIN

TCP相关状态

  1. 对于客户端:

连接阶段:

  • SYN_SENT(客户端主动打开时的状态) 发送SYN
  • ESTABLISHED(服务端返回SYN,ACK后)此后可以开始传输数据

传输数据阶段:。。。

关闭连接阶段

  • 若被动关闭,则直接进入CLOSE_WAIT 状态 ,否则己方应用程序进行主动关闭

    TIME_WAIT状态会持续2MSL(maximum segment lifetime),这个状态存在的意义在于:

    1. 可靠的实现TCP全双工连接的 终止,否则如果没有这个状态,也就是当收到ACK M+1后立即关闭,那么此时服务端发来的FIN将收不到应答ACK,然后会重发,3次以上后服务端报错,但此时只是关闭了一方,并不是全双工关闭

    2. 要让老的重复分节在网络中消逝,我们直到TIME_WAIT会持续2MSL,而一个数据包最多在网络中存在MSL,因此当处于TIME_WAIT时,如果当发送的ACK分组因为网络异常(路由异常或者其他)没有按时到达服务端(但有可能没有消失,还在一个MSL中),发送端超时重传,然后刚好异常回复,这时可能无论是旧的,还是新的都到达了目的。

      还有一种解释:如果此时在形同的ip和端口间又建立一个连接,因为是相同的ip,端口,因此TIME_WAIT的存在会防止旧的分组在关闭之后出现在新连接中,2MSL的时间足够,让发送的分组最多存活MSL,另一方向的应答也最多存活MSL,刚好2MSL之内旧的连接内确保所有分组不再存在网络中,关闭。

image-20220224111259626
  1. 对于服务端

    连接阶段

    • LISTEN(被动打开) 当初次收到SYN 时
    • SYN_RCVD 并且发送SYN 和 ACK
    • ESTABLISHED 是在收到ACK 后

    传输数据:。。。

    关闭阶段

    • 收到FIN 进入CLOSE_WAIT
    • LAST_ACK 发送FIN 应用关闭
    • CLOSED 收到ACK

TCP和并发服务器

  • 并发服务器当接受一个客户的连接时,会 fork 一个自身的副本,让子进程来处理客户的请求
  • 服务器上拥有 监听套接字已连接套接字 ,监听套接字就是父进程,而已连接套接字就是一系列子进程,但他们使用相同的21端口

缓冲区大小及限制

  • 涉及到数据的最大数据长度,
    • TCP分组的最大数据长度MSS用来通知对端TCP在每个分节中能发送的最大TCP数据量,
    • 以太网的最大MTU(maximum transmission unit),一般为1500
    • 在以太网中的IPv4的MSS为1500 - ip头部的20 - tcp头部的20 = 1460
    • 在TCP头部选项中的MSS为65535(16位的字段)
    • 在IPv4数据报中的最大TCP数据量时65495(IPv4数据报最大65535 - ip头部20 - tcp头部20
  • 比如某个应用程序写数据到一个TCP套接字中时发生的过程:
    • 一个应用程序向应用程序缓冲区发送数据(发生在用户进程中)
    • 调用内核write函数调用, 将应用程序缓冲区写到套接字发送缓冲区中(发生在内核中)
    • 此时,如果套接字发送缓冲区中的数据不足以承载应用程序缓冲区(可能其中含有其他数据或者压根就放不下),那么wirte调用将被默认阻塞,内核将不会从wirte调用中返回,直至可以将数据全部复制到套接字发送缓冲区中。
    • 然后TCP提取套接字发送缓冲区中的数据发送给对端TCP,加头部成为TCP分组,加头部成为IP数据报,查找路由表确定目的接口,加头部成为数据帧送给数据链路。
    • 注意:在UDP中,实际上并没有套接字发送缓冲区

套接字编程(套接字API)

套接字地址结构

  • IPv4套接字地址结构以 sockaddr_in 命名, 在 <netinet/in.h> 下:

    • 是一个主要包含 sin_family , sin_port , sin_addr 这三个成员的 struct (sin_addr类型是只有一个unit32_t成员的名叫 in_addr的一个结构体)

    • 分别用来描述套接字地址结构的 地址族, TCP或UDP端口,IPv4地址 (注意:ip地址和端口要求以网络字节序在这个地址结构中存储

    • 套接字结构仅在主机上使用,不会在主机之间进行传递

    • 同时,为了解决不同协议的地址结构可能因类型不同而导致套接字函数要支持不同类型的不同协议地址结构而变得复杂,因此设计了一个 通用的套接字地址结构 sockaddr如下, 这就导致,任何以套接字地址结构的参数的套接字函数都需要进行强制转化,如 bind(sockfd, (struct sockadd*) &serv, sizeof(serv))

      struct sockaddr{
          uint8_t sa_len;
          sa_family_t sa_family;  //协议族:AF_xxx
          char sa_data[14]; //协议具体地址
      } 
      
  • IPv6套接字地址结构

    • 具体的sockaddr_in6相关成员含义
  • 值-结果参数

    • 当向一个套接字函数传递一个套接字地址结构时,总是通过指向它的地址的指针来作为参数,而另一个用来表示该结构长度的参数则是取决于 结构的传递方向
      • 从进程到内核传递套接字地址结构的指针的函数:bind, connect, sendto,这样内核才能知道到底需要从进程中复制多少数据
      • 从内核到进程: accept, recvfrom, getsockname, getpeername,但和上面不同的是,这几个函数中的那个用来表示结构长度的参数不是一个int值了,而是一个指针,这个指针指向一个整形变量:用来表示内核实际上在该结构中存储了多少信息,而上面的那个参数只是为了让内核写数据时不至于越界
  • 字节排序函数

    • 内存中存储数据的方式:小端和大端方式,就是数据的高位到低位是从 低地址到高地址,后者则相反是数据高位到低位是从 内存低地址到高地址, 这个在不同机器中规定不同

    • 而对于网络协议来说,必须指定一个 网络字节序,使得双方对于各字节的顺序保持一致,使用 大端字节顺序来传送,也就是数据高位在内存地址的地位;

    • 具体来说,套接字地址结构中的某些字段要求必须按照网络字节序进行维f护,不能使用主机字节序,通过使用:

      <netinet/in.h>头文件下的 htons, htonl函数 : 用来实现从主机字节序到网络字节序的转化; ntohs , ntohl 函数则相反

  • 字节操纵函数(在string.h 下)

    • bzero 把指定数目的字节置为0 通常用来将一个套接字地址结构初始化为0; 还有 bcopy, bcmp
    • memset, memcpy, memcmp
  • 地址转换函数(用来在ASCII 字符串和网络字节序的二进制值间转换)

    • inet_aton, inet_addr 用来将一个c字符串转换成网络字节序二进制值
    • inet_ntoa 将网络字节序二进制地址转化为点分10进制字符串
    • inet_pton(p: presentation n: numeric) 和 inet_aton 功能相同
    • inet_ntop 和 inet_ntoa 相同

基本TCP套接字编程

  • ​ 通常TCP客户和服务器进程直接发送的事件时间顺序:

    image-20220223141336224
  • socket 函数

    • 执行网络IO,一个进程首先要调用 socket函数, 指定协议

      #include <sys/socket.h>
      int socket(int family, int type, int protocol);
      
    • 参数faiily 表示协议族, 通常值为 : AF_INET ipv4协议,AF_INET6 IPv6, AF_LOCALUNIX域协议, AF_ROUTE路由套接字, AF_KEY密钥套接字

    • 参数type 表示套接字类型, 通常值为:SOCK_STREAM字节流套接字 SOCK_DGRAM 数据包套接字 SOCK_SEQPACKET有序分组套接字 SOCK_RAW原始套接字

    • Protocol值 IPPROTO_CP, IPPROTO_UDP, IPPRO_SCTP 表示某个协议(tcp, udp ,sctp),也可以为0,这样就由前俩个参数默认组合

    • 该函数返回一个套接字描述符(sockfd)

  • connect函数

    • 客户端用来建立和TCP服务器的连接

      int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
      
    • 函数需要一个套接字描述符,一个套接字地址结构(包含了服务器ip地址和端口),和其大小

    • 若是TCP套接字,调用connect 相当于三次握手, 但有可能失败:

      1. TCP客户没有收到SYN的确认,返回 etimedout
      2. 或者收到乐意RST(表示复位),则表明服务端没有指定的端口的进程运行
      3. 路由器出错,返回了一个ICMP(目的不可达),客户端将会继续发送SYN(若在75s后认为收到相应), 将ehostunreach或enetunreach错误返回
    • 不论失败还是成功,都需要执行完进行close()

  • bind函数

    • 将一个 本地 协议地址(ip:port)赋予一个套接字

      int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
      
    • 一般来说,客户端可以不指定可以不指定一个port, 有内核进行选择一个临时端口;但对于TPC服务器来说不会交给内核指定端口,因为服务器的端口一般是众所周知的

    • 对于客户端来说,绑定一个本地网络接口的IP地址到套接字上,意味着指定了源地址;对于服务器来说,通常不需要绑定IP地址,如果绑定了,就意味着只接受那些目的为这个IP地址的客户连接

  • listen函数

    • 只能由服务器调用,将未连接的套接字转换成一个被动套接字,指示内核应该接受指向该套接字的连接请求

    • int listen(int sockfd, int backlog)
      

      backlog表示内核应该为该套接字设置的 排队最大连接数,一般内核回给监听套接字安排俩种类型的队列:

      1. 未完成连接队列 对应SYN_RCVD状态,其中的每一项就是一个SYN分节,只不过此时服务器在等待三次握手的过程的完成

      2. 已完成连接队列 对应ESTABLISHED状态

        image-20220224111736186
  • accept函数

    • 同样由服务器调用,用来将 已完成队列 中的队头出列并返回,如果队列空,则该进程睡眠(挂起)

    • int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
      

      cliaddr参数表示对端进程(客户)的协议地址

      addrlen参数在调用前将其置为一个指向 cliaddr 所指向的套接字地址结构的长度;调用后是内核中存放该套接字地址结构内的确切字节数

    • 调用成功后,会返回一个由内核生成的描述符,用来代表和所返回客户的TCP连接

    • 第一个参数要求是一个 监听套接字(经由sockt, bind, listen函数创建/使用),而返回的是一个 已连接套接字

      俩者区别:前者一般一个服务器只有一个,后者则对于每个接受的客户连接都会创建一个;并且俩者的生命周期也不同

  • fork 函数

    • #include<unistd.h>
      pid_t fork(void);
      
    • 调用fork 一次,返回俩次:会在父进程中返回一次,返回值是新进程的pid;会在子进程中又返回一次,返回值是0,从而告知当前进程是父进程还是子进程

    • 这个函数通常用来进行服务器的网络并发

    • 也可以用来执行程序,具体就是通过fork 一个子进程,让子进程去exec ,从而把自身替换成新的程序

    • #include<unistd.h>
      int execve(const char *pathname, char *const arg[], char *const envp[]);
      

      通常还有其他exec函数,但都是调用也只有这个是内核的系统调用

  • close函数

    • 将套接字标记为已关闭, 同时返回到调用进程,此后该套接字描述符不能再由调用进程使用
    • 但调用close不一定会导致TCP的四次挥手,因为他只是将套接字的引用计数减1,可能有其他进程在使用这个套接字;shutdown函数则会直接向TCP连接发送 FIN来代替close
  • getsocknamegetpeername函数

    • 用来返回套接字关联的 本地或者关联的 外地的协议地址
  • signal函数

    • 建立信号处置的俩种方法:调用 sigaction函数,调用 signal函数,前者调用流程:
    • 调用sigaction函数的复杂之处在于,该函数的参数之一是我们必须分配并填写的结构
    • 设置处理函数,传递给sa_handler
    • 设置处理函数的信号掩码,sa_mask设置为空集意味着该信号处理函数运行期间,不阻塞额外的信号
    • 设置sa_flags,比如SA_RESTART
    • 调用sigaction函数
  • 处理 SIGCHILD 信号

    • listen调用之后增加 signal函数(要在fork第一个子进程之前完成),函数中的信号处理函数为 wait或者 waitid函数
  • waitwaitpid函数

  • accept函数调用时可能发生的错误:

    • 在accept调用之前,客户端connct返回之后,发送了一个RST,此时accept还没有返回但连接以及被中断了,需要在调用一次accept函数
    • accept在被 慢调用阻塞期间,捕获了一个信号,信号处理程序处理后返回可能会中断accept函数,必要时需要重新运行这个函数调用
  • 当服务器进程终止时,而此时客户端可能阻塞在某个函数调用上,比如fgets等待终端的输入。也就是说当FIN到达客户端到达套接字时,但是此时客户正在阻塞于fgets上,它看不到这个FIN,直到该套接字可以为止;否则正常是要将FIN作为一个EOF加入到接受缓冲区末尾,客户实际面对的是套接字和用户输入这俩个文件描述符,他不能只是阻塞在这俩个源中的某个特定源的输入上,而是都将他们阻塞。这个时候要能够提前告知内核,使得内核一旦发现进程指定的一个或多个I/O条件准备就绪了(就是说可以被读取了),那就通知进程。这个过程就叫I/O复用,用 select 和 poll函数处理后,会处理这个问题:一旦杀死了服务器子进程,客户会被立即告知已收到FIN,而不是还让其继续输入

  • 当服务器主机崩溃时

    • 客户端发送了数据分节后,会等待对方的应答
    • 然后持续重传数据分节,试图等待接受一个ACK
    • 当9分钟后,客户端TCP放弃重传,返回一个错误:
      • 如果时服务器崩溃,则返回一个 ETIMEDOUT
      • 如果是目的不可达,则返回一个 EHOSTUNREACHENETUNREACH
    • 通过对比如readline调用设置一个超时,可以不用等这9分钟就能更快的检测;或者,通过 SO_KEEPALIVE套接字选项,这样不主动发送数据也可以检测出来
  • 当服务器主机崩溃后再重启

    • 然后客户端发送一个数据
    • 服务器虽然重启了,但是之前的连接信息都丢失,因此响应一个RST
    • 客户端收到RST时,正阻塞在readline中,然该调用返回一个ECONNRESET错误。

I/O复用:select和poll函数

  • 应用场景

    • 客户处理多个描述符时
    • 服务器既要处理监听套接字,又要处理已连接套接字
    • 服务器既要处理TCP,又要处理UDP
    • 服务器要处理多个服务或者多个协议
  • I/O模型

    • 非阻塞式I/O模型

      进程把一个套接字设置成非阻塞是在通知内核:当所请求的I/O操作非得把本进程投入睡眠才能完成时,那就不要将其投入睡眠,而是返回一个错误

    • 阻塞式I/O(blocking I/O)模型
      默认情况下所有套接字都是阻塞的

    • I/O复用模型

      通过调用select 或 poll,阻塞在这俩个系统调用中的一个上,而不是阻塞在真正的I/O系统调用上;比如当阻塞于select调用时,等待数据报套接字变为可读。当select返回套接字可读这一条件时,就不阻塞在select上了

    • 信号驱动式I/O模型

    • 通过使用信号,让内核在描述符就绪时发送SIGIO 信号通知我们,这样在等待数据报到达的期间进程不被阻塞,主循环可以继续执行,只要等待来自信号处理函数的通知:既可以是数据已准备好被处理,也可以是数据报已准备好被读取

    • 异步I/O模型(asynchronous I/O)

      • 告知内核启动某个操作,并让内核在整个操作(包括将数据从内核复制到我们自己的缓冲区)完成时通知我们,它和信号驱动模型的主要 区别:前者是要内核通知我们I/O操作何时 完成,而后者是由内核通知我们何时可以 启动一个I/O动作
  • select函数

    • 该函数允许进程指示内核等待多个事件中的 任何一个 发生,并只有在有一个或多个事件发生或经历一段指定的时间后才唤醒它

      #include<sys/select.h>
      #include<sys/time.h>
      
      int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset,
                 const struct timeval *timeout);  
      							//若有就绪描述符则返回数目,否则超时返回0,错误返回-1
      

      timeout 告知内核等待任何一个指定描述符就绪时要花的时间

      1. 置为空指针:仅在有一个描述符准备好I/O时才返回,否则永远等下去
      2. 值为0:检查描述符后立刻返回,这称为轮询,其中的timeval中的tv_sec, tv_usec都要为0
      3. 在有一个描述符准备好且不超过参数所指定的timeval中的秒,微秒数才返回

      readset, writeset, exceptset 指定我们要让内核测试读,写和异常条件的描述符:

      1. 使用fd_set的数据类型来作为一个 描述符集(一个整数数组,每个整数中的一位对应一个描述符)并通过4个宏进行初始化
      2. 如果对某一个条件不感兴趣,就可以把他设为空指针

      maxfdp1 参数指定待测试的描述符的个数,它的值是待测试的最大描述符的加1(描述符是从0开始的)

      每次调用select时,都要把描述符集内的关心的位置为1;因为在select函数返回时,描述符集内没有就绪的描述符对于的位会请0

    • 引起select返回套接字”就绪“的条件

      • 套接字准备好读,此时可能是下列条件中的任何一个发生了:
        1. 套接字接收缓冲区中的数据字节数大于等于其低水位标记的当前大小(TCP/UDP默认为1,可以通过SO_RECVLOWAT套接字选项设置)
        2. 读半部关闭(也就是接受了FIN的TCP连接),这样的情况下对套接字的读也不会阻塞,并发送EOF
        3. 或者当有一个套接字错误待处理,会设置errno,并返回-1
        4. 该套接字是一个监听套接字且此时已连接套接字数量不为0.对这样的套接字的accep通常不会阻塞
      • 套接字准备好写,满足下列任何一个条件:
        1. 套接字发送缓冲区中的可用空间字节数大于等于其低水位标记的当前大小,或者该套接字已连接,或者该套接字不需要连接(如UDP套接字)
        2. 该连接的写半部关闭。对这样的套接字的写操作会产生SIGPIPE信号(这个信号的产生是因为对一个已返回RST的套接字执行写操作,要知道此时对方进程已经终止了,这与关闭连接不同,连接关闭了依然接受数据只是无法发送了)
        3. 套接字上有一个错误待处理
        4. 套接字存在 带外数据或者仍处于 带外标记
      • 接受低水位标记和发送低水位标记目的在于:允许应用进程控制在select返回可读可写条件时到底缓冲区中还有多大空间用来读或写
  • shutdown函数

    • 和close不同的是:

      • close会将描述符的引用减1,但只在计数变为0时才会关闭套接字。而使用shutdown可以不管引用计数就激发TCP的正常连接终止序列(四次挥手)

      • close会同时终止读和写俩个方向的数据传送,而有时我们只需要终止一个方向上的连接,比如告知对端我们已经完成了数据发送,但不拒绝对端发送给我们的数据,这是就用shutdown

      • #include<sys/socket.h>
        int shutdown(int sockfd, int howto); //成功返回0,否则出错返回-1
        

      howto参数的值:

      ​ SHUT_RD 关闭连接的读这一半,就是不允许这端进行读数据了,套接字中不再有数据可以接受,现有的套接字接收缓冲区中的数据将被 丢弃

      ​ SHUT_WR 关闭连接的写这一半,对于TCP套接字,这称为 半关闭,套接字发送缓冲区中的数据将被发送掉,后面跟正常的连接终止序列(四次挥手的四个序列)

      ​ SHTU_RDWR 连接的读半步和写半部都被关闭

  • 拒绝服务型攻击(DOS)

    • 就是针对服务器做些动作,导致服务器不能再为其他合法客户提供服务
    • 解决办法:设置超时时间,使用非阻塞式I/O,让每个客户由单独的控制线程提供服务
  • pselect函数

  • #include<sys/select.h>
    #include<signal.h>
    #include<time.h>
    
    int pselect(int maxfd1, fd_set *readset, fd_set *writeset, fd_set *exceptset,
               	const struct timespec *timeout, const sigset_t *sigmask);
    //成功返回就绪描述符的数目,超时返回0,出错返回-1
    

    相较于select有俩个变化:

    1. pselect使用timespec结构,而不是timeval结构,区别在于前者能够指定纳秒数
    2. pselect增加了第六个参数:一个指向信号掩码的指针。该参数允许程序先禁止递交某些信号
  • poll函数

    • 除了可以工作在描述符上,还可以对 流设备进行处理

      #include<poll.h>
      
      int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);
      

      fdarray是一个指向一个结构数组的第一个元素的指针,每个数组的元素都是一个 pollfd结构,用来指定测试某个给定描述符fd的条件:

      struct pollfd{
          int fd;
          short events;
          short revents;
      }
      

      fd=-1时,表示不关心enents用来指定描述符的测试条件,revents用来返回该描述符的状态;同select值-结果参数不同;这俩个成员的值可以指定为不同的常量:处理输出的,处理输入的,处理错误;并且识别三类数据:普通,优先,高优先;

      nfds用来指明数组的元素个数

      timeout指定poll函数返回时等地多长时间

通用套接字选项

  • SO_BROADCAST选项

    用来开启或者禁止进程发送广播消息的能力,但是只有 数据报套接字才支持广播,并且还必须是在支持广播消息的网络上(以太网,令牌环网),不可能在基于连接的输出协议(tcp, sctp)上进行广播

  • SO_DEBUG选项

    仅由TCP支持,内核将为TCP在该套接字上保存接受,发送的详细分组信息

  • SO_DONTROUTE选项

    规定外出的分组将绕过底层协议的正常路由机制,比如路由守护进程(routed和gated)会通过该选项来绕过路由表,强制将分组从特定接口送出

  • SO_ERROR套接字选项

    当套接字发送错误时,内核会将该套接字的名为so_error的变量设为标准的Unix exxx值中的一个,然后通过正在阻塞中的select调用返回或者信号驱动式I/O模型给进程产生一个SIGIO信号

  • SO_KEEPALIVE选项

    给一个TCP套接字设置该选项后,如果2小时内该套接字没有进行数据交换,TCP就会自动发送一个 keep-alive探测分组,会导致:

    1. 对端回复ACK,一切正常
    2. 对端回复RST:表示自己已崩溃并在重新启动,套接字会设置pending error即待处理错误(so_error)的值,然后关闭
    3. 对端没有相应,则TCP将另外发送新的探测分组,每个75秒,若在第一个之后11分15秒后没有依旧没有则放弃,设置so_error为ETIMEOUT
    4. 收到一个ICMP错误作为相应,表面对端不可达,设置so_error为EHOSTUNREACH

基本UDP套接字编程

  • recvfrom函数和sendto函数

    #include<sys/socket.h>
    ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags, 
                     struct sockaddr *from, socklen_t *addrlen);
    ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags,
                  	const struct sockaddr *to, socklen_t addrlen);
    
    • 类似于read和write函数, 前三个参数分别是:描述符,指向读入或写出缓冲区的指针,读写的字节数
    • flags参数 recv, send, recvmsg, sendmsg 需要用到该标志
    • sendto的 to参数指向一个含有数据包 接收者 的协议地址(如ip地址和端口号)的套接字地址结构,其大小由addrlen参数指定。
    • recvfrom的 from参数指向一个由该函数在返回时填写数据包 发送者的协议地址的套接字地址结构,并且它是以一个值-结果参数
    • recvfrom最后俩个参数类似于accept中最后俩个参数:告诉我们是是谁发送了数据报(udp)或建立了连接;sendto最后俩个参数类似于connect中最后俩个参数:其中套接字结构是被我们填写过的将发往对方的地址结构
    • 俩个函数的返回值都是:读写数据的长度
  • 一般来说大多TCP服务器是并发的,而UDP服务器是迭代的,也就是单个进程处理所有的用户请求

  • 每个UDP套接字都有一个接收缓冲区,当进程调用recvfrom时,缓冲区中的下一个数据报会以FIFO的顺序返回给进程。SO_RCVBUF套接字选项讨论了这个大小以及如何增大它

  • 与TCP不同的是,他不会派生新的子进程来处理不同的连接,UDP服务器进程只有一个套接字,只有一个接收缓冲区。

  • UDP的connect函数

    • 一般UDP是未连接的(这里是指没有调用connect函数),那么异步错误(比如ICMP不可达 UNP p196)是不会返回到UDP套接字的
    • 但UDP的connect函数由和TCP的不同,不会进行三次握手,内核只会检查是否存在立即可知的错误,然后返回传递给connect的套接字的地址结构中的ip地址和端口号到调用进程中
  • 区分未连接UDP套接字和已连接UDP套接字,区别在于后者可以不用在sendto中指定地址或者之间使用write,send;同样也不用通过recvfrom来获得数据报的发送者,而改用read, recv, recvmsg(要设置相关选项);同时后者只能拿与一个对端进行交换数据,就是绑定到connect函数上的那个对端

posted @ 2022-05-01 14:46  Kuaid1an  阅读(119)  评论(0)    收藏  举报