1.什么是高可靠通信?

        高可靠通信是指在通信过程中,数据能够无丢失、无重复、无乱序、无损坏地从发送方传递到接收方,并且能够应对网络丢包、延迟、乱序等异常情况。

2.TCP与UDP的区别有哪些?

特性 TCP UDP
连接性 面向连接:传输前必须通过三次握手建立连接,结束后四次挥手释放 无连接:无需握手,直接发送数据包
可靠性 可靠:保证数据不丢失、不重复、按序到达 不可靠:不保证送达、不保证顺序、丢包不重传
控制机制 确认应答、超时重传、流量控制、拥塞控制 无任何控制机制,仅做简单校验
数据模式 面向字节流(无边界,连续传输) 面向数据报(有边界,独立发包)
首部开销 20~60 字节(变长,含序号、确认号等) 固定 8 字节(极短,仅含端口、长度、校验和)
传输效率 慢(机制复杂、延迟高) 极快(开销小、无等待、实时性高)
通信模式 仅支持单播(一对一) 支持单播、广播、组播(一对多)
典型应用 HTTP/HTTPS、FTP、SMTP、文件传输、远程登录 视频直播、语音通话、游戏、DNS、实时监控

TCP:可靠、慢、像打电话(先连接、再通话、确保听清)。
UDP:不可靠、快、像寄信(直接发、不管是否收到、不管顺序)。

3.简述三种超时检测的办法

1、通过setsockopt()函数设置套接字属性。设置的是fd,5秒没有获取数据则read()会返回-1报错

    struct timeval tm = {5,0};
if (setsockopt(fd, soL_SOCKET,SO_RCVTIMEO,&tm,sizeof(tm)) < 0) {
    perror("timeout");
    exit(-1);
}

2、通过select、poll、epoll多路复用机制附带的定时器完成,注意不可与设置套接字属性同时设置超时。以下为select函数举例

fd_set fds;    //定义位图
FD_ZERO(&fds);    //位图清空
FD_SET(fd, &fds);    //将需要检测的文件描述符fd插入到位图中
struct timeval tm = {5, 0}; //设置定时器 为5秒
if (select(fd + 1, &fds, NULL, NULL, &tm) == 0) {  //等于0超时,小于0报错,大于0表示fd可读
    //超时处理  注意select每次使用后都要重新更新fds与tm
}

3、通过信号定时器完成,使用setitimer()函数启动定时器,定时器到时后会触发SIGALRM信号。这里不多赘述,本质上与文件描述符无关,只是单纯的定时器。

4.什么是粘包?如何解决粘包带来的问题

粘包:TCP 是字节流协议,没有消息边界,多个独立的数据包可能被合并成一个发送,导致接收方一次读取多个消息,造成数据 “粘在一起”。

解决方法:

1.定长消息:每个消息固定长度,不足部分填充占位符。

2.特殊分隔符:在消息末尾添加约定的分隔符(如\r\n),接收方按分隔符拆分。

3.消息头 + 消息体:消息头包含消息总长度,接收方先读头再按长度读体(工业界最常用)。

注意UDP与SCTP是有边界保护,每次读取都是一个数据包的内容,不存在粘包现象

5.说出OSI七层协议模型,并简述各层的功能

1. 物理层(Physical Layer ):负责传输比特流,定义物理介质连接的特性。

2. 数据链路层(Data Link Layer):提供可靠的数据传输,处理帧的传输和错误检测。

3. 网络层(Network Layer):负责数据包的路由和转发,实现不同网络之间的通信。

4. 传输层(Transport Layer):提供端到端的数据传输,确保数据可靠传输,处理数据分段和重组。

5. 会话层(Session Layer):建立、管理和终止会话连接,处理会话层的控制和同步。

6. 表示层(Presentation Layer):处理数据的格式和编码,确保数据在不同系统间的兼容性。

数据格式转换、加密解密、压缩解压缩

7. 应用层(Application Layer):提供用户接口和网络服务,实现特定应用程序的功能HTTP、FTP、DNS 协议。

6.编写TCP服务器与客户端的代码框架(只编写函数名即可)

TCP:
服务端:
1.创建套接字(socket());
2.绑定IP、端口号(bind());
3.设置监听套接字(listen());
4.等待客户端连接(accept());
5.收发数据(read()/write()、send()/recv());
6.关闭套接字。

