C++面试 一 socket、tcp相关知识总结

1. socket编程知识

    1> server和client函数调用流程

        server: socket、bind、listen、accept、send/recv

        client:socket、connect、recv/send

    2> listen函数第二个参数的作用

        当客户端发起连接,发送SYN到服务端后,服务端的socket就处于SYN_RCVD状态,并将其放入未完成队列,然后服务端发送SYN ACK到客户端,客服端应答ACK到服务端,此时服务端的socket编程ESTABLISHED状态,并将其放入已完成队列。

        调用accept之前,队列的大小,就是listen函数的第二个参数,backlog的大小。

        调用accept是从已完成队列中取出一个,这样,未完成队列的就可以进入已完成队列。

        比如设置10,一次来了20个连接,会先处理10个,剩下的会拒绝, TCP是忽略这些请求,也就是不发送RST,这样客户端会重发SYN。

        设置了syncookie选项,则第二个参数无效,是为了防范SYN Flood攻击,原理是:在TCP服务器收到SYN包并返回SYN+ACK包时,不分配一个专门的数据区,二是根据这个SYN包计算一个cookie值,在收到ACK包时,根据cookie值检查这个ACK包的合法性,如果合法,再分配专门的数据区进行处理。

    3> recv函数返回值是什么含义

        recv先等待发送缓冲区的数据被协议传送完毕,如果传送中出现网络错误,则recv函数返回SOCKET_ERROR错误

        如果接收缓冲区中没有数据,或正在接受数据,recv会等待数据接收完毕,然后把接收缓冲区中的数据copy到buf中,recv仅仅是copy数据,真正的接收数据时协议完成的。

        阻塞和非阻塞的recv返回值没有区别,

        > 0 是实际copy数据的大小

        = 0 是连接正常关闭

        < 0 是出错,出错时需要根据errno判断需要做什么处理

            errno == EINTR 操作被信号中断,认为连接正常,可以jxu接收数据

            errno == EWOULDBLOCK || EAGIN 阻塞或非阻塞socket,没有数据或接收超时,也认为连接正常,可以继续接收

            errno == 其他错误认为连接不正常,可以关闭连接

    4> 怎么判断字符接收完毕

        一般是自定义一个结束符,比如\n,

        或者由socket的接收和发送端约定好一种消息的结构,比如先发数据长度,再发实际数据

    5> accept返回的socket和listen中socket的关系

        在服务端创建的socket,用于listen和accept,这个socket不能用于和客户端之间交互数据

        accept接受客户端的连接后,返回一个新的socket,这个socket用于和这次连接的客户端之间进行通信,使用getsockname和getpeername可以获取到这个新socket的本端地址和对端地址。

2. TCP相关问题

    1> TCP的三次握手和四次挥手

        

   2> 为什么是三次握手?

        1. 防止已经过期的连接请求突然传送到服务器,从而产生错误。客户端发送连接请求SYN后,由于某个网络节点拥塞导致很长时间后才到达服务端,如果是两次握手,服务端收到后,应答,就认为建立连接了,但是在客户端看来,这是一个已经失效的连接。

        2. 如果服务端收到连接请求后,应答的消息丢失,客户端会进行重传,如果数据一直丢失,服务端就会产生很多无效连接,占用资源。 syn flood攻击

    3> 为什么四次挥手?

        确保数据能够传输完成。当客户端主动发起关闭连接时,服务端收到FIN,仅仅表示客户端没有数据要发送了,服务端应答ACK,之后还可以继续发送数据,发送完之后,再发FIN,表示自己也没有数据要发送了,服务端的ACK和FIN多数情况下是可以分开发送的

    4> 关闭的发起端,最后为什么等待2MSL

        MSL是报文最大生存时间。发起端第四次挥手发送ACK之后,就进入到TIME_WAIT状态,必须等待2MSL,是因为如果最后一个ACK,对方没有收到,那么对方在超时之后,会重发第三次挥手的FIN包,发起端收到重发的FIN包后,可以再发送一个ACK应答。

        处于2MSL等待状态TIME_WAIT时,任何迟到的报文都将被丢弃.

    5> TCP的多连接,高并发

        1. 单机支持最大TCP连接数

            accept返回的socket用来标识一个TCP连接,

            系统使用一个四元组来表示一个连接{local ip, local port, remote ip, remote port}

            客户端最大的TCP连接数由端口数决定,端口是unsigned short,最大65535

           服务端监听在一个固定端口,因此最大tcp连接数为客户端的ip数*端口数,大约为2的32次方 * 2的16次方,也就是服务端最大支持2的48次方,但是实际上,linux系统限制连接数的因素主要是 内存和允许的文件描述符个数。

            ulinit -n 输出1024表示一个进程最多只能打开1024个文件

            临时修改 ulimit -n 100000 重启或用户退出后失效

            永久修改 /etc/rc.local 添加 ulimit -SHn 100000

            全局限制 cat /proc/sys/fs/file -nr 输出9033 0 592026分别表示已经分配的文件句柄数、已分配但未使用的文件句柄数、最大文件句柄数

