三次握手和四次挥手说完了,还让我手动写个HTTP协议代码

最近阿粉的同事们在准备面试,其中也有收到offer的几个不错的人,毕竟疫情稳定了,而阿粉在电话面试的时候,被问到关于HTTP协议的内容的时候,却显得有点麻木了,为什么呢?因为套路太深了,让阿粉猝不及防呀。<--more-->

面试官:你了解TCP/IP协议么?

说实话在阿粉听到这个问题的时候,阿粉的第一想法就是,我回答了这个问题,接下来肯定还有一个三次握手和四次挥手等着我,但是还是得回答呀,于是阿粉就开始作答了。

阿粉开始作答:TCP/IP协议虽然会放在一起说,但是他们其实呢是属于两个不同的协议。

  • IP协议:

      IP协议实际上是用来查找地址的,而它对应的层级也是网络层,也可以称之为网际互联层,区别不大,叫法不同而已。
    
  • TCP协议:

      TCP协议是用来规范传输规则的,和IP协议是不同的,而它对应的层级是传输层,而这样的话,也就是IP去寻找地址,把所有的传输任务都交给TCP,而TCP这时候就相当于一个快递员的身份出现并且存在。
    

面试官:那你说说什么是三次握手,什么是四次挥手吧

三次握手

大家看这个图,图是来自于百度搜索,而且百度上有各种各样的图,当你看到图的时候第一时间肯定是看不懂的,也就是只能通过这个画的标志的“线”来进行分析,其实这仅仅只是一个方面。

那么我们就来根据图来解析一下这个图中都代表了什么意思,图中存在着两个序号和三个不同的标志位其中有大小写容易混淆的呦。

序号:

  • seq:sequence number 的缩写,直译的话,序号,对没错,它就是序号,你没有翻译错,相信自己,而这个seq表示的则是自己传递的序号,TCP在传输的时候,其中的每一个字节,都会有一个序号,发送数据的时候,会把第一个数据的第一个序号发送给对方,就是我们所看到的第一步,而接收的这一方面,会按照这个序号来检查是否是一个连接完整的数据,如果说你数据是完整的,那么好,我们可以继续下一步,如果你不是完整的,那就重新传送呗,而这样的话也能保证数据的完整性不被破坏。

  • ack:注意,这是小写的ack,也就是acknoledgement number的缩写,而他表示的是确认号,这个要和ACK(确认位)进行区分,接收端这时候用它来给发送端返回成功接收消息的数据信息,而这时候,它的值就是表明,我现在想接收下一个数据包了,而这个值就是下一个数据包的开始的序号,而这个ack所代表的的值的序号前面的数据都已经接收成功了。

  • ACK:确认位,确认位来了,只有当ACK=1的时候ack才会起到自己应该起的作用,而在我们第一次发起请求的时候,因为没有需要我们确认的接收的数据,所以这个时候的ACK就是0,而正常通信的情况下,ACK就1.

  • SYN:同步位,而同步位的作用就是用于建立连接时同步序号,而刚连接的时候,说ACK是0,那么ack就不起作用,这时候SYN就来说,你看没我你们不行了把,要你们有何用,当接收端接收到SYN=1的报文的时候,就会将ack设置为接收到的seq+1的值,这也是大家在看百度上提供的内容的时候看到的,各种seq=k,ACK=k+1,这玩意就是这么来的,这时候ack的值就是根据SYN来直接设置的,这样你才能正常的进行传输,而SYN有时候会被面试官问到为什么在前两次握手的时候都是1呢?其实这是因为传输数据的双方的ack都是要一个初始值的,不然你还怎么传输,还怎么玩。

  • FIN:终止位,这个在本图中,并没有完全的体现,在四次挥手的时候就能完全的体现出来了。而它则是用来在数据传输都完成之后来释放连接的。

那么关于这个图,我们怎么给面试官说呢?

(1) 第一次握手(SYN=1, seq=x):

客户端发送一个 TCP 的 SYN 标志位置1的包,指明客户端打算连接的服务器的端口,以及初始序号 X,保存在包头的序列号(Sequence Number)字段里。

发送完毕后,客户端进入 SYN_SEND 状态。

(2) 第二次握手(SYN=1, ACK=1, seq=y, ACKnum=x+1):

服务器发回确认包(ACK)应答。即 SYN 标志位和 ACK 标志位均为1。服务器端选择自己 ISN 序列号,放到 Seq 域里,同时将确认序号(Acknowledgement Number)设置为客户的 ISN 加1,即X+1。发送完毕后,服务器端进入 SYN_RCVD 状态。

