uc_15_TCP协议

news2024/11/19 8:37:15

1  TCP协议

        TCP提供客户机与服务器的链接。一个完整TCP通信过程需要经历三个阶段

        1)首先,客户机必须建立与服务器的连接,所谓虚电路

        2)然后,凭借已建立好的连接,通信双方相互交换数据

        3)最后,客户机与服务器双双终止连接,结束通信过程

        TCP保证数据传输的可靠性(超时重传、反向确认):

        TCP的协议栈底层在向另一端发送数据时,会要求对方在一个给定的时间窗口内返回确认。如果超过了这个时间窗口仍没有收到确认,则TCP会重传数据并等待更长的时间。只有在数次重传均告失败以后,TCP才会最终放弃。TCP含有用于动态估算数据往返时间(Round-Trip Time, RTT)的算法,因此它知道等待一个确认需要多长时间。

        TCP保证数据传输的有序性

        TCP的协议栈底层在向另一端发送数据时,会为所发送数据的每个字节指定一个序列号。即使这些数据字节没有能够按照发送时的顺序到达接收方,接收方的TCP也可以根据它们的序列号重新排序,再把最后的结果交给应用程序。

        TCP是全双工的:

        在给定的连接上,应用程序在任何时候都既可以发送数据也可以接收数据。因此,TCP必须跟踪每个方向上数据流的状态信息,如序列号和通告窗口的大小。

1.1  三次握手(建立连接)

                         

                                         三次握手                                                          交换数据

        1)客户机的TCP协议栈向服务器发送一个SYN分节(SYN比特位是1),告知对方自己将在连接中发送数据的初始序列号,称为主动打开。

        2)服务器的TCP协议栈向客户机发送一个单个分节,其中不仅包括对客户机SYN分节的ACK应答,还包含服务器自己的SYN分节(ACK和SYN比特位均是1),以告知对方自己在同一连接中发送数据的初始序列号。

        3)客户机的TCP协议栈向服务器返回ACK应答,以表示对服务器所发SYN的确认。
 

        交换数据

        - 一旦连接建立,客户机即可构造请求包,并发往服务器。服务器接收并处理来自客户机的请求包,构造响应包。

        - 服务器向客户机发送响应包,同时捎带对客户机请求包的ACK应答(反向确认)。

        - 客户机接收来自服务器的响应包,同时向对方发送ACK应答(反向确认)。

1.2  四次挥手(关闭连接)

                                                        

        1)客户机或者服务器主动关闭连接,TCP协议向对方发送FIN分节,表示数据通信结束。如果此时尚有数据滞留于发送缓冲区中,则FIN分节跟在所有未发送数据之后。

        2)接收到FIN分节的另一端执行被动关闭,一方面通过TCP协议栈向对方发送ACK应答,另一方面向应用程序传递文件结束符。

        3)一段时间后,方才接收到FIN分节的进程关闭自己的链接,同时通过TCP协议栈向对方发送FIN分节。

        4)对方在收到FIN分节后发送ACK应答。

2  TCP函数

2.1  listen()

        #include <sys/socket.h>

        int  listen( int socket,   int backlog );

                功能:启动侦听,在指定套接字上启动对连接请求的侦听功能

                sockfd:套接字描述符,在调用此函数之前是一个主动套接字,是不能感知连接请求的

                               在调用此函数并成功返回后,是一个被动套接字,具有感知连接请求的能力。

                backlog:未决连接请求队列的最大长度,一般取不小于1024的值

                返回值:成0-1 

2.2  accept()

        三次握手始于connect(),终于accept()结束

        #include <sys/socket.h>

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

                功能:等待并接受连接请求,在指定套接字上阻塞,直到连接建立完成

                sockfd:侦听套接字描述符

                addr:输出连接请求发起方的地址信息

                addrlen:输出连接请求发起方的地址信息字节数

                返回值:成功返回可用于后续通信的连接套接字描述符,失败返回-1 