3. I/O多路复用select、poll、epoll机制

    IO多路复用就是通过一种机制,可以监视多个描述符,一旦某个描述符可读可写,就能够通知程序进行响应的操作。select、poll、epoll本质上都是同步I/O,因为它们都需要在读写事件就绪后,自己负责读写。异步IO的实现会负责把数据从内核拷贝到用户空间。

    select实现

        1. 将fd_set从用户空间宝贝到内核空间,并注册回调函数

        2. 遍历所有的fd,调用pollwait方法,主要工作是吧当前进程挂到设备的等待队列中

        3. poll方法会返回描述符读写操作是否就绪的掩码mask,并给fd_set赋值,遍历完所有的fd,还没有可读写的,就调用timeout进入睡眠,如果超过一定时间,则重新唤醒

       4. 将fd_set从内核空间拷贝到用户空间

       select的缺点:1. 每次都把fd_set拷贝到内核,2.需要遍历所有的fd,3. 最大支持1024

    poll的实现

        本质上和select类似,但是没有最大文件描述符数量的限制,另外两个缺点和select一样

    epoll的实现

        epoll_create

        epoll_ctl 注册新的时间到epoll句柄中时,会把fd拷贝到内核,而不是在epoll_wait的时候拷贝,保证了每个fd在整个过程中 只会拷贝一次,解决了select的第一个缺点。

        epoll_wait,不同于轮训,epoll会把发生的可读写事件通过callback回调机制通知到用户程序,相当于事件驱动

        epoll对文件描述符操作有两种模式:LT水平模式和ET边缘模式

        LT模式:当epoll_wait检测到事件发生时通知应用程序,可以不处理,下一次调用epoll_wait时,会再次通知到应用程序。

        ET模式:当epoll_wait检测到事件发生时,必须马上处理,如果不处理,下一次调用epoll_wait时,将不再通知到应用。

        ET模式很大程度上减少了epoll事件被重复触发的次数。因此效率要比 LT高。

    epoll的优点:

        1. 没有最大并发连接的限制,能打开的fd上限,远远超过1024

        2. 效率高,不是轮询的方式,而是只有活跃的FD才会调用callback

       3. 利用mmap文件映射内存,加速用户空间和内核空间的消息传递,减少复制开销

4. TCP的6个标识位的作用

16位源端口号 16位目的端口号
32位序列号
32位确认号

4位部首

长度

保留

6位

URG ACK PSH RST SYN FIN 16位窗口大小
16位校验和 16位紧急指针
选项
数据

    源端口号:表示发送端端口号,16位

    目的端口号: 表示接收端端口号,16位

    序列号:表示发送数据的位置,32位,没发送一次数据,就累加一次该数据字节数的大小。序号不会从0或1开始,而是建立连接时产生一个随机数,通过SYN发给服务端,然后每次累加

    确认序号:表示下一次应该收到的数据序列号,32位,这个序号之前的数据都已经正常接收

    部首长度:长度4位,单位是4字节

    控制位:

        URG:紧急指针是否有效,为1表示某一位要被优先处理

        ACK:确认号是否有效,一般为1

        PSH:表示接收端应用程序立即从TCP缓冲区读取数据

        RST:对方要求重新建立连接,复位

        SYN:请求建立连接,并在序列号字段设置初始值

        FIN:希望断开连接

        窗口大小:接收缓冲区的大小

        检验和:发送端填充CRC校验,TCP校验TCP首部加数据,UDP只校验数据

        紧急指针:只在URG为1时有效,表示本报文段中紧急数据的指针

        选项:用于提高TCP传输性能,最大长度40字节