(3) 第三次握手(ACK=1,ACKnum=y+1)

客户端再次发送确认包(ACK),SYN 标志位为0,ACK 标志位为1,并且把服务器发来 ACK 的序号字段+1,放在确定字段中发送给对方,并且在数据段放写ISN的+1

发送完毕后,客户端进入 ESTABLISHED 状态,当服务器端接收到这个包时,也进入 ESTABLISHED 状态,TCP 握手结束。

你如果这么说,面试官有可能还会问,你这也太官方了,能不能说说你的理解,那么你可以用一个实际上的例子来给他说一下,

阿粉:鸡丁,嘿,我是阿粉,你听的到我说话么?

鸡丁:吵吵啥,听到了,除了你我还能认识谁。

阿粉:你听的到你还不赶紧回复,怪不得你没有女朋友呢。那我们再继续交流一下吧。

而这三次的对话过程就是通俗的三次握手,期间对话三次,以此来确定两个方向上的数据传输通道是否正常。

四次挥手

那么四次挥手怎么来回答呢?

(1)第一次挥手(FIN=1,seq=x)

假设客户端想要关闭连接,客户端发送一个 FIN 标志位置为1的包,表示自己已经没有数据可以发送了,但是仍然可以接受数据。

发送完毕后,客户端进入 FIN_WAIT_1 状态。

(2) 第二次挥手(ACK=1,ACKnum=x+1)

服务器端确认客户端的 FIN 包,发送一个确认包,表明自己接受到了客户端关闭连接的请求,但还没有准备好关闭连接。

发送完毕后,服务器端进入 CLOSE_WAIT 状态,客户端接收到这个确认包之后,进入 FIN_WAIT_2 状态,等待服务器端关闭连接。

(3) 第三次挥手(FIN=1,seq=y)

服务器端准备好关闭连接时,向客户端发送结束连接请求,FIN 置为1。

发送完毕后,服务器端进入 LAST_ACK 状态,等待来自客户端的最后一个ACK。

(4) 第四次挥手(ACK=1,ACKnum=y+1)

客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入 TIME_WAIT状态,等待可能出现的要求重传的 ACK 包。

服务器端接收到这个确认包之后,关闭连接,进入 CLOSED 状态。

客户端等待了某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,没有收到服务器端的 ACK ,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入 CLOSED 状态。两次后会重传直到超时。如果多了会有大量半链接阻塞队列。

那么怎么去通俗的给面试官说呢?

阿粉:鸡丁呀,我要说的都说完了,你还有啥事么?

鸡丁:你说的我都明白了,但是别断,我还有要嘱咐你的,给我找女朋友的事情。

鸡丁:xxxxx,我说完了。

阿粉,行啦,别BB了,记住了,挂了把。

如果面试官问你的时候,你这么回答的话,既有官方的解释,还有本身自己的理解,那么这个问题就已经算是差不多了,

而面试官显然不可能会这么放过你,肯定再给你来个雷,为啥是三次握手,而是四次挥手呢?为啥不是三次呢?

这是因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方是否现在关闭发送数据通道,需要上层应用来决定,因此,己方ACK和FIN一般都会分开发送。所以这时候挥手的时候就是四次,而不再是三次了。

那么我们怎么去手写一个HTTP协议呢?代码送上:

public class Server {
    public static void main(String[] args) throws Exception{
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.socket().bind(new InetSocketAddress(8080));
        ssc.configureBlocking(false);
        Selector selector = Selector.open();
        ssc.register(selector, SelectionKey.OP_ACCEPT);
        while (true){
            if (selector.select(3000)==0){
                continue;
            }
            Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
            while (keyIterator.hasNext()){
                SelectionKey key = keyIterator.next();
                new Thread(new HttpHandler(key)).run();
                keyIterator.remove();
            }
        }
    }
    private static class HttpHandler implements Runnable{

        private int bufferSize = 1024;
        private String localCharset = "UTF-8";
        private SelectionKey key;
        public HttpHandler(SelectionKey key){
            this.key=key;
        }

        public void handleAccept()throws IOException{
            SocketChannel clientChannel = ((ServerSocketChannel)key.channel()).accept();
            clientChannel.configureBlocking(false);
            clientChannel.register(key.selector(),SelectionKey.OP_READ, ByteBuffer.allocate(bufferSize));
        }