2.3  recv()

        #include <sys/socket.h>

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

                功能:接收数据

                flags:取0则与read()函数等价。另外也可取以下值

                           MSG_DONTWAIT  以非阻塞方式接收数据

                           MSG_OOB             接收带外数据

                           MSG_WAITALL      等待所有数据,即不接收到count个字节就不返回

                返回值:成功返回实际接收到的字节数,失败返回-1  

2.4  send()

        #include <sys/socket.h>

        ssize_t send( int sockfd,   void const* buf,   size_t count,   int flags );

                功能:发送数据

                flags:取0则与write()函数等价。另外也可取以下值

                           MSG_DONTWAIT  以非阻塞方方式发送数据

                           MSG_OOB             接收带外数据

                           MSG_DONTROUTE 不查路由表,直接在本地网络中寻找目的主机

                返回值:成功返回实际发送的字节数,失败返回-1 

3  TCP编程模型

        

4  通信终止

4.1  客户机主动终止

        在某个时刻,客户机认为已经不再需要服务器继续为其提供服务器了,于是它在接收完最后一个响应包以后,通过close()函数关闭与服务器通信的套接字。

        客户机的TCP协议栈向服务器发送FIN分节并得到对方的ACK应答。

        服务器专门负责与客户机通信的子进程,此刻正视图通过recv()接收下一个请求包,结果却因为收到来自客户机的FIN分节而返回0。

        于是该子进程退出收发循环,同时通过close()关闭连接套接字,导致服务器的TCP协议栈向客户机发送FIN分节,使对方进入TIME_WAIT状态,并在收到对方ACK应答以后,自己进入CLOSED状态。

        随着收发循环的退出,服务器子进程终止,并在服务器主进程的SIGCHLD(17)信号处理函数中被回收。

        通信过程宣告结束。

4.2  服务器主动终止

        服务器专门负责和某个特定客户机通信的子进程,在运行过程中出现错误,不得不调用close()函数关闭连接套接字,或者直接退出,甚至被信号杀死。于是服务器的TCP协议栈向客户机发送FIN分节并得到对方的ACK应答。

        A、如果客户机这时正视图通过recv()接收响应包,那么该函数会返回0。客户机可据此判断服务器已宕机,直接通过close()关闭与服务器通信的套接字,终止通信进程。

        B、如果客户机这时正视图通过send()发送请求包,那么该函数并不会失败,但会导致对方以RST分节做出响应,该响应分节甚至会先于FIN分节被紧随其后的recv()收到并返回-1,同时置errno为ECONNRESET。这也是终止通信的条件之一。

4.3  服务器主机不可达(主机崩溃、网络中断、路由失效...)

        在服务器主机不可达的情况下,无论是客户机还是服务器,它们的TCP协议栈都不可能再有任何数据分节的交换。因此,客户机通过send()函数发送完请求包以后,会阻塞在recv()上等待来自服务器的响应包。

        这是客户机的TCP协议栈会持续地重传数据分节,视图得到对方的ACK应答。源自伯克利的实现最多重传12次,最长等待9分钟。

        当TCP最终决定放弃时,会通过recv()向用户进程返回失败,并置errno为ETIMEOUT或EHOSTUNREACH或ENETUNREACH。

        此后,即使服务器主机被重启,或者通信线路被恢复,由于TCP协议栈已丢失了与先前连接有关的信息,通信依然无法继续,对所接收到的一切数据一律响应RST分节,只有在重新建立TCP连接后,才能继续通信。

5  域名解析

        IP地址是网络上标识站点的数字地址,为了方便记忆,采用域名来代替IP地址标识站点地址。

        域名解析就是域名IP地址的转换过程,由DNS服务器完成。

        当应用过程需要将一个主机域名映射为IP地址时,就调用域名解析函数,解析函数将待转换的域名放在DNS请求中,以UDP报文方式发给本地域名服务器。本地的域名服务器查到域名后,将对应的IP地址放在应答报文中返回。