客户端:
1.创建套接字(socket());
2.连接服务端(connect());
3.收发数据(read()/write()、send()/recv());
4.关闭套接字(close())

为什么服务器要绑定,客户端不用绑定?

服务端:被动打开,必须固定端口,供客户端连接
客户端:主动打开,系统自动分配临时端口(ephemeral port)
通信依靠 五元组 唯一标识,客户端端口只要不重复即可
手动 bind 反而可能冲突(端口被占用)
服务端必须 bind:提供固定地址,让人找到
客户端不用 bind:主动连接别人,系统自动分配临时端口

8.TCP如何判断对方异常断电?

异常断电:对方主机突然断电,不会发送 FIN 包。

         判断方法:

        1.超时重传失败:发送方多次重传数据后仍未收到 ACK,判定连接断开。

        2.保活机制(SO_KEEPALIVE):开启 TCP 保活,定时发送探测包,多次无响应则判定连接失效。

        3.应用层心跳包:应用层定时发送心跳,超时未收到则判定断开。

9.二层交换机的工作原理

首先二层交换机主要用于在局域网中数据的转发。两台主机可以通过连接同一个交换机进行通信。

①主机A要想与主机B通信需要先知道对方的IP地址,接着根据两方的IP地址通过子网掩码判断是否在同一个局域网内;

②若在同一个局域网,则主机A需要获取主机B的MAC地址,此时主机A会发送ARP协议广播,请求主机B的MAC地址;

③交换机收到ARP的广播后会转发给所有连接在交换机的主机上;

④主机B收到此广播后会进行回应,告知主机A自己的MAC;

⑤主机A收到回应后便会构建出完整的数据包,再次发送数据给交换机;

⑥交换机收到数据后,可以查询交换机自己的网口映射表,判断与该MAC地址相连的对应网口,将数据包转发给主机B。

如果在通信过程中,网线发生过插拔,更换网口,导致交换机映射表对应的MAC地址丢失,再次收到发送给此MAC地址的数据包时,交换机会将此数据包转发给所有连接交换机的主机,此现象被称为“泛洪”。交换机会按周期更新网口映射表。

12.简述五种IO模型,并说明是同步操作还是异步操作

1.阻塞 IO:同步 IO,进程发起系统调用后一直阻塞,直到数据从内核复制到用户空间完成才返回(recv()会一直阻塞直到数据到达。)

2.非阻塞 IO:同步 IO,调用recv()立即返回,数据未就绪则返回错误,需轮询。(进程发起系统调用后,若数据未准备好立即返回错误,通过轮询不断重试,数据准备好后再阻塞复制)

3.IO 多路复用(select/poll/epoll):同步 IO,进程通过 select / epoll 等同时监听多个 fd,当其中任意一个就绪时再调用读取函数进行数据复制

4.信号驱动 IO:同步 IO,内核在数据准备好时发送信号通知进程,进程再主动调用函数复制数据。

5.异步 IO(AIO):异步 IO,进程发起 IO 请求后立即返回,内核自动完成数据准备和复制全过程,完成后通知进程,进程无需等待或主动复制

13.路由器的工作原理?简述数据包发送出去的流程

工作原理:路由器根据路由表,选择最佳路径转发数据包,实现不同网络间的通信。

数据包发送流程:

1.主机判断目标 IP 是否在同一网段,若不在则发送给默认网关(路由器)。

2.路由器收到数据包,根据目标 IP 查找路由表,选择下一跳。

3.将数据包转发到下一跳路由器,重复步骤 2,直到到达目标网络。

4.目标路由器将数据包转发给目标主机。

是连接两个或多个网络的硬件设备,在网络间起网关的作用,是读取每一个数据包中的地址然后决定如何传送的专用智能性的网络设备。        

假设我们电脑需要访问百度服务器。在我们输入网址www.baidu.com时,

①首先会先请求DNS服务器,获取百度的ip地址。

②接着会通过子网掩码判断百度的ip与自己的ip是否在同一个网段,因为百度在外网,所以不在同一个网段。

③那么我们主机就需要将数据包发送给网关,也就是路由器,数据包中的对方MAC地址填写为路由器的MAC地址。

