腾讯一面
-
自我介绍
-
TCP三次握手,四次挥手过程
-
首先Client端发送连接请求报文,Server段接受连接后回复ACK报文,并为这次连接分配资源。Client端接收到ACK报文后也向Server段发生ACK报文,并分配资源,这样TCP连接就建立了。
-
总结三次握手过程:
- 第一次握手:起初两端都处于CLOSED关闭状态,Client将标志位SYN置为1,随机产生一个值seq=x,并将该数据包发送给Server,Client进入SYN-SENT状态,等待Server确认;
- 第二次握手:Server收到数据包后由标志位SYN=1得知Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=x+1,随机产生一个值seq=y,并将该数据包发送给Client以确认连接请求,Server进入SYN-RCVD状态,此时操作系统为该TCP连接分配TCP缓存和变量;
- 第三次握手:Client收到确认后,检查ack是否为x+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=y+1,并且此时操作系统为该TCP连接分配TCP缓存和变量,并将该数据包发送给Server,Server检查ack是否为y+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client和Server就可以开始传输数据。
(3)为什么A还要发送一次确认呢?可以二次握手吗?
答:主要为了防止已失效的连接请求报文段突然又传送到了B,因而产生错误。如A发出连接请求,但因连接请求报文丢失而未收到确认,于是A再重传一次连接请求。后来收到了确认,建立了连接。数据传输完毕后,就释放了连接,A工发出了两个连接请求报文段,其中第一个丢失,第二个到达了B,但是第一个丢失的报文段只是在某些网络结点长时间滞留了,延误到连接释放以后的某个时间才到达B,此时B误认为A又发出一次新的连接请求,于是就向A发出确认报文段,同意建立连接,不采用三次握手,只要B发出确认,就建立新的连接了,此时A不理睬B的确认且不发送数据,则B一致等待A发送数据,浪费资源
4)Server端易受到SYN攻击?
服务器端的资源分配是在二次握手时分配的,而客户端的资源是在完成三次握手时分配的,所以服务器容易受到SYN洪泛攻击,SYN攻击就是Client在短时间内伪造大量不存在的IP地址,并向Server不断地发送SYN包,Server则回复确认包,并等待Client确认,由于源地址不存在,因此Server需要不断重发直至超时,这些伪造的SYN包将长时间占用未连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络拥塞甚至系统瘫痪。
防范SYN攻击措施:降低主机的等待时间使主机尽快的释放半连接的占用,短时间受到某IP的重复SYN则丢弃后续请求。
2、四次挥手
(1)四次挥手的详述
假设Client端发起中断连接请求,也就是发送FIN报文。Server端接到FIN报文后,意思是说"我Client端没有数据要发给你了",但是如果你还有数据没有发送完成,则不必急着关闭Socket,可以继续发送数据。所以你先发送ACK,“告诉Client端,你的请求我收到了,但是我还没准备好,请继续你等我的消息”。这个时候Client端就进入FIN_WAIT状态,继续等待Server端的FIN报文。当Server端确定数据已发送完成,则向Client端发送FIN报文,“告诉Client端,好了,我这边数据发完了,准备好关闭连接了”。Client端收到FIN报文后,"就知道可以关闭连接了,但是他还是不相信网络,怕Server端不知道要关闭,所以发送ACK后进入TIME_WAIT状态,如果Server端没有收到ACK则可以重传。“,Server端收到ACK后,“就知道可以断开连接了”。Client端等待了2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,我Client端也可以关闭连接了。Ok,TCP连接就这样关闭了!
总结
- 1)A的应用进程先向其TCP发出连接释放报文段(FIN=1,序号seq=u),并停止再发送数据,主动关闭TCP连接,进入FIN-WAIT-1(终止等待1)状态,等待B的确认。
- 2)B收到连接释放报文段后即发出确认报文段,(ACK=1,确认号ack=u+1,序号seq=v),B进入CLOSE-WAIT(关闭等待)状态,此时的TCP处于半关闭状态,A到B的连接释放。
- 3)A收到B的确认后,进入FIN-WAIT-2(终止等待2)状态,等待B发出的连接释放报文段。
- 4)B没有要向A发出的数据,B发出连接释放报文段(**FIN=1,ACK=1,序号seq=w,确认号ack=u+1),**B进入LAST-ACK(最后确认)状态,等待A的确认。
- 5)A收到B的连接释放报文段后,对此发出确认报文段(ACK=1,seq=u+1,ack=w+1),A进入TIME-WAIT(时间等待)状态。此时TCP未释放掉,需要经过时间等待计时器设置的时间2MSL后,A才进入CLOSED状态。
3)为什么A在TIME-WAIT状态必须等待2MSL的时间?
MSL最长报文段寿命Maximum Segment Lifetime,MSL=2
答: 两个理由:保证**A****发送的最后一个ACK报文段能够到达B。2)防止“已失效的连接请求报文段”出现在本连接中。
- 1)这个ACK报文段有可能丢失,使得处于LAST-ACK状态的B收不到对已发送的FIN+ACK报文段的确认,B超时重传FIN+ACK报文段,而A能在2MSL时间内收到这个重传的FIN+ACK报文段,接着A重传一次确认,重新启动2MSL计时器,最后A和B都进入到CLOSED状态,若A在TIME-WAIT状态不等待一段时间,而是发送完ACK报文段后立即释放连接,则无法收到B重传的FIN+ACK报文段,所以不会再发送一次确认报文段,则B无法正常进入到CLOSED状态。
- 2)A在发送完最后一个ACK报文段后,再经过2MSL,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失,使下一个新的连接中不会出现这种旧的连接请求报文段。
(4)为什么连接的时候是三次握手,关闭的时候却是四次握手?
答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,“你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
(5)为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?
答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。
-
-
TCP,UDP区别
- 综述
- TCP 是面向连接的,UDP 是面向无连接的
- UDP程序结构较简单
- TCP 是面向字节流的,UDP 是基于数据报的
- TCP 保证数据正确性,UDP 可能丢包
- TCP 保证数据顺序,UDP 不保证
2 udp:
-
TCP 和 UDP 是传输层的两个协议
我们来看一下 UDP 的包头
UDP 除了端口号,基本啥都没有了。如果没有这两个端口号,数据就不知道该发给哪个应用。
所以 UDP 就像一个小孩子,特别简单,有如下三个特点
UDP 的特点
沟通简单,不需要大量的数据结构,处理逻辑和包头字段
轻信他人。它不会建立连接,但是会监听这个地方,谁都可以传给它数据,它也可以传给任何人数据,甚至可以同时传给多个人数据。
愣头青,做事不懂变通。不会根据网络的情况进行拥塞控制,无论是否丢包,它该怎么发还是怎么发
因为 UDP 是"小孩子",所以处理的是一些没那么难的项目,并且就算失败的也能接收。基于这些特点的话,UDP 可以使用在如下场景中UDP 的主要应用场景
需要资源少,网络情况稳定的内网,或者对于丢包不敏感的应用,比如 DHCP 就是基于 UDP 协议的。
不需要一对一沟通,建立连接,而是可以广播的应用。因为它不面向连接,所以可以做到一对多,承担广播或者多播的协议。
需要处理速度快,可以容忍丢包,但是即使网络拥塞,也毫不退缩,一往无前的时候
基于 UDP 的几个例子直播。直播对实时性的要求比较高,宁可丢包,也不要卡顿的,所以很多直播应用都基于 UDP 实现了自己的视频传输协议
实时游戏。游戏的特点也是实时性比较高,在这种情况下,采用自定义的可靠的 UDP 协议,自定义重传策略,能够把产生的延迟降到最低,减少网络问题对游戏造成的影响
物联网。一方面,物联网领域中断资源少,很可能知识个很小的嵌入式系统,而维护 TCP 协议的代价太大了;另一方面,物联网对实时性的要求也特别高。比如 Google 旗下的 Nest 简历 Thread Group,推出了物联网通信协议 Thread,就是基于 UDP 协议的
- 综述
-
OS你都熟悉什么
- 是管理计算机硬件与软件资源的计算机程序。操作系统需要处理如管理与配置内存、决定系统资源供需的优先次序、控制输入设备与输出设备、操作网络与管理文件系统等基本事务。操作系统也提供一个让用户与系统交互的操作界面。
- 并发就是在一段时间内,多个任务都会被处理
- 并行就是在同一时刻,有多个任务在执行
- 分时系统(Sharing time system)就是系统把CPU时间分成很短的时间片,轮流地分配给多个作业。它的优点就是对多个用户的多个作业都能保证足够快的响应时间,并且有效提高了资源的利用率。
实时系统(Real-time system)是系统对外部输入的信息,能够在规定的时间内(截止期限)处理完毕并做出反应。它的优点是能够集中地及时地处理并作出反应,高可靠性,安全性。
通常计算机采用的是分时,就是多个进程/用户之间共享CPU,从形势上实现多任务。各个用户/进程之间的调度并非精准度特别高,如果一个进程被锁住,可以给它分配更多的时间。而实时操作系统则不同,软件和硬件必须遵从严格的时间限制,超过时限的进程可能直接被终止。在这样的操作系统中,每次加锁都需要仔细考虑。
-
进程,线程,协程的区别
-
进程是运行起来的程序,也是系统进行资源分配和调度的一个独立单位。每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。不同进程间数据共享比较麻烦,由于进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全。
-
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。
-
协程是一种用户态的轻量级线程,其实也是一种特使的函数,可以用户自定的在不同的协程函数中跳转,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
-
区别
-
进程内至少有一个线程,它们共享进程的地址空间,而进程有自己独立的地址空间
-
-
一个线程可以多个协程,一个进程也可以单独拥有多个协程,这样python中则能使用多核CPU。
-
线程进程都是同步机制,而协程则是异步
-
协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态
-
5.进程和线程、协程在python中的使用
- 多进程一般使用multiprocessing库,来利用多核CPU,主要是用在CPU密集型的程序上,当然生产者消费者这种也可以使用。多进程的优势就是一个子进程崩溃并不会影响其他子进程和主进程的运行,但缺点就是不能一次性启动太多进程会严重影响系统的资源调度
- 多线程一般是使用threading库,完成一些IO密集型并发操作。多线程的优势是切换快,资源消耗低,但一个线程挂掉则会影响到所有线程,所以不够稳定。现实中使用线程池的场景会比较多
- 协程一般是使用gevent库,当然这个库用起来比较麻烦,所以使用的并不是很多,在工作中使用asyncio比较多一点
- 总结一下就是IO密集型一般使用多线程或者多进程,CPU密集型一般使用多进程,强调非阻塞异步并发的一般都是使用协程,当然有时候也是需要多进程线程池结合的,或者是其他组合方式
-
-
-
为什么协程运行时其他协程会被阻塞,一次只能运行一个?
- 线程内的多个协程是串行的。即线程下的某个协程在运行时,其他协程必然是挂起
-
磁盘调度算法有哪些?优化方向是什么?
- 访问磁盘的时间因子由3部分构成,它们是查找(查找磁道)时间、等待(旋转等待扇区)时间和数据传输时间,其中查找时间是决定因素。因此,磁盘调度算法先考虑优化查找策略,需要时再优化旋转等待策略。
- 先来先服务(FCFS)调度:按先来后到次序服务,未作优化。
- 最短寻找时间优先调度算法(SSTF) :总是从等待访问者中挑选寻找时间最短的那个请求先执行的,而不管访问者到来的先后次序。
- 电梯调度算法(SCAN)。SCAN算法是磁头前进方向上的最短查找时间优先算法,它排除了磁头在盘面局部位置上的往复移动,SCAN算法在很大程度上消除了SSTF算法的不公平性,但仍有利于对中间磁道的请求。
- 循环扫描算法(CSCAN):不考虑访问者等待的先后次序,总是从0号柱面开始向里道扫描,按照各自所要访问的柱面位置的次序去选择访问者
- FSCAN算法实质上是N步SCAN算法的简化,即FSCAN只将磁盘请求队列分成两个子队列。一个是由当前所有请求磁盘I/O的进程形成的队列,由磁盘调度按SCAN算法进行处理。在扫描期间,将新出现的所有请求磁盘I/O的进程,放入另一个等待处理的请求队列。这样,所有的新请求都将被推迟到下一次扫描时处理。
-
OS内存分配算法
-
OSI七层模型说一下
-
ICMP,IGMP协议的作用,应用场景
- CMP协议主要用来检测网络通信故障和实现链路追踪,最典型的应用就是PING和tracerooute。
- ping通过发送回送请求报文和回送回答报文来检测源主机到目的主机的链路是否有问题,目的地是否可达,以及通信的延迟情况。
- traceroute:通过发送探测报文来获取链路地址信息
-
ARP协议的原理
- 首先,每台主机都会在自己的ARP缓冲区中建立一个 ARP列表,以表示IP地址和MAC地址的对应关系。当源主机需要将一个数据包要发送到目的主机时,会首先检查自己 ARP列表中是否存在该 IP地址对应的MAC地址,如果有,就直接将数据包发送到这个MAC地址;如果没有,就向本地网段发起一个ARP请求的广播包,查询此目的主机对应的MAC地址。此ARP请求数据包里包括源主机的IP地址、硬件地址、以及目的主机的IP地址。网络中所有的主机收到这个ARP请求后,会检查数据包中的目的IP是否和自己的IP地址一致。如果不相同就忽略此数据包;如果相同,该主机首先将发送端的MAC地址和IP地址添加到自己的ARP列表中,如果ARP表中已经存在该IP的信息,则将其覆盖,然后给源主机发送一个 ARP响应数据包,告诉对方自己是它需要查找的MAC地址;源主机收到这个ARP响应数据包后,将得到的目的主机的IP地址和MAC地址添加到自己的ARP列表中,并利用此信息开始数据的传输。如果源主机一直没有收到ARP响应数据包,表示ARP查询失败。
-
堆和栈的区别是什么
1、栈由系统自动分配,而堆是人为申请开辟;
2、栈获得的空间较小,而堆获得的空间较大;
3、栈由系统自动分配,速度较快,而堆一般速度比较慢;
4、栈是连续的空间,而堆是不连续的空间。
-
linux你常用的命令有哪些?
- ps -ef|grep tomcat (查询tomcat进程)
- env是一个shell命令,用于打印当前环境变量的列表
- tail命令显示文件的最后部分 tail -n 100 / var / log / httpd / access_log 查看最后100行的日志文件
- 使用此grep命令搜索日志文件,特定进程等。$ cat tomcat.log | grep org.apache.catalina.startup.Catalina.start
- 使用此top命令来确定正在运行的进程以及它们消耗了多少内存和CPU
- netstat命令显示网络状态。此netstat命令显示正在使用的网络端口及其传入连接。
- 命令ls列出了与您的应用程序关联的打开文件
- du命令用于检索有关哪些文件使用目录中磁盘空间的更多详细信息。
- iptables命令阻止或允许Linux主机上的流量,类似于网络防火墙。此iptables命令可能会阻止某些应用程序接收或传输请求。
- 推送命令将当前目录放到堆栈上,以便您可以弹出它 pushed
- sudo fuser -k 8000 / tcp 使用此命令可以通过一个端口杀死程序
- namei -l /path/to/file.txt 检查每个目录对文件的权限 检测权限错误很有用,例如在配置Web服务器时
- comm file1 file2 将两个已排序文件中的两行合并
-
除过top命令,如何查看linux系统内存使用情况?
- cat /proc/meminfo
- sudo atop
- free -h
- htop命令显示了每个进程的内存实时使用率,它提供了所有进程的常驻内存大小、程序总内存大小、共享库大小等的报告。列表可以水平及垂直滚动。
-
git你都用过哪些命令?
分支切换(checkout)
切换到master分支
git checkout master
从远端拉取更新(pull)
从当前分支拉取远端更新
git pull
合并代码(merge)
合并master分支代码到当前分支,先切换到当前分支
git merge master -
git中分支和target有什么区别?
-
场景题:有29层楼,2个玻璃珠子,从楼上将玻璃珠抛下(玻璃珠会碎),找出玻璃珠碎与不碎的临界点楼层
-
手撕:手写建一个二叉树, 并进行先中后遍历
package bbb;
public class TreeNode {
private TreeNode left;
private TreeNode right;
private char data;
public TreeNode(char data,TreeNode left, TreeNode right) {
super();
this.left = left;
this.right = right;
this.data = data;
}
public TreeNode(char data) {
super();
this.data = data;
}
public TreeNode getLeft() {
return left;
}
public void setLeft(TreeNode left) {
this.left = left;
}
public TreeNode getRight() {
return right;
}
public void setRight(TreeNode right) {
this.right = right;
}
public char getData() {
return data;
}
public void setData(char data) {
this.data = data;
}
class Bintree {
public TreeNode initTree(){
TreeNode A=new TreeNode('A');
TreeNode B=new TreeNode('B',A,null);
TreeNode C=new TreeNode('C');
TreeNode D=new TreeNode('D',B,C);
TreeNode E=new TreeNode('E');
TreeNode F=new TreeNode('F',E,null);
TreeNode G=new TreeNode('G',null,F);
TreeNode H=new TreeNode('H',D,G);
return H;
}
public void visitNode(TreeNode node){
System.out.print(node.getData()+" ");
}
//先序遍历
public void preOrder(TreeNode t){
if(t!=null){
visitNode(t);
preOrder(t.getLeft());
preOrder(t.getRight());
}
}
//中序遍历
public void midOrder(TreeNode t){
if(t!=null){
midOrder(t.getLeft());
visitNode(t);
midOrder(t.getRight());
}
}
//后续遍历
public void tailOrder(TreeNode t){
if(t!=null){
tailOrder(t.getLeft());
tailOrder(t.getRight());
visitNode(t);
}
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Bintree myTree=new Bintree();
TreeNode t=myTree.initTree();
myTree.preOrder(t);
System.out.println();
myTree.midOrder(t);
System.out.println();
myTree.tailOrder(t);
}