5.1  gethostbyname()

        #include <netdb.h>

        struct hostnet* gethostbyname( char const* host_name );

                功能:通过参数所传的主机域名,获取主机信息

                host_name:主机域名

                返回值:成功返回表示主机信息的结构体指针,失败返回NULL 

        注意,该函数需要在联网情况下使用。

        

        struct hostent {

                char   *h_name;        // 主机官方名

                char   **h_aliases;    // 主机别名表

                int   h_addrtype;       // 地址类型

                int   h_length;           // 地址长度

                int   **h_addr_list;    // IP地址表

        };

        对于WEB服务器而言,主机官方名有一个,而主机别名可能有多个,这些别名都是为了便于用户记忆。同时IP地址也可能有多个。

        h_aliases   ->   *   ->   "xxx\n"

                          ->   *   ->   "xxx\n"

                          NULL;

        h_addr_list ->   *   ->   in_addr

                          ->   *   ->   in_addr

                          NULL

//tcpser.c  基于tcp的服务器
#include<stdio.h>
#include<string.h>
#include<ctype.h> // toupper()
#include<sys/socket.h>//网络
#include<sys/types.h>//网络
#include<arpa/inet.h>//网络
#include<signal.h>
#include<sys/wait.h>
#include<errno.h>
#include<unistd.h>
//信号处理函数,收尸

int main(void){
    //捕获17号信号


    printf("服务器:创建套接字\n");
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1){
        perror("socket");
    }
    //组织地址结构
    printf("服务器:组织地址结构\n");
    struct sockaddr_in ser;
    ser.sin_family = AF_INET;
    ser.sin_port = htons(8980); //8980由小端转大端
    //ser.sin_addr.s_addr = inet_addr("192.168.222.136");
    ser.sin_addr.s_addr = INADDR_ANY;

    //绑定地址结构和套接字
    printf("服务器:绑定套接字和地址结构\n");
    if(bind(sockfd,(struct sockaddr*)&ser,sizeof(ser)) == -1){
        perror("bind");
        return -1;
    }
    //开启侦听 套接字 主动-->被动-->感知连接请求
    printf("服务器:启动侦听\n");
    if(listen(sockfd,1024) == -1){
        perror("listen");
        return -1;
    }

    for(;;){
        //等待并建立通信连接
        printf("服务器:等待并建立通信连接\n");
        struct sockaddr_in cli;//用来输出客户端的地址结构
        socklen_t len = sizeof(cli);//用来输出地址结构大小
        int conn = accept(sockfd,(struct sockaddr*)&cli,&len);
        if(conn == -1){
            perror("accept");
            return -1;
        }
        printf("服务器:接收到%s:%hu的客户端的连接\n",inet_ntoa(cli.sin_addr),ntohs(cli.sin_port));
        //创建子进程 fork
        
        //子进程服务业务处理
        //业务处理
        printf("服务器:业务处理\n");
        for(;;){
            //接受客户端发来的小写的串
            char buf[64] = {};
            ssize_t size = read(conn,buf,sizeof(buf)-1);
            if(size == -1){
                perror("read");
                return -1;
            }
            if(size == 0){
                printf("服务器:客户端断开连接\n");
                break;
            }
            //转成大写
            for(int i = 0;i < strlen(buf);i++){
                buf[i] = toupper(buf[i]);
            }
            //将转成大写的串回传客户端
            if(write(conn,buf,strlen(buf)) == -1){
                perror("write");
                return -1;
            }
        }
        printf("服务器:关闭套接字\n");
        close(conn);
    }
    close(sockfd);  
    return 0;
}






#include<stdio.h>
#include<string.h>
#include<ctype.h> // toupper()
#include<sys/socket.h>//网络
#include<sys/types.h>//网络
#include<arpa/inet.h>//网络
#include<signal.h>
#include<sys/wait.h>
#include<errno.h>
#include<unistd.h>
//信号处理函数,收尸
void sigchild(int signum){
    printf("服务器:捕获到%d号信号\n",signum);
    for(;;){
        pid_t pid = waitpid(-1,NULL,WNOHANG);
        if(pid == -1){
            if(errno == ECHILD){
                printf("服务器:没有子进程\n");
                break;
            }else{
                perror("waitpid");
                return ;
            }
        }else if(pid == 0){
            printf("服务器:子进程在运行\n");
            break;
        }else{
            printf("服务器:回收了%d进程的僵尸\n",pid);
        }
    }
}