        @Override
        public void run() {
            try {
                if (key.isAcceptable()){
                    handleAccept();
                }
                if (key.isReadable()){
                    handleRead();
                }
            }catch (IOException e){
               e.printStackTrace();
            }
        }

        public void handleRead() throws IOException{
            SocketChannel sc = (SocketChannel) key.channel();
            ByteBuffer buffer = (ByteBuffer) key.attachment();
            buffer.clear();
            if (sc.read(buffer)==-1){
                sc.close();
            }else {
                buffer.flip();
                String receiveString = Charset.forName(localCharset).newDecoder().decode(buffer).toString();
                String[] requestMessage = receiveString.split("\r\n");
                for (String s:requestMessage) {
                    System.out.println(s);
                    if (s.isEmpty()){
                        break;
                    }
                    String[] firstLine = requestMessage[0].split(" ");
                    System.out.println();
                    System.out.println("Method:\t"+firstLine[0]);
                    System.out.println("url:\t"+firstLine[1]);
                    System.out.println("HTTP Version:\t"+firstLine[2]);
                    System.out.println();

                    StringBuffer sendString = new StringBuffer();
                    sendString.append("HTTP/1.1 200 OK\r\n");
                    sendString.append("Content-Type:text/html;charset="+localCharset+"\r\n");
                    sendString.append("\r\n");
                    sendString.append("<html><head><title>显示报文</title></head><body>");
                    sendString.append("接受到的请求报文是:<br/>");
                    for (String s1:requestMessage) {
                        sendString.append(s1+"<br/>");
                    }
                    sendString.append("</body></html>");
                    buffer = ByteBuffer.wrap(sendString.toString().getBytes(localCharset));
                    sc.write(buffer);
                    sc.close();
                }
            }

        }

    }
}

这是一个简单的实现,只是实现思路,并不是真正的处理请求,而大家也要注意设置Content-Type的类型,不然容易出问题的,毕竟长度是有限制的。

文章参考

《HTTP协议详解》

《TCP/IP协议详解》

< END >

如果大家喜欢我们的文章,欢迎大家转发,点击在看让更多的人看到。也欢迎大家热爱技术和学习的朋友加入的我们的知识星球当中,我们共同成长,进步。