5. TCP的重发机制

    TCP使用两套独立的机制完成重传,一是基于时间,二是基于确认信息

        1. 基于时间:设置一个定时器判断数据是否长时间没有应答,超时则重传

        2. 基于确认信息:

            1. 快速重传:当接收端收到失序报文时,要立即生成确认信息ACK,发送端会连续收到多个重复的ACK,此时发送端就可以判断出丢失的数据,然后马上重传

            2. SACK选项:TCP虽然是有序的,但是基于IP层的传输是无序的,TCP的接收端可能收到的序列号是不连续的,使用SACK选项,保存接收方已经接收的序列号范围,每个范围被称作一个SACK块,起始和结束序列号都是32位,最大包含3个SACK块。

    TCP滑动窗口机制

        窗口的大小是指无需等待确认,就可以继续发送数据的最大值,比如窗口大小是400,每次发送100,则第2、3、4包,不需要等待应答,就可以连续发送

        当收到第1包的ACK后,滑动窗口后移,继续发第5包

        系统开辟发送缓冲区来记录哪些数据没有应答,只有确认应答了的数据才会从缓冲区中删除

        窗口越大,网络吞吐率越高

6. TCP拥塞控制使用的算法和具体过程

    拥塞的标志:1. 重传计时器超时 2 接收到三个重复确认(丢包)

    基于丢包的拥塞控制分为四个阶段:慢启动、拥塞避免、快重传、快恢复

    cwnd拥塞窗口、ssthresh慢开始门限、RTT往返时间

    1. 慢开始:刚刚加入网络的连接,一点一点的提速,不要一上来就把路占满

        连接建立之后,先初始化cwnd=1,

        每收到一个ACK,cwnd++; 线性增长

        每当过了一个RTT,cwnd = cwnd*2; 指数增长

        为防止cwnd增长过大引起网络拥塞,设置一个慢开始门限

            当cwnd < ssthresh时,使用慢开始算法

            当cwnd = ssthresh时,既可以使用慢开始,也可以使用拥塞避免算法

            当cwnd > ssthresh时,使用拥塞避免算法

    2. 拥塞避免:(拥塞窗口线性增长)并不能避免拥塞,而是使网络比较不容易出现拥塞

        没收到一个ACK,cwnd = cwnd + 1/cwnd

        每过一个RTT,cwnd = cwnd + 1

        每经过一个RTT就把发送方的拥塞窗口加1

        无论在慢开始,还是在拥塞避免阶段,只要发送发判断网络出现拥塞(没有搜到确认),就把慢开始门限设置为出现拥塞时的发送窗口大小的一半,然后把拥塞窗口设置为1,执行慢开始算法

    3. 快重传:要求接收方在收到一个失序报文段后就立即发出重复确认,而不是等自己要发数据时捎带确认,(为的是及早通知到发送方有数据丢失),快重传算法规定:发送发只要一连收到三个重复确认,就应答立即重传对方尚未收到的报文段,而不必等待设置的重传计时器时间到。

    4. 快恢复:至少收到了3个重复的ACK,表明网络没那么糟糕,可以执行快恢复算法。

        每收到一个重复的ACK,则cwnd = cwnd + 1;

        收到非重复的ACK时,cwnd = ssthresh,转拥塞避免算法

        如果发生超时重传,则ssthresh = cwnd/2; cwnd = 1进入慢启动

7. SOCKET相关选项

1. SO_LINGGER选项用于控制close系统调用在关闭tcp连接时的行为

l_onoff = 0, l_linger = 0 是默认情况,close将FIN送入发送缓冲区,并立即返回,发送缓冲区由系统接管,系统将数据发送完成后,释放fd

l_onoff = 1, l_linger = 0 close会立即返回,发送缓冲区数据将丢弃,并给对端发送RST信号重置连接,close发送方直接进入close状态,没有time_wait状态

l_onoff = 1, l_linger > 0 close的行为取决于发送缓冲区是否有数据,和是否阻塞,如果是阻塞socket,close会等待linger的时间,直到发送缓冲区发送完毕,如果是非阻塞socket,close将立即返回,此时需要根据返回值和errno判断结果

2. SO_RCVTIMEO设置socket接收(发送)超时,防止socket一直等待造成阻塞

3. SO_REUSEADDR选项,一般情况下,一个端口释放后,等待2分钟才能再次被使用, 这个选项让端口释放后立即就可以被再次使用。

8. recv recvfrom recvmsg

ssize_t recv(int sockfd, void * buf, size_t nbytes, int flags);

只能用在建立连接的socket上, 比如TCP使用recv,针对SOCK_STREAM套接字,接收的数据 可以比发送的少,MSG_WAITALL可以改变这种行为,但这个标志对包传输没有影响

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

连接无连接都可以使用,可以获取数据发送者的地址,通常用于udp,否则等同于recv

ssize_t recvmsg(int sockfd, struct msghdr * msg, int flag);

连接无连接都可以使用

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章