int main(void){
    //捕获17号信号
    if(signal(SIGCHLD,sigchild) == SIG_ERR){
        perror("signal");
        return -1;
    }

    printf("服务器:创建套接字\n");
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1){
        perror("socket");
    }
    //组织地址结构
    printf("服务器:组织地址结构\n");
    struct sockaddr_in ser;
    ser.sin_family = AF_INET;
    ser.sin_port = htons(8980);
    //ser.sin_addr.s_addr = inet_addr("192.168.222.136");
    ser.sin_addr.s_addr = INADDR_ANY;//接受任意IP地址的数据(服务器可能有多个地址)

    //绑定地址结构和套接字
    printf("服务器:绑定套接字和地址结构\n");
    if(bind(sockfd,(struct sockaddr*)&ser,sizeof(ser)) == -1){
        perror("bind");
        return -1;
    }
    //开启侦听 套接字 主动-->被动-->感知连接请求
    printf("服务器:启动侦听\n");
    if(listen(sockfd,1024) == -1){
        perror("listen");
        return -1;
    }

    for(;;){
        //等待并建立通信连接
        printf("服务器:等待并建立通信连接\n");
        struct sockaddr_in cli;//用来输出客户端的地址结构
        socklen_t len = sizeof(cli);//用来输出地址结构大小
        int conn = accept(sockfd,(struct sockaddr*)&cli,&len);
        if(conn == -1){
            perror("accept");
            return -1;
        }
        printf("服务器:接收到%s:%hu的客户端的连接\n",inet_ntoa(cli.sin_addr),ntohs(cli.sin_port));
        //创建子进程 fork
        pid_t pid = fork();
        if(pid == -1){
            perror("fork");
            return -1;
        }
        //子进程服务业务处理
        //业务处理
        if(pid == 0){
            close(sockfd);
            printf("服务器:业务处理\n");
            for(;;){
                //接受客户端发来的小写的串
                char buf[64] = {};
                ssize_t size = read(conn,buf,sizeof(buf)-1);
                if(size == -1){
                    perror("read");
                    return -1;
                }
                if(size == 0){
                    printf("服务器:客户端断开连接\n");
                    break;
                }
                //转成大写
                for(int i = 0;i < strlen(buf);i++){
                    buf[i] = toupper(buf[i]);
                }
                //将转成大写的串回传客户端
                if(write(conn,buf,strlen(buf)) == -1){
                    perror("write");
                    return -1;
                }
            }
            printf("服务器:关闭套接字\n");
            close(conn);
            return 0;//!!!!!!
        }
        //父进程代码,关闭通信套接字
        close(conn);//通信套接字(1个通信套接字对应1个客户端)
    }
    close(sockfd);//侦听套接字(整个服务器只有1个)
    return 0;
}//结合tcpcli,1台虚拟机做服务器执行本代码,另一台做客户端执行tcpcli
//tcpcli.c  基于的客户端
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>

int main(void){
    //创建套接字
    printf("客户端:创建套接字\n");
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1){
        perror("socket");
        return -1;
    }
    //准备服务器的地址结构
    printf("客户端:准备服务器的地址结构\n");
    struct sockaddr_in ser;
    ser.sin_family = AF_INET;
    ser.sin_port = htons(8980);
    ser.sin_addr.s_addr = inet_addr("192.168.222.136");
    //发起连接
    printf("客户端:发起连接\n");
    if(connect(sockfd,(struct sockaddr*)&ser,sizeof(ser)) == -1){
        perror("connect");
        return -1;
    }
    //业务处理
    for(;;){
        //通过键盘获取小写的串
        char buf[64] = {};
        fgets(buf,sizeof(buf),stdin);
        // ! 退出
        if(strcmp(buf,"!\n") == 0){
            break;            
        }
        //将小写的串发送给服务器
        if(send(sockfd,buf,strlen(buf),0) == -1){
            perror("send");
            return -1;
        }
        //接受服务器回传大写的串
        if(recv(sockfd,buf,sizeof(buf)-1,0) == -1){
            perror("recv");
            return -1;
        }
        //显示
        printf("%s",buf);
    }
    printf("客户端:关闭套接字\n");
    close(sockfd);
    return 0;
}