④路由器收到此数据包后发现该数据包要对外,先进行NAT端口映射(该功能较为复杂,这里只讲一个最简单的)。NAT端口映射将数据包的源ip与源端口映射成一个新的端口号,保存到路由器的端口映射表中,再将映射后的端口填写到源端口上,将路由器的公网ip填写到源ip上。

⑤再查询路由表,找到与自己相连且距离百度服务器最近的路由器,将数据包中的目标MAC改为此路由器的MAC地址,也就是下一跳的MAC地址,源MAC改为自己路由器的MAC,将此数据包转发给下一个路由器。

⑥后面的路由器也都查询自己的路由表,转发给下一个路由器,直到发送给百度服务器。

⑦百度回发的数据包最终回到自己的路由器上,此时百度回的数据包目标ip写的是路由器公网ip,目标端口是映射后的端口,目标MAC是路由器的MAC地址。

⑧路由器收到此数据包后查询自己的端口映射表,将目标ip改为主机的内网ip,目标端口改为主机端口,目标MAC也改为主机的MAC地址,再将此数据包发送给主机。        

虽然看着写了很多文字,但依然是最简单的一种介绍,路由器本身协议的控制远比写的复杂,这里只简单介绍了路由器最核心的两个功能的使用:NAT与路由表

14.TCP三次握手与四次挥手的过程

SYN:用于建立连接,该位设为 1,表示希望建立连接,并在其序列号的字段进行序列号初值设定;

FIN:该位设为 1,表示今后不再有数据发送,希望断开连接。

ack确认序号(acknowledgement number):占32位(4字节),表示收到的下一个报文段的第一个数据字节的序号,如果确认序号为N,序号为S,则表明到序号N-S为止的所有数据字节都已经被正确地接收到了。

ACK:该位设为 1,确认应答的字段有效,TCP规定除了最初建立连接时的 SYN 包之外该位必须设为 1;

SEQ序列号(sequence number):字段长 32 位,占4个字节,序号的范围为 [0,4284967296]。

1.由于TCP是面向字节流的,在一个TCP连接中传送的字节流中的每一个字节都按顺序编号
2.首部中的序号字段则是指本报文段所发送的数据的第一个字节的序号,这是随机生成的。
3.序号是循环使用的,当序号增加到最大值时,下一个序号就又回到了0

x随机序号

回复了 ack=x+1, 这是确认序号

三次握手(建立连接)

1.客户端发送 SYN=1 包(seq(sequence number 序号)=x)到服务器,进入 SYN_SENT 状态。

2.服务器收到 ACK=1,ack=x+1,接是双向的,所以第二个数据包也是服务器请求数据包建立连接,SYN=1,seq=y,进入 SYN_RCVD 状态。

(x: 客户端生成的随机序号
1: 客户端给服务器发送的数据的量, SYN标志位存储到某一个字节中, 因此按照一个字节计算,表示客户端给服务器发送的1个字节服务器收到了。)

3.客户端收到 SYN+ACK,回复 ACK 包(ack=y+1),双方进入 ESTABLISHED 状态。

(y:服务器端生成的随机序号
1:服务器给客户端发送的数据量,服务器给客户端发送了ACK和SYN, 都存储在这一个字节中)

四次挥手(断开连接)

4.客户端发送 FIN =1包(seq=u),进入 FIN_WAIT_1 状态。

5.服务器收到 FIN,回复 ACK 包(ack=u+1),进入 CLOSE_WAIT 状态,客户端进入 FIN_WAIT2 状态。

6.服务器发送完数据后,发送 FIN 包(seq=v),进入 LAST_ACK 状态。

7.客户端收到 FIN,回复 ACK 包(ack=v+1),进入 TIME_WAIT 状态,服务器收到 ACK 后进入 CLOSED 状态。

三握四挥

15.编写epoll多路复用TCP服务器代码(完整代码)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <errno.h>

#define PORT 8888
#define MAX_EVENTS 1024
#define BUFFER_SIZE 1024

// 设置文件描述符为非阻塞模式
void set_nonblocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}   