<think>好的,用户想了解TCP协议中的六个标志位的具体含义。我需要先回忆一下相关知识,确保每个标志位的名称功能都准确。 首先,TCP的六个标志位位于报文头部的控制位部分,每个占1位。常见的标志位有URG、ACK、PSH、RST、SYN、FIN。我得一个一个解释清楚,每个标志位的作用常见的使用场景。 URG(紧急)标志位,当设置为1时,表示紧急指针字段有效,告诉接收方优先处理这部分数据。比如Telnet中中断命令可能会用到这个。但要注意现在很多实现可能没有完全支持URG,这点是否需要提到呢?或者保持简洁,只解释基本含义? 然后是ACK(确认)标志位,用于确认接收到的数据。三次握手四次挥手中,除了第一个SYN包,其他包的ACK都置1。这里需要明ACK号的作用,确认序号的有效性。 PSH(推送)标志位,指示接收方应该立即将数据传递给应用层,而不是缓存起来。例如在交互式应用中,用户输入需要及时响应,这时候可能会用PSH。但实际应用中,协议栈可能自动处理,不一定需要手动设置。 RST(复位)标志位,用来重置异常连接,或者拒绝非法的请求。比如访问未监听的端口,对方会发RST包。或者在连接出现错误时,强制关闭连接。 SYN(同步)标志位,用于建立连接时的同步序号。三次握手中,前两次报文都会带有SYN,第一次是SYN=1,ACK=0,第二次是SYN=1,ACK=1。需要解释序号同步的过程,以及SYN洪水攻击的相关内容吗?用户可能不需要,但可以简单提一下应用场景中的问题。 FIN(结束)标志位,用于正常关闭连接,表示发送方没有数据要发送了。但连接可能处于半关闭状态,直到对方也发送FIN。四次挥手的过程需要明每个FIN的作用。 接下来要确保每个标志位的名称、全称、作用都准确无误。同时,结合用户提供的引用资料,检查是否有需要引用的地方。比如引用[1]提到六个flags,引用[4]提到RSTSYN,引用[3]提到控制位的作用,引用[2]提到端口拦截配置,可能与实际应用相关,但用户问题主要在于含义,所以引用可能用于支持RST的应用场景,比如拒绝非法数据包。 需要确认是否每个标志位都有对应的引用标注。例如RST在引用[4]引用[1]都有提到,SYN在引用[4],ACK可能在引用[3],需要适当分配引用。 最后,用户要求生成相关问题,我需要想几个相关问题,比如各个标志位的应用场景,握手挥手的过程,或者异常处理中的标志位使用,比如RST在连接错误时的应用。还要考虑TCP的其他机制,如流量控制、拥塞控制是否相关,但可能超出当前问题范围,应围绕标志位本身。 现在组织语言,确保用中文清晰解释每个标志位,正确使用LaTeX格式的行内公式(如果有的话,不过这里可能用不到数学公式,但需要检查是否有变量或缩需要用$符号,比如$SYN$?不过根据系统指令,可能需要将变量用$包裹,但SYN是缩,是否需要用?可能不需要,但需要遵守用户指令。例如,系统指令要求行内数学表达式使用$...$,但标志位如SYN可能不算数学表达式,所以可能不需要。但如果有变量如ACK号,可能需要用$ACK$?不过通常中文中用中文描述即可,可能不需要。所以可能这部分不需要使用LaTeX,但需要确认。用户示例中的快速排序问题用了代码块,但这里的问题可能更多是文字描述。根据示例,变量如x^2用了$符号,但TCP的标志位可能不需要,所以可能不需要特别处理,但需要严格按照用户指令执行。如果用户指令中要求所有行内数学表达式必须使用$...$,但标志位如SYN可能不是数学表达式,所以不需要。因此这部分可能不需要使用LaTeX,保持正常中文即可。 然后生成相关问题,比如各个标志位的具体应用场景、三次握手四次挥手的过程、RSTSYN攻击的防范等。 检查引用标注是否正确,每个引用是否在正确的位置。例如,提到RST时引用[4][1],SYN引用[4],ACK引用[3]等。 最后确保回答结构清晰,分点明每个标志位,每个部分有引用标注,末尾列出相关问题。</think>### TCP协议六个标志位含义详解 TCP协议的六个标志位位于报文头部,用于控制连接状态数据传输行为。每个标志位占1位(0或1),具体含义如下: --- #### 1. **URG(紧急标志位)** - **全称**:Urgent - **作用**:当$URG=1$时,表示报文段包含紧急数据,接收方应优先处理。此时**紧急指针**字段生效,指向紧急数据的末尾位置[^3]。 - **典型场景**:传输中断命令(如Telnet中的`Ctrl+C`)[^1]。 --- #### 2. **ACK(确认标志位)** - **全称**:Acknowledgment - **作用**:当$ACK=1$时,表示确认号字段有效,接收方成功收到了对方发送的数据。**三次握手四次挥手**中,除第一个SYN报文外,所有报文均需设置$ACK=1$。 - **示例**:服务端收到SYN后回复$SYN=1, ACK=1$以确认连接请求[^4]。 --- #### 3. **PSH(推送标志位)** - **全称**:Push - **作用**:当$PSH=1$时,要求接收方立即将数据提交给应用层,而非缓存等待更多数据。 - **应用场景**:实时交互协议(如SSH输入命令需即时响应)。 --- #### 4. **RST(复位标志位)** - **全称**:Reset - **作用**:当$RST=1$时,表示连接异常,需强制复位。用于拒绝非法请求或终止故障连接[^4]。 - **典型场景**: - 客户端访问未监听的端口时,服务端返回$RST=1$[^2]。 - 处理半开连接(如一方崩溃后恢复)[^1]。 --- #### 5. **SYN(同步标志位)** - **全称**:Synchronize - **作用**:在**三次握手**中同步序列号。 - 客户端发送$SYN=1, ACK=0$发起连接。 - 服务端回复$SYN=1, ACK=1$确认连接。 - **安全问题**:SYN洪水攻击通过伪造大量$SYN$报文耗尽服务端资源[^2]。 --- #### 6. **FIN(结束标志位)** - **全称**:Finish - **作用**:当$FIN=1$时,表示发送方数据已传完,请求关闭连接。**四次挥手**中双方各发送一次$FIN=1$以安全终止连接。 --- ### 总结 六个标志位的组合定义了TCP报文的行为: - **连接建立**:$SYN$ + $ACK$ - **连接终止**:$FIN$ + $ACK$ - **异常处理**:$RST$ - **数据传输控制**:$URG$, $PSH$ ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值