1

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1298330.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python爬虫T1——urllib的基本使用 获取百度网页的源代码

文章目录 代码以及解释效果 代码以及解释 import urllib.request #使用urllib来获取百度的源码 urlhttps://zhuanlan.zhihu.com/p/357258757 #定义一个url 目标访问地址 responseurllib.requesturllib.request.urlopen(url) #模拟浏览…

有什么进销存软件能对接微信小程序?

有什么进销存软件能对接微信小程序&#xff1f; 据我所知&#xff0c;很多进销存软件都有配套的微信小程序吧。 以我们现在用的这个为例&#xff0c;这也是同行推荐过来的&#xff0c;很好用&#xff0c;而且性价比很高—— 在线平台&#xff0c;无需下载APP&#xff0c;搭载…

【小沐学Python】Python实现语音识别(Whisper)

文章目录 1、简介1.1 whisper简介1.2 whisper模型 2、安装2.1 whisper2.2 pytorch2.3 ffmpeg 3、测试3.1 命令测试3.2 代码测试&#xff1a;识别声音文件3.3 代码测试&#xff1a;实时录音识别 结语 1、简介 https://github.com/openai/whisper 1.1 whisper简介 Whisper 是…

C语言-字符串操作函数-附加使用方式

文章目录 前言字符串复制-strcpy字符串复制&#xff08;按照位数&#xff09;-strncpy字符串比较-strcmp字符串比较(按照位数)-strncmp不区分大小写的字符串比较-strcasecmp不区分大小写的比较(前n位)-strncasecmp字符串按照格式写入-sprintf字符串按照格式和个数写入-snprintf…

Leetcode—2646.最小化旅行的价格总和【困难】

2023每日刷题&#xff08;五十三&#xff09; Leetcode—2646.最小化旅行的价格总和 算法思想 看灵神的 实现代码 class Solution { public:int minimumTotalPrice(int n, vector<vector<int>>& edges, vector<int>& price, vector<vector&l…

Win10的SVN Adapter V1.0 中黄色感叹号 -- 解决

大部分都问题都可以通过&#xff1a; 关闭 SVN Adapter V1.0 在下载最新的 SVNDrv.sys替换 C:\Windows\System32\drivers 中的同名文件启动 SVN Adapter V1.0 就能成功 但是部分人的电脑 SVN Adapter V1.0 是有感叹号的&#xff0c;说明注册表有问题 先用 CCleaner 修复注册表…

解决npm install时报:gyp ERR! configure error

报错内容&#xff1a; npm ERR! gyp ERR! cwd C:\Users\zccbbg\code\my\examvue\node_modules\node-sass npm ERR! gyp ERR! node -v v16.13.1 npm ERR! gyp ERR! node-gyp -v v3.8.0 npm ERR! gyp ERR! not ok npm ERR! Build failed with error code: 1 解决办法&#xff1a;…

Weblogic T3协议反序列化漏洞

文章目录 1. Weblogic T3协议反序列化漏洞1.1 漏洞描述1.2 基本原理1.3 漏洞复现1.4 修复建议 1. Weblogic T3协议反序列化漏洞 1.1 漏洞描述 说明内容漏洞编号CVE-2018-2628漏洞名称Weblogic T3协议反序列化漏洞漏洞评级高危影响范围Weblogic 10.3.6.0Weblogic 12.1.3.0Webl…

从零开始学Scala,这些学习网站让你轻松入门!

介绍&#xff1a;Scala&#xff0c;可伸缩的语言&#xff0c;是一门多范式的编程语言&#xff0c;由联邦理工学院洛桑&#xff08;EPFL&#xff09;的Martin Odersky于2001年基于Funnel的工作开始设计。它是以Java虚拟机&#xff08;JVM&#xff09;为运行环境并将面向对象编程…

