我们经常会有需求,希望能获取的到当前用户的IP地址,而IP又分为公网ip(也称外网)和私网IP(也称内网IP),IP地址是IP协议提供的一种统一的地址格式,每台设备都设定了一个唯一的IP地址”,从而确保了用户在连网的计算机上操作时,能够快速地从互联网中找到自己所需的对象。
1,外网IP是全球唯一的IP地址,仅分配给某一台网络设备。内网IP是由路由器分配给每一台设备内部使用的IP地址;
2,外网IP任何一台设备都可以ping通。内网IP只有在同一环境的内部设备才能ping通;
3,外网用户无法直接访问到内网用户,内网用户可以访问外网用户,因为内网的所有用户都是通过同一个外网IP进行上网的;
这里我们可以借助现成的接口,搜狐提供的一个JS接口获取IP地址,我们只需在入口index.html中直接引入该接口即可轻松获取到当前用户的外网ip,Vue中在public中的index.html中引入接口,然后再需要获取的地方通过returnCitySN['cip']即可拿到IP地址,然后将IP存到localstorage或者Vuex中,这样随时可以调用了。
//引入JS <script src="http://pv.sohu.com/cityjson?ie=utf-8"></script> //在组件中获取,可以选择在首页载入前获取该参数 var Ip=returnCitySN['cip'] localStorage.setItem('Ip', Ip)
获取内网IP相对来说会复杂些,毕竟没有现成的接口可以调用,这里我们用到了WebRTC(网页即时通信),在WebRTC规范中,RTCPeerConnection可以用于视频流/音频流、以及数据的传输。这里们通过RTCPeerConnection 对象建立一个连接通道,下面3个或对象是针对不同浏览器来创建的。
var RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection;
当RTCPeerConnection对象存在时,我们就可以实例化该对象并创建一个可以发送任意数据的数据通道,此时我们的RTCPeerConnection对象中数据基本都是null。
var RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection; if (RTCPeerConnection) (()=>{ var rtc = new RTCPeerConnection() rtc.createDataChannel(''); //创建一个可以发送任意数据的数据通道 })()
SDP 是一种会话描述格式 ,由许多文本行组成,文本行的格式为<类型>=<值>,<类型>是一个字母,<值>是结构化的文本串。SDP中包含了很多媒体信息,包括了媒体类型(video)、传输协议(RTP/UDP/IP)、媒体格式(H.264 video)、多播或单播地址和端口、本端的带宽信息、本端的加密信息等。
我们从要做的就是从SDP中拿到传输协议中的信息,创建一条sdp数据并将数据存入LocalDescription对象中。这样我们在LocalDescription中就得到了所有的SDP数据,从下图中我们可以看到有IP地址在里面,接下来就可以从sdq中提取ip地址了。
rtc.createOffer( offerDesc => { //创建并存储一条sdp数据 rtc.setLocalDescription(offerDesc) }, e => { console.warn(e)})
onicecandidate属性在RTCPeerConnection实例上发生icecandidate事件时要调用的函数,当我们向服务器发送消息时触发并获取到SDP中的candidate属性,而candidate中正好有我们想要的IP地址,你可以直接获取var ip_addr = evt.candidate.address。当然你也可以从candidate属性中获取,不过这里就会复杂点,用个正则来提取吧(显然方法一简单)。
rtc.onicecandidate =(evt) => { //监听candidate事件 if (evt.candidate) { //方法一: var ip_addr = evt.candidate.address //方法二: let ip_rule = /([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/. var ip_addr = ip_rule.exec(evt.candidate.candidate)[1] console.log("ip_addr==",ip_addr) }}
最后整理下代码,封装成一个方法需要的时候直接调用即可,通过localStorage来存储获取到的ip_addr(或者Vuex存储),然后我们只需要通过localStorage.getItem('ip_addr'))就可以获取到我们的内网IP地址了。
getUserIP(){ var RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection; if (RTCPeerConnection) (()=>{ var rtc = new RTCPeerConnection() rtc.createDataChannel(''); //创建一个可以发送任意数据的数据通道 rtc.createOffer( offerDesc => { //创建并存储一个sdp数据 rtc.setLocalDescription(offerDesc) }, e => { console.log(e)}) rtc.onicecandidate =(evt) => { //监听candidate事件 if (evt.candidate) { var ip_addr = evt.candidate.address localStorage.setItem('ip_addr',ip_addr) }} })() else{console.log("目前仅测试了chrome浏览器OK")} }
欢迎关注本人的公众号:编程手札,文章也会在公众号更新
、总线的概念
所谓总线(Bus),是指计算机设备和设备之间传输信息的公共数据通道。总线是连接计算机硬件系统内多种设备的通信线路,它的一个重要特征是由总线上的所有设备共享,可以将计算机系统内的多种设备连接到总线上。如果是某两个设备或设备之间专用的信号连线,就不能称之为总线。系统总线架构图如下所示:
微机中的总线分为数据总线、地址总线和控制总线3类。不同型号的CPU芯片,其数据总线、地址总线和控制总线的条数可能不同。
数据总线DB用来传送数据信息,是双向的。CPU既可通过DB从内存或输入设备读入数据,又可通过DB将内部数据送至内存或输出设备。DB的宽度决定了CPU和计算机其他设备之间每次交换数据的位数。
地址总线AB用于传送CPU发出的地址信息,是单向的。传送地址信息的目的是指明与CPU交换信息的内存单元或I/O设备。存储器是按地址访问的,所以每个 存储单元都有一个固定地址,要访问1MB存储器中的任一单元,需要给出1M个地址,即需要20位地址(220=1M)。因此,地址总线的宽度决定了CPU 的最大寻址能力。
控制总线CB用来传送控制信号、时序信号和状态信息等。其中有的是CPU向内存或外部设备发出的信息,有的是内存或外部设备向CPU发出的信息。显然,CB中的每一条线的信息传送方向是一定的、单向的,但作为一个整体则是双向的。所以,在各种结构框图中,凡涉及到控制总线CB,均是以双向线表示。
总线的性能直接影响到整机系统的性能,而且任何系统的研制和外围模块的开发都必须依从所采用的总线规范。总线技术随着微机结构的改进而不断发展与完善。
二. 常见总线
QPI总线
Intel的QuickPath Interconnect技术缩写为QPI,译为快速通道互联, 用来实现处理器之间的直接互联. QPI是一种基于包传输的串行式高速点对点连接协议,采用差分信号与专门的时钟进行传输。它的特点是:高速带宽,低功耗,支持热插拔。
Memory总线(内存总线)
用来实现处理器和内存的之间的连接.处理器里集成的内存控制器负责通过内存总线和内存模组通讯,例如寻址、读写等。目前内存总线所支持的内存模组有DDR2, DDR3, 将来还会支持DDR4。
JTAG接口
主要用于芯片或处理器内部测试和调试的接口.通过连接调试器, 可以对芯片或处理器的运行进行跟踪和调试。
DMI总线
DMI是指Direct Media Interface(直接媒体接口)。用来连接处理器和南桥的总线.它是基于PCIE总线,因此具有PCI-E总线的优势,这个高速接口集成了高级优先服务,允许并发通讯和真正的同步传输能力。它的基本功能对于软件是完全透明的,因此早期的软件也可以正常操作。
USB总线
USB,是英文Universal Serial BUS(通用串行总线)的缩写,而其中文简称为“通串线,是一个外部总线标准,用于规范电脑与外部设备的连接和通讯。USB接口支持设备的即插即用和热插拔功能。USB总线会根据外设情况在两种传输模式中自动地动态转换。USB是基于令牌的总线。类似于令牌环网络或FDDI基于令牌的总线。USB主控制器广播令牌,总线上设备检测令牌中的地址是否与自身相符,通过接收或发送数据给主机来响应。USB通过支持悬挂/恢复操作来管理USB总线电源。USB系统采用级联星型拓扑,该拓扑由三个基本部分组成:主机(Host),集线器(Hub)和功能设备。
SMBUS/I2C总线
I2C(Inter-Integrated Circuit)总线和SMBus (System Management Bus 的缩写,译为系统管理总线)是一种二线制串行总线,它主要应用的场合:不需要高速通讯,但希望通过一条廉价并且功能强大的总线(由两条线组成),来控制主板上的设备并收集相应的信息。SMBUS大部分基于I2C总线规范。和 I2C一样,SMBus不需增加额外引脚,创建该总线主要是为了增加新的功能特性,但只工作在100kHz且专门面向智能电池管理应用, 也被用来连接各种设备,包括电源相关设备,系统传感器,EEPROM等等。它工作在主/从模式:主器件提供时钟,在其发起一次传输时提供一个起始位,在其终止一次传输时提供一个停止位;从器件拥有一个唯一的7或10位从器件地址。
SMBus与I2C总线之间在时序特性上存在一些差别。首先,SMBus需要一定数据保持时间,而 I2C总线则 是从内部延长数据保持时间。SMBus具有超时功能,因此当SCL太低而超过35 ms时,从器件将复位正在进行的通信。相反,I2C采用硬件复位。SMBus具有一种警报响应地址(ARA),因此当从器件产生一个中断时,它不会马上清 除中断,而是一直保持到其收到一个由主器件发送的含有其地址的ARA为止。SMBus只工作在从10kHz到最高100kHz。最低工作频率10kHz是由SMBus超时功能决定的。
SPI总线
SPI(Serial Peripheral Interface--串行外设接口)总线系统是一种同步串行外设接口,它可以使南桥与各种外围设备以串行方式进行通信以交换信息。SPI接口主要应用在连接EEPROM、FLASH、实时时钟、AD转换器,还有数字信号处理器和数字信号解码器之间。在Intel架构中放BIOS/UEFI固件的Flash可以通过SPI总线和南桥连接。
LPC总线
LPC(Low Pin Count, 少引脚数)接口一个取代传统ISA总线的一种新接口规范,主要用于和传统的外围设备连接让系统能向下兼容。以往为了连接ISA扩充槽、适配器、ROM BIOS芯片、Super I/O等接口,南桥芯片必须保留一个ISA总线,并且连通Super I/O芯片,以控制传统的外围设备。传统ISA 总线速率大约在7.159~8.33MHz,提供的理论尖峰传输值为16MB/s,但是ISA总线与传统的PCI总线的电气特性、信号定义方式迥异,南桥芯片、Super I/O芯片得多浪费针脚来做处理,主板的线路设计也显得复杂。 intel所定义的LPC接口,将以往ISA BUS的地址/数据分离译码,改成类似PCI的地址/数据信号线共享的译码方式,信号线数量大幅降低,工作速率由PCI总线速率同步驱动,虽然改良过的LPC接口一样维持最大传输值16MB/s,不过所需要的信号脚位数大幅降低25~30个,以LPC接口设计的Super I/O芯片、Flash芯片都能享有脚位数减少、体积微缩的好处,主板的设计也可以简化,这也就是取名LPC——Low Pin Count的原因。
PS/2接口
PS/2 (Personal System 2, 个人系统2)接口主要用于连接输入设备,而不是传输接口。所以PS2口根本没有传输速率的概念,只有扫描速率。PS/2接口设备不支持热插拔,强行带电插拔有可能烧毁主板。
RS-232接口
RS-232-C是美国电子工业协会EIA(Electronic Industry Association)制定的一种异步传输串行物理接口标准。RS是英文“推荐标准”的缩写,232为标识号,C表示修改次数。RS-232-C总线标准设有25条信号线。一般个人计算机上会有两组 RS-232 接口,分别称为 COM1 和 COM2。
LPT接口
打印终端(line print terminal)接口,通常称呼为LPT并口,是一种增强了的双向并行传输接口,在USB接口出现以前是扫描仪,打印机最常用的接口。其默认的中断号是IRQ7,采用25脚的DB-25接头。并口的工作模式主要有三种:1、SPP标准工作模式。SPP数据是半双工单向传输,传输速率较慢,仅为15Kbps,但应用较为广泛,一般设为默认的工作模式。2、EPP增强型工作模式。EPP采用双向半双工数据传输,其传输速率比SPP高很多,可达2Mbps,目前已有不少外设使用此工作模式。3、ECP扩充型工作模式。ECP采用双向全双工数据传输,传输速率比EPP还要高一些,但目前支持的设备少。
前端总线
前端总线的英文名字是Front Side Bus,通常用FSB表示,是将CPU连接到北桥芯片的总线。选购主板和 CPU时,要注意两者搭配问题,一般来说,如果CPU不超频,那么前端总线是由CPU决定的,如果主板不支持CPU所需要的前端总线,系统就无法工作。也 就是说,需要主板和CPU都支持某个前端总线,系统才能工作,只不过一个CPU默认的前端总线是唯一的,因此看一个系统的前端总线主要看CPU就可以。北桥芯片负责联系内存、显卡等数据吞吐量最大的部件,并和南桥芯片连接。CPU就是通过前端总线(FSB)连接到北桥芯片,进而通过北桥芯片和内存、显卡交换数据。前端总线是CPU和外界交换数据的最主要通道,因此前端总线的数据传输能力对计算机整体性能作用很大,如果没足够快的前端总线,再强的CPU也不能明显提高计算机整体速度。数据传输最大带宽取决于所有同时传输的数据的宽度和传输频率,即数据带宽=(总线频率×数据位宽)÷8。下图为FSB示意图。
PCI总线
Intel公司首先提出了PCI的概念,并联合IBM、Compaq、AST、HP、DEC等100多家公司成立了PCI集团,其英文全称为:Peripheral Component Interconnect Special Interest
Group(外围部件互连专业组),简称PCISIG。PCI是一种先进的局部总线,已成为局部总线的新标准。
最早提出的PCI总线工作在33MHz频率之下,传输带宽达到132MB/s(33MHz * 32bit/8),基本上满足了当时处理器的发展需要。随着对更高性能的要求,后来又提出把PCI 总线的频率提升到66MHz,传输带宽能达到264MB/s。1993年又提出了64bit的PCI总线,称为PCI-X,目前广泛采用的是32-bit、33MHz或者32-bit、66MHz的PCI 总线,64bit的PCI-X插槽更多是应用于服务器产品。和PCIE总线一样,它用来外接扩展板卡,支持即插即用 (plug and play)。但和PCIE不同,PCI允许多路复用技术(分时复用),即允许一个以上的电子信号同时存在于总线之上,但相对PCIE,它的带宽和速度比较慢。
PCI总线是一种不依附于某个具体处理器的局部总线。从结构上看,PCI是在CPU和原来的系统总线之间插入的一级总线,具体由一个桥接电路实现对这一层的管理,并实现上下之间的接口以协调数据的传送。管理器提供了信号缓冲,使之能支持10种外设,并能在高时钟频率下保持高性能。PCI总线也支持总线主控技术,允许智能设备在需要时取得总线控制权,以加速数据传送。 PCI总线支持10台外设 ,总线时钟频率33.3MHz/66MHz,最大数据传输速率133MB/s,时钟同步方式 ,与CPU及时钟频率无关 ,总线宽度 32位(5V)/64位(3.3V),能自动识别外设 ,特别适合与Intel的CPU协同工作; 具有与处理器和存储器子系统完全并行操作的能力,具有隐含的中央仲裁系统,采用多路复用方式(地址线和数据线)减少了引脚数,支持64位寻址,完全的多总线主控能力,提供地址和数据的奇偶校验,可以转换5V和3.3V的信号环境。
PCI-E总线
PCI Express是新一代的总线接口。早在2001年的春季,英特尔公司就提出了要用新一代的技术取代PCI总线和多种芯片的内部连接,并称之为第三代I/O总线技术。随后在2001年底,包括Intel、AMD、DELL、IBM在内的20多家业界主导公司开始起草新技术的规范,并在2002年完成,对其正式命名为PCI Express。它采用了目前业内流行的点对点串行连接,比起PCI以及更早期的计算机总线的共享并行架构,每个设备都有自己的专用连接,不需要向整个总线请求带宽,而且可以把数据传输率提高到一个很高的频率,达到PCI所不能提供的高带宽。
PCI Express的接口根据总线位宽不同而有所差异,包括X1、X4、X8以及X16(X2模式将用于内部接口而非插槽模式)。较短的PCI Express卡可以插入较长的PCI Express插槽中使用。PCI Express接口能够支持热拔插,这也是个不小的飞跃。PCI Express卡支持的三种电压分别为+3.3V、3.3Vaux以及+12V。用于取代AGP接口的PCI Express接口位宽为X16,将能够提供5GB/s的带宽,即便有编码上的损耗但仍能够提供4GB/s左右的实际带宽,远远超过AGP 8X的2.1GB/s的带宽。
PCI Express规格从1条通道连接到32条通道连接,有非常强的伸缩性,以满足不同系统设备对数据传输带宽不同的需求。例如,PCI Express X1规格支持双向数据传输,每向数据传输带宽250MB/s,PCI Express X1已经可以满足主流声效芯片、网卡芯片和存储设备对数据传输带宽的需求,但是远远无法满足图形芯片对数据传输带宽的需求。因此,必须采用PCI Express X16,即16条点对点数据传输通道连接来取代传统的AGP总线。PCI Express X16也支持双向数据传输,每向数据传输带宽高达4GB/s,双向数据传输带宽有8GB/s之多。
PCIe属于高速串行点对点双通道高带宽传输,所连接的设备分配独享通道带宽,不共享资源,主要支持主动电源管理,错误报告,端对端的可靠性传输,热插拔以及服务质量(QOS)等功能。它主要用来和一些需要高速通讯的外部板卡设备控制器连接,例如显示卡,网卡,声卡等, 相对于老的并行的PCI总线,它具有针脚少,速度快, 更好的电源管理等优点。
PCI-Express 3.0 规范
早在2007年上半年PCI-E 2.0版规范刚刚公布的时候,PCI Express技术标准组织PCI-SIG就准备用两年多的时间将其快速进化到第三代,但是谁也没想到PCI-E 3.0的酝酿过程会如此一波三折,直到今天才终于修成正果。
PCI-SIG主席兼总裁几乎泪流满面:“PCI-SIG始终致力于I/O创新,我们也很骄傲
地向我们的成员发布PCI-E 3.0规范。PCI-E 3.0架构从细节上对前两代PCI-E规范进行了极大地改进,为我们的成员在各自领域继续创新提供了所必需的性能和功能。”
在对可制造性、成本、功耗、复杂性、兼容性等诸多方面进行综合、平衡之后,PCI-E 3.0规范将数据传输率提升到8GHz|8GT/s(最初也预想过10GHz),并保持了对PCI-E 2.x/1.x的向下兼容,继续支持2.5GHz、5GHz信号机制。基于此,PCI-E 3.0架构单信道(x1)单向带宽即可接近1GB/s,十六信道(x16)双向带宽更是可达32GB/s。
PCI-E 3.0同时还特别增加了128b/130b解码机制,可以确保几乎100%的传输效率,相比此前版本的8b/10b机制提升了25%,从而促成了传输带宽的翻番,延续了PCI-E规范的一贯传统。
新规范在信号和软件层的其他增强之处还有数据复用指示、原子操作、动态电源调整机制、延迟容许报告、宽松传输排序、基地址寄存器(BAR)大小调整、I/O页面错误等等,从而全方位提升平台效率、软件模型弹性、架构伸缩性。
PCI-E 3.0规范完整文档现已向PCI-SIG组织成员公布其中详细描述了PCI-E架构、互联属性、结构管理、编程接口等等,但没有公开发表。另外,intel X79高端芯片组经已完整支持pci-e 3.0规格,AMD最新架构旗舰显卡AMD Radeon 7970,以及其他采用pci-e 3.0规格的显卡将于2012年陆续发布。
PCI Express总线的特点和长处
PCI Express总线是一种完全不同于过去PCI总线的一种全新总线规范,与PCI总线共享并行架构相比,PCI Express总线是一种点对点串行连接的设备连接方式,点对点意味着每一个PCI Express设备都拥有自己独立的数据连接,各个设备之间并发的数据传输互不影响,而对于过去PCI那种共享总线方式,PCI总线上只能有一个设备进行通信,一旦PCI总线上挂接的设备增多,每个设备的实际传输速率就会下降,性能得不到保证。现在,PCI Express以点对点的方式处理通信,每个设备在要求传输数据的时候各自建立自己的传输通道,对于其他设备这个通道是封闭的,这样的操作保证了通道的专有性,避免其他设备的干扰。
在传输速率方面,PCI Express总线利用串行的连接特点将能轻松将数据传输速度提 到一个很高的频率,达到远超出PCI总线的传输速率。PCI Express的接口根据总线位宽不同而有所差异,包括x1、x4、x8以及x16(x2模式将用于内部接口而非插槽模式),其中X1的传输速度为 250MB/s,而X16就是等于16倍于X1的速度,即是4GB/s。与此同时,PCI Express总线支持双向传输模式,还可以运行全双工模式,它的双单工连接能提供更高的传输速率和质量,它们之间的差异跟半双工和全双工类似。因此连接的每个装置都可以使用最大带宽,PCI Express接口设备将有着比PCI设备优越的多的资源可用。
除了这些,PCI Express设备能够支持热拔插以及热交换特性,支持的三种电压分别为+3.3V、3.3Vaux以及+12V。考虑到现在显卡功耗的日益上涨,PCI Express而后在规范中改善了直接从插槽中取电的功率限制,16x的最大提供功率达到了70W,比AGP 8X接口有了很大的提高。基本可以满足未来中高端显卡的需求。这一点可以从AGP、PCI Express两个不同版本的6600GT上就能明显地看到,后者并不需要外接电源。
可以看到PCI Express只是南桥的扩展总线,它与操作系统无关,所以也保证了它与原有PCI的兼容性,也就是说在很长一段时间内在主板上PCI Express接口将和PCI接口共存,这也给用户的升级带来了方便。由此可见,PCI Express最大的意义在于它的通用性,不仅可以让它用于南桥和其他设备的连接,也可以延伸到芯片组间的连接,甚至也可以用于连接图形芯片,这样,整个I/O系统将重新统一起来,将更进一步简化计算机系统,增加计算机的可移植性和模块化。PCI Express已经为PC的未来发展重新铺设好了路基,下面就要看PCI Express产品的应用情况了。
硬盘的总线
一般有SCSI、ATA、SATA等几种。
SCSI(Small Computer System Interface),一种用于计算机和智能设备之间(硬盘、软驱、光驱、打印机、扫描仪等)系统级接口的独立处理器标准。 SCSI是一种智能的通用接口标准。它是各种计算机与外部设备之间的接口标准。SCSI具有以下特点:SCSI可支持多个设备;SCSI还允许在对一个设备传输数据的同时,另一个设备对其进行数据查找,SCSI占用CPU极低;SCSI设备还具有智能化,SCSI卡自己可对CPU指令进行排队;最快的SCSI总线有160MB/s的带宽。
ATA技术是一个关于IDE(Integrated Device Electronics)的技术规范族。最初,IDE只是一项企图把控制器与盘体集成在一起为主要意图的硬盘接口技术。 随着IDE/EIDE得到的日益广泛的应用,全球标准化协议将该接口自诞生以来使用的技术规范归纳成为全球硬盘标准,这样就产生了ATA(Advanced Technology Attachment)。
SATA全称是Serial Advanced Technology Attachment(串行高级技术附件,一种基于行业标准的串行硬件驱动器接口),由Intel、IBM、Dell、APT、Maxtor和Seagate公司共同提出的硬盘接口规范,主要用来后硬盘等大容量存储器的连接。它具有支持热插拔,传输速度快,执行效率高等优点。Serial ATA采用串行连接方式,串行ATA总线使用嵌入式时钟信号,具备了更强的纠错能力,与以往相比其最大的区别在于能对传输指令(不仅仅是数据)进行检查,如果发现错误会自动矫正,这在很大程度上提高了数据传输的可靠性。串行接口还具有结构简单、支持热插拔的优点。 串口硬盘是一种完全不同于并行ATA的新型硬盘接口类型,由于采用串行方式传输数据而知名。相对于并行ATA来说,就具有很多的优势。首先,Serial ATA以连续串行的方式传送数据,一次只会传送1位数据。这样能减少SATA接口的针脚数目,使连接电缆数目变少,效率也会更高。实际上,Serial ATA 仅用四支针脚就能完成所有的工作,分别用于连接电缆、连接地线、发送数据和接收数据,同时这样的架构还能降低系统能耗和减小系统复杂性。
小型计算机系统接口(SCSI)
小型计算机系统接口是一套用于在计算机与外围设备之间进行物理连接和数据传输的标准。
串行ATA(SATA)
磁盘、光驱 EIDE 的替代品
SATA 1.0 速度为 150 MB/s
串行(1 位数据路径),点到点专用链路
薄型,1 米电缆长度
每根电缆一台设备
支持固定连接或热插拔
串行连接SCSI (SAS)
SAS 支持小尺寸、更长的布线距离、更好的寻址能力,并兼容 SCSI
SAS 1.0 在SCSI 和 RAID 控制器中替换 Ultra320 SCSI
全双工、双口、点到点连接
更高的带宽
Ultra320
- 每通道 320 MBps
SAS
– 8 端口,每个端口支持高达 3 Gbps 的速率
宽端口(4 口) – 12 Gbps
两个宽端口(8 口)– 24 Gbps
受到PCI-X/PCI 总线速度的限制
更大的驱动支持
基于 SCSI 的产品 - 每通道 14 个驱动器
基于 SAS 的产品 - 每四个端口 72 个驱动器
本文转载自: http://wangboxyk.cn/post/Zongxian-Bus-QPI-Memory.html
做这个项目的初衷是因为我去年在微信卖老家水果,好多朋友下单后都问我快递单号,每天发货后我都要挨个甄别这个人是哪个快递信息,很麻烦一部小心就搞错了。基于这件小事我有了自助快递查询的这个想法。将发货的快递信息导入到我的系统里,用户访问我的系统,通过输入手机号就可以查看自己的快递物流信息。 项目是去年8月写的,一直搁浅在哪,最近无意间翻看我发的那篇文章自助快递单号查询阅读量竟然都1.8w了,有图有真相。
这着实让我很震惊,看来自助快递查询这块确实是个热点。今天我就讲一下我手撸的快递查询系统。
项目地址:github.com/hellowHuaai… 有兴趣的可以直接下载源码,觉得项目不错的伙伴记得点个star,谢谢啦!
项目涉及到的技术栈有:
创建entity 创建快递单实体类,属性包括id,用户名(userName),电话(phone),快递单号(kuaidiNo),快递公司(company),数据创建时间(createTime)。代码如下:
@Data
@Builder
public class KuaiDi {
private Integer id;
/* 收件人姓名 */
private String userName;
/**收件人电话*/
private String phone;
/* 快递单号*/
private String kuaidiNo;
/*快递公司名称(拼音)*/
private String company;
/*订单创建时间*/
private Date createTime;
public KuaiDi(Integer id, String userName, String phone, String kuaidiNo, String company, Date createTime) {
this.id = id;
this.userName = userName;
this.phone = phone;
this.kuaidiNo = kuaidiNo;
this.company = company;
this.createTime = createTime;
}
public KuaiDi(Integer id, String userName, String phone, String kuaidiNo, String company) {
this.id = id;
this.userName = userName;
this.phone = phone;
this.kuaidiNo = kuaidiNo;
this.company = company;
}
}
service,mapper是常规的增删查改操作,就是保存快递的基本信息在数据库中,并可以对数据进行简单的维护功能。详细可参考项目源码。接下来看核心代码。
查询快递信息 快递的基本信息存入数据库,然后就是通过这些信息查询快递的详细物流信息。这里我做过很多尝试,想直接调用一些快递公司的快递信息查询接口,但是都发现接口都有session,当session失效后就无法查询到数据或者就查询到的数据不正确。最终在网上找到一种付费的方案,使用快递100接口。www.kuaidi100.com/ 查询快递的demo代码如下:
public class SynQueryDemo {
/**
* 实时查询请求地址
*/
private static final String SYNQUERY_URL = "http://poll.kuaidi100.com/poll/query.do";
private String key; //授权key
private String customer; //实时查询公司编号
public SynQueryDemo(String key, String customer) {
this.key = key;
this.customer = customer;
}
/**
* 实时查询快递单号
* @param com 快递公司编码
* @param num 快递单号
* @param phone 手机号
* @param from 出发地城市
* @param to 目的地城市
* @param resultv2 开通区域解析功能:0-关闭;1-开通
* @return
*/
public String synQueryData(String com, String num, String phone, String from, String to, int resultv2) {
StringBuilder param = new StringBuilder("{");
param.append("\"com\":\"").append(com).append("\"");
param.append(",\"num\":\"").append(num).append("\"");
param.append(",\"phone\":\"").append(phone).append("\"");
param.append(",\"from\":\"").append(from).append("\"");
param.append(",\"to\":\"").append(to).append("\"");
if(1 == resultv2) {
param.append(",\"resultv2\":1");
} else {
param.append(",\"resultv2\":0");
}
param.append("}");
Map<String, String> params = new HashMap<String, String>();
params.put("customer", this.customer);
String sign = MD5Utils.encode(param + this.key + this.customer);
params.put("sign", sign);
params.put("param", param.toString());
return this.post(params);
}
/**
* 发送post请求
*/
public String post(Map<String, String> params) {
StringBuffer response = new StringBuffer("");
BufferedReader reader = null;
try {
StringBuilder builder = new StringBuilder();
for (Map.Entry<String, String> param : params.entrySet()) {
if (builder.length() > 0) {
builder.append('&');
}
builder.append(URLEncoder.encode(param.getKey(), "UTF-8"));
builder.append('=');
builder.append(URLEncoder.encode(String.valueOf(param.getValue()), "UTF-8"));
}
byte[] bytes = builder.toString().getBytes("UTF-8");
URL url = new URL(SYNQUERY_URL);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(3000);
conn.setReadTimeout(3000);
conn.setRequestMethod("POST");
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setRequestProperty("Content-Length", String.valueOf(bytes.length));
conn.setDoOutput(true);
conn.getOutputStream().write(bytes);
reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
String line = "";
while ((line = reader.readLine()) != null) {
response.append(line);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (null != reader) {
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return response.toString();
}
}
上面的代码就是通过java代码调用kuaidi100的查询接口,这个查询接口会通过快递单号自动识别快递是属于哪个快递公司,然后调用对应快递公司接口获取响应数据。付费购买接口使用权其实就是生成一个授权key和实时查询公司编号customer,在线调用会做身份认证。这样就可以获取快递信息的json数据了。我已经购买了100块大洋的接口使用权,大家可直接调用快递查询接口。
controller代码 快递信息增删查改的controller就不在列了,这里主要看下我对查询快递的接口进行了一次包装处理。代码如下:
@RestController
public class KuaiDiQueryController {
@Autowired
private KuaiDiService kuaiDiService;
@Autowired
private KuaiDiQueryService kuaiDiQueryService;
/**
* 返回json数据
* @param com
* @param no
* @return
*/
@GetMapping("/getKuaiDiInfoByJson")
@ResponseBody
public String queryKuadiInfoByJson(String com, String no) {
return kuaiDiQueryService.synQueryData(com, no,"", "", "", 0);
}
@GetMapping("/getKuaiDiInfoByPhone")
@ResponseBody
public Response queryKuaidiByPhone(String phone){
Response response = new Response();
if(StringUtils.isNotEmpty(phone)){
List<ResponseData> responseDataList = new ArrayList<>();
// 1.通过手机号查询下面的所有订单号
List<KuaiDi> kuaiDiList = kuaiDiService.getList("", phone);
if(!CollectionUtils.isEmpty(kuaiDiList)){
kuaiDiList.forEach(kuaiDi -> {
// 2.依次查出所有的订单号
String responseDataStr = kuaiDiQueryService.synQueryData(kuaiDi.getCompany(), kuaiDi.getKuaidiNo(),"", "", "", 0);
ResponseData responseData = CommonUtils.convertJsonStr2Object(responseDataStr);
responseDataList.add(responseData);
});
}
// 3.组装数据返回给前台
response.setDataList(responseDataList);
}
return response;
}
}
前端展示主要包括两个页面,管理员页面和客户页面。管理员页面功能包括快递信息的新增,修改,删除,分页查询,在线快递物流信息接口。客户页面包括快递信息的分页查询和在线快递物流信息接口。所以主要看一下管理员页面。
html页面 html页面引入了jQuery和Bootstrap,jQuery已经过时了,但是使用起来还是很方便的。
<html>
<head>
<title>快递单号查询</title>
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://cdn.bootcss.com/bootbox.js/4.4.0/bootbox.min.js"></script>
<link href="https://cdn.bootcss.com/bootstrap-table/1.11.1/bootstrap-table.min.css" rel="stylesheet">
<script src="https://cdn.bootcss.com/bootstrap-table/1.11.1/bootstrap-table.min.js"></script>
<script src="https://cdn.bootcss.com/bootstrap-table/1.11.1/locale/bootstrap-table-zh-CN.min.js"></script>
...
</head>
<body>
<div class="container-fluid">
<div class="row">
<nav class="navbar navbar-inverse navbar-fixed-top">
<a class="navbar-brand" href="http://mhtclub.com">我的个人主页</a>
<button class="navbar-toggle" data-toggle="collapse" data-target="#collapseMenu">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<div class="collapse navbar-collapse" id="collapseMenu">
<ul class="nav navbar-nav">
<li class="nav-li">
<a href="https://github.com/hellowHuaairen/kuaidi" target="_blank">本项目github</a>
</li>
</ul>
</div>
</nav>
</div>
<h1 class="page-header">
快递单号自助查询
</h1>
<!-- 查询工具栏 -->
<div class="form-inline">
<div class="form-group">
<label for="queryNameText">收件人姓名:</label>
<input id="queryNameText" class="form-control input-sm">
</div>
<div class="form-group">
<label for="queryPhoneText">收件人电话:</label>
<input id="queryPhoneText" class="form-control input-sm">
</div>
<button class="btn btn-primary btn-sm" id="queryBtn">查询</button>
<button class="btn btn-primary btn-sm" id="resetBtn">重置</button>
<button class="btn btn-primary btn-sm" id="addBtn">新增</button>
</div>
<hr>
<table id="testTable"></table>
<!-- 查看订单信息模态窗 -->
<div class="modal fade" id="viewModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title">订单信息</h4>
</div>
<div class="modal-body" id="viewDataList"></div>
<div class="modal-footer">
<button class="btn btn-default" data-dismiss="modal">关闭</button>
</div>
</div>
</div>
</div>
<!-- 新增模态窗 -->
<div class="modal fade" id="addModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title">新增信息</h4>
</div>
<div class="modal-body">
<div class="form-inline">
</div>
<div class="form-group">
<label for="addNameText">收件人姓名:</label>
<input id="addNameText" class="form-control input-sm">
</div>
<div class="form-group">
<label for="addPhoneText">收件人电话:</label>
<input id="addPhoneText" class="form-control input-sm">
</div>
<div class="form-group">
<label for="addKuaiDiNoText">快递单号:</label>
<input id="addKuaiDiNoText" class="form-control input-sm">
</div>
<div class="form-group">
<label for="addCompanyText">快递公司(拼音):</label>
<input id="addCompanyText" class="form-control input-sm">
</div>
</div>
<div class="modal-footer">
<button class="btn btn-default" data-dismiss="modal">关闭</button>
<button class="btn btn-primary" id="saveAdd">保存</button>
</div>
</div>
</div>
</div>
<!-- 修改模态窗 -->
<div class="modal fade" id="modifyModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title">修改信息</h4>
</div>
<div class="modal-body">
<div class="form-inline">
</div>
<div class="form-group">
<label for="modifyNameText">收件人姓名:</label>
<input id="modifyNameText" class="form-control input-sm">
</div>
<div class="form-group">
<label for="modifyPhoneText">收件人电话:</label>
<input id="modifyPhoneText" class="form-control input-sm">
</div>
<div class="form-group">
<label for="modifyKuaiDiNoText">快递单号:</label>
<input id="modifyKuaiDiNoText" class="form-control input-sm">
</div>
<div class="form-group">
<label for="modifyCompanyText">快递公司(拼音):</label>
<input id="modifyCompanyText" class="form-control input-sm">
</div>
</div>
<div class="modal-footer">
<button class="btn btn-default" data-dismiss="modal">关闭</button>
<button class="btn btn-primary" id="saveModify">保存</button>
</div>
</div>
</div>
</div>
</div> <!-- container-fluid -->
<script type="text/javascript" src="js/admin.js"></script>
</body>
</html>
admin.js 这里说明一下前端我引入的jQuery,包括新增,修改,删除,查询的功能,查询事件添加了对电话号码的必填校验。
var $testTable = $('#testTable');
$testTable.bootstrapTable({
url: 'getList',
queryParams: function (params) {
return {
offset: params.offset,
limit: params.limit,
userName: $('#queryNameText').val(),
phone: $('#queryPhoneText').val()
}
},
columns: [{
field: 'id',
title: '编号'
}, {
field: 'userName',
title: '收件人姓名'
}, {
field: 'phone',
title: '收件人电话'
}, {
field: 'company',
title: '快递公司'
},{
field: 'kuaidiNo',
title: '快递单号',
formatter: function (value, row, index) {
return [
'<a onclick="kuaidiRecordInfo(' + "'" + row.kuaidiNo + "','" + row.company + "')" + '">' + row.kuaidiNo +'</a>',
].join('');
},
}, {
formatter: function (value, row, index) {
return [
'<a href="javascript:modifyKuaiDi(' + row.id + ",'" + row.userName + "'," + row.phone + ",'" + row.kuaidiNo + "'" + ')">' +
'<i class="glyphicon glyphicon-pencil"></i>修改' +
'</a>',
'<a href="javascript:delKuaiDi(' + row.id + ')">' +
'<i class="glyphicon glyphicon-remove"></i>删除' +
'</a>'
].join('');
},
title: '操作'
}],
striped: true,
pagination: true,
sidePagination: 'server',
pageSize: 10,
pageList: [5, 10, 25, 50, 100],
rowStyle: function (row, index) {
var ageClass = '';
if (row.age < 18) {
ageClass = 'text-danger';
}
return {classes: ageClass}
},
});
// 设置bootbox中文
bootbox.setLocale('zh_CN');
var titleTip = '提示';
function kuaidiRecordInfo(no, company) {
$('#viewModal').modal('show');
$.ajax({
type:'get',
url:'getKuaiDiInfoByJson?com='+ company +'&no=' + no,
cache:false,
dataType:'json',
success:function(result){
// 显示详细信息 发送请求通过单号
$("#viewDataList").empty();
console.log(result.data);
var dataList = result.data;
if(null != dataList){
$("#viewDataList").append('<li class="accordion-navigation"><a href="#kuaidi'+ '">快递单号:'+ result.nu +'</a><div id="kuaidi'+ '" class="content"></div></li>');
$("#kuaidi").append('<section class="result-box"><div id="resultTop" class="flex result-top"><time class="up">时间</time><span>地点和跟踪进度</span></div><ul id="reordList'+'" class="result-list sortup"></ul></section>');
for(var i=0;i<dataList.length; i++){
var kuaiRecodList = dataList[i];
if( i == 0){
$("#reordList").append('<li class="last finish"><div class="time"> '+ kuaiRecodList.ftime + '</div><div class="dot"></div><div class="text"> '+ kuaiRecodList.context +'</div></li>');
}else{
$("#reordList").append('<li class=""><div class="time"> '+ kuaiRecodList.ftime + '</div><div class="dot"></div><div class="text"> '+ kuaiRecodList.context +'</div></li>');
}
}
}
}
});
}
// 验证姓名和地址是否为空
function verifyNameAndAddress(name, address) {
if (name != '' && address != '') {
return true;
}
return false;
}
function nullAlert() {
bootbox.alert({
title: titleTip,
message: '所有项均为必填!'
});
}
// 点击查询按钮
$('#queryBtn').click(function () {
var age = $('#queryAgeText').val();
// 刷新并跳转到第一页
$testTable.bootstrapTable('selectPage', 1);
});
// 点击重置按钮,清空查询条件并跳转回第一页
$('#resetBtn').click(function() {
$('.form-group :text').val('');
$testTable.bootstrapTable('selectPage', 1);
});
// 用于修改服务器资源
function exchangeData(path, id, userName, phone, kuaiDiNo, company) {
$.ajax({
url: path,
type: 'post',
data : {
id: id,
userName: userName,
phone: phone,
kuaiDiNo: kuaiDiNo,
company: company
},
success: function(res) {
bootbox.alert({
title: titleTip,
message: res.message
});
// 在每次提交操作后返回首页
$testTable.bootstrapTable('selectPage', 1);
}
});
}
// 新增
$('#addBtn').click(function() {
$('#addNameText').val('');
$('#addPhoneText').val('');
$('#addKuaiDiNoText').val('');
$('#addCompanyText').val('');
$('#addModal').modal('show');
});
$('#saveAdd').click(function() {
$('#addModal').modal('hide');
bootbox.confirm({
title: titleTip,
message: '确认增加?',
callback: function (flag) {
if (flag) {
var userName = $('#addNameText').val();
var phone = $('#addPhoneText').val();
var kuaiDiNo = $('#addKuaiDiNoText').val();
var company = $('#addCompanyText').val();
if (verifyNameAndAddress(userName, kuaiDiNo)) {
exchangeData('addKuaiDi', null, userName, phone, kuaiDiNo, company);
} else {
nullAlert();
}
}
}
});
});
var mid;
// 修改
function modifyKuaiDi(id, name, age, address) {
mid = id;
$('#modifyNameText').val(name);
$('#modifyPhoneText').val(age);
$('#modifyKuaiDiNoText').val(address);
$('#modifyCompanyText').val(address);
$('#modifyModal').modal('show');
}
$('#saveModify').click(function() {
$('#modifyModal').modal('hide');
bootbox.confirm({
title: titleTip,
message: '确认修改?',
callback: function (flag) {
if (flag) {
var userName = $('#modifyNameText').val();
var phone = $('#modifyPhoneText').val();
var kuaiDiNo = $('#modifyKuaiDiNoText').val();
var company = $('#modifyCompanyText').val();
if (verifyNameAndAddress(userName, phone)) {
exchangeData('modifyKuaiDi', mid, userName, phone, kuaiDiNo, company);
} else {
nullAlert();
}
}
}
});
});
// 删除
function delKuaiDi(id) {
bootbox.confirm({
title: titleTip,
message: '确认删除?',
callback: function(flag) {
if (flag) {
exchangeData("delKuaiDi", id);
}
}
});
修改配置文件 项目配置文件src/resources/application.properties,根据实际情况修改对应的数据库连接信息。
#MySQL配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql://localhost:3306/kuaidi?useUnicode=true&characterEncoding=UTF-8
spring.datasource.username=root #数据库账号
spring.datasource.password=root #数据库密码
#MyBatis日志配置
mybatis.mapperLocations=classpath:mapper/*.xml
mybatis.config-location=classpath:/config/mybatis-config.xml
#端口配置
server.port=8082
# 定位模板的目录
spring.mvc.view.prefix=classpath:/templates/
# 给返回的页面添加后缀名
spring.mvc.view.suffix=.html
创建数据库表 表结构如下:
DROP TABLE IF EXISTS `kuaidi`;
CREATE TABLE `kuaidi` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '收件人姓名',
`phone` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '收件人电话',
`kuaidi_no` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '快递单号',
`company` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '快递公司名称拼音',
`create_time` datetime(0) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
运行 将项目导入Idea工具,找到com.wangzg.kuaidi.KuaiDiApplication文件,执行main方法即可,如下图:
上传安装包 在服务器创建/usr/myworkspace,执行下面命令可直接创建:
mkdir -p /usr/myworkspace
复制代码
下载相关文件,上传到服务器/usr/myworkspace。下载地址:github.com/hellowHuaai… 文件主要包括:
初始化数据库 打开Navicat工具,选中数据库,右键选择运行SQL文件...,具体操作,这样数据库就初始化完成。
运行项目
在服务器/usr/myworkspace目录下,执行如下命令,即可运行项目:
chmod +x *.sh #给所有 .sh文件添加执行权限
./start.sh
Docker 容器化部署项目,需要创建一个 mysql 的容器,创建kuaidi的容器,再初始化一下数据库。
创建数据库容器 代码如下:
docker run -d --name mysql5.7 -e MYSQL_ROOT_PASSWORD=root -it -p 3306:3306 daocloud.io/library/mysql:5.7.7-rc
导入数据库脚本 数据库脚本kuaidi.sql内容如下:
create DATABASE kuaidi;
use kuaidi;
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `kuaidi`;
CREATE TABLE `kuaidi` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '收件人姓名',
`phone` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '收件人电话',
`kuaidi_no` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '快递单号',
`company` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '快递公司名称拼音',
`create_time` datetime(0) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
然后执行下面命令,就可以导入kuaidi.sql脚本:
docker exec -i mysql5.7 mysql -uroot -proot mysql < kuaidi.sql
创建kuaidi容器 执行下面命令就可以创建容器:
docker run -d -p 9082:8082 -v application.properties:/home/conf/application.properties --name kuaidi1 huaairen/kuaidi:latest
注:application.properties文件为项目的配置文件,在src/main/resources目录下;huaairen/kuaidi:latest是我打包好的镜像,直接下载就可以。
项目功能还特别简陋,很多功能需要开发和完善。如果你也遇到类似的问题我们可以一起讨论,合作共赢哦!
作者:不安分的猿人
链接:https://juejin.im/post/5e9313ece51d4546c62f9ac4
来源:掘金
*请认真填写需求信息,我们会在24小时内与您取得联系。