int main() {
    int listen_fd, epoll_fd;
    struct sockaddr_in server_addr;

    // 创建监听套接字
    listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_fd < 0) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 设置地址复用
    int opt = 1;
    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    // 配置服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(PORT);

    // 绑定地址
    if (bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind");
        close(listen_fd);
        exit(EXIT_FAILURE);
    }

    // 监听
    if (listen(listen_fd, 128) < 0) {
        perror("listen");
        close(listen_fd);
        exit(EXIT_FAILURE);
    }

    // 设置监听套接字为非阻塞模式
    set_nonblocking(listen_fd);

    // 创建 epoll 实例
    epoll_fd = epoll_create1(0);
    if (epoll_fd < 0) {
        perror("epoll_create1");
        close(listen_fd);
        exit(EXIT_FAILURE);
    }

    // 注册监听套接字到 epoll
    struct epoll_event event, events[MAX_EVENTS];
    event.events = EPOLLIN;
    event.data.fd = listen_fd;
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &event) < 0) {
        perror("epoll_ctl");
        close(listen_fd);
        close(epoll_fd);
        exit(EXIT_FAILURE);
    }

    printf("Server listening on port %d...\n", PORT);

    // 事件循环
    while (1) {
        int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        if (nfds < 0) {
            if (errno == EINTR) continue; // 忽略信号中断
            perror("epoll_wait");
            break;
        }

        for (int i = 0; i < nfds; i++) {
            int fd = events[i].data.fd;

            // 处理新连接
            if (fd == listen_fd) {
                struct sockaddr_in client_addr;
                socklen_t client_len = sizeof(client_addr);
                int client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_len);
                if (client_fd < 0) {
                    perror("accept");
                    continue;
                }

                printf("New connection: %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

                // 设置客户端套接字为非阻塞模式
                set_nonblocking(client_fd);

                // 将客户端套接字加入 epoll
                event.events = EPOLLIN | EPOLLET; // 边缘触发
                event.data.fd = client_fd;
                if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event) < 0) {
                    perror("epoll_ctl add client");
                    close(client_fd);
                }
            }
            // 处理客户端数据
            else {
                char buf[BUFFER_SIZE];
                while (1) {
                    ssize_t n = recv(fd, buf, sizeof(buf) - 1, 0);
                    if (n < 0) {
                        if (errno == EAGAIN || errno == EWOULDBLOCK) {
                            // 数据读取完毕
                            break;
                        } else {
                            perror("recv");
                            epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL);
                            close(fd);
                            break;
                        }
                    } else if (n == 0) {
                        // 客户端断开连接
                        printf("Client disconnected, fd=%d\n", fd);
                        epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL);
                        close(fd);
                        break;
                    } else {
                        // 回显数据
                        buf[n] = '\0';
                        printf("Recv from fd=%d: %s\n", fd, buf);
                        send(fd, buf, n, 0);
                    }
                }
            }
        }
    }

    close(listen_fd);
    close(epoll_fd);
    return 0;
}

10.简述任意两种并发服务器的设计,并说明其优缺点以及容易出现的问题

多进程服务器:

原理:每来一个连接,fork()一个子进程处理。

优点:每个进程独立解决通信问题,性能高,可以进行并发(进程隔离,一个连接崩溃不影响其他连接)。

缺点:进程占用空间大,对服务器内存和性能要求高,回收进程资源麻烦。(进程创建 / 切换开销大,连接数多时性能差)。

问题:僵尸进程、资源泄漏

多线程服务器:

原理:每来一个连接,创建一个线程处理。

优点:线程开销比进程小,上下文切换快。

缺点:线程共享地址空间,一个线程崩溃会导致整个进程退出;需处理同步问题。

问题:死锁、数据竞争、条件竞争

11.简述三种IO多路复用方法以及各自的特点

select: 特点:支持跨平台,监听的文件描述符数量有限(默认 1024),每次调用需遍历所有描述符,效率低。

poll: 特点:无文件描述符数量限制,但同样需要遍历,效率和 select 相近。

epoll: 特点:Linux 独有,基于事件驱动,仅返回就绪的文件描述符,支持水平 / 边缘触发,高并发场景效率极高。

7.说出TCP/IP四层协议族,以及每层有哪些协议

网络接口层:以太网、PPP、ARP、RARP

网络层:IP、ICMP、IGMP

传输层:TCP、UDP

应用层:HTTP、FTP、SMTP、DNS、Telnet、SSH

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