智能优化算法应用:基于灰狼算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于灰狼算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于灰狼算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.灰狼算法4.实验参数设定5.算法结果6.参考文献7.MA…

用友T3如何反结账、反记账、反审核及删除凭证

在T3总账中已经进行了总账记账和月末结账&#xff0c;但是需要去修改凭证或删除凭证&#xff0c;这个时候就需要去进行反结账、反记账等操作&#xff0c;以下是具体的操作流程 第一步、反结账 1、进入用友T3件&#xff0c;打开总账系统模块&#xff0c;点月末结账&#xff0c…

【链表OJ—链表的中间节点】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 1、链表的中间节点 方法讲解&#xff1a; 图文解析&#xff1a; 代码实现&#xff1a; 总结 前言 世上有两种耀眼的光芒&#xff0c;一种是正在升起的太阳&#x…

智能优化算法应用:基于蚁狮算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于蚁狮算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于蚁狮算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.蚁狮算法4.实验参数设定5.算法结果6.参考文献7.MA…

pwn入门:基本栈溢出之ret2libc详解(以32位+64位程序为例)

目录 写在开头 题目简介 解题思路 前置知识&#xff08;简要了解&#xff09; plt表和got表 延迟绑定 例题详解 32位 64位 总结与思考 写在开头 这篇博客早就想写了&#xff0c;但由于近期事情较多&#xff0c;一直懒得动笔。近期被领导派去临时给合作单位当讲师&a…

全自动网印机配件滚珠螺杆起什么作用?

全自动网印机配件主要包括印刷网板、墨水或油墨箱、墨水或油墨管道、传动部件、移动部件、传动系统、控制系统、安全保护装置等等这些。 滚珠螺杆是一种精密的传动元件&#xff0c;能够将电机的旋转运动转化为线性运动&#xff0c;从而驱动印刷机的工作台进行精确的移动&#x…

Java毕业设计—vue+SpringBoot药房药店药品管理系统

项目介绍 项目背景 药店系统是现代医疗行业不可或缺的部分&#xff0c;它为药店提供全面、高效的管理服务&#xff0c;对保障顾客健康和药品质量起着至关重要的作用。 药店系统的功能主要包括库存管理、销售管理、客户管理和财务管理等多个方面。在库存管理方面&#xff0c;…

上网监控软件丨5大不同人群的锋利观点

上网监控软件是一种可以监控员工上网行为的软件&#xff0c;通常被用于企业或组织内部。这种软件可以记录员工访问的网站、发送的邮件、聊天的记录等&#xff0c;并可以对员工的上网行为进行控制和限制&#xff0c;从而提高员工的工作效率&#xff0c;防止数据泄露和网络攻击。…

PyCharm编辑器结合Black插件,轻松实现Python代码格式化

大家好&#xff0c;使用Black对Python代码进行格式化&#xff0c;可使代码看起来更美观。但是&#xff0c;随着项目规模不断变大&#xff0c;对每个文件运行Black变得很繁琐。本文就来介绍在PyCharm中实现这一目标的方法。 1.安装Black 首先&#xff0c;在虚拟环境中安装Blac…

javacv踩坑记录

前一阵学习opencv&#xff0c;发现在做人脸识别的时候遇到一些类库不存在的情况&#xff0c;查找后发现是由于拓展包没有安装完全&#xff08;仅安装了基础版&#xff09;。由于网络的问题&#xff08;初步猜测&#xff09;&#xff0c;始终无法安装好拓展包。 于是另辟蹊径&am…

【LeetCode刷题】数组篇2

&#x1f387;数组中等题Part &#x1f308; 开启LeetCode刷题之旅 &#x1f308; 文章目录 &#x1f387;数组中等题Part&#x1f370;229.多数元素II&#x1f451;思路分析1.哈希表法2.摩尔投票法(进阶) &#x1f370;15.三数之和&#x1f451;思路分析1.排序双指针 &#x…