node网络编程基础
来源:互联网 发布:育知同创大连 编辑:程序博客网 时间:2024/06/05 14:54
Node提供丰富的网络编程模块
Node模块协议netTCPdgramUDPhttpHTTPhttpsHTTPSTCP服务
TCP服务事件分为下面两类 (1)、服务器事件 对于通过net.createServer()创建的服务器而言,它是一个EventEmitter实例,自定义事件有以下几种: listening:在调用listen()绑定端口或Domain Socket后触发,简写为server.listen(port, listener),通过第二个参数传入。 connection:每个客户端套接字连接到服务器时触发,简洁写法为通过net.createServer(),最后一个参数传递。close:当服务器关闭时触发,在调用server.close()后,服务器将停止接受新的套接字连接,但保持当前存在的连接,等待所有连接断开后,会触发该事件。 error:当服务器发生异常时,将会触发该事件。 (2)、连接事件服务器可以同时与多个客户端保持连接,对于每个连接而言是典型的可写可读Stream对象。Stream对象可以用于服务端和客户端之间的通信,既可以通过data事件从一端读取另一端发来的数据,也可以通过write()方法从一端向另一端发送数据。
data:当一端调用write()发送数据时,另一端会触发data事件,事件传递的数据就是write()发送的数据 end:当连接中的任意一端发送FIN数据时,将会触发该事件。 connect:该事件用于客户端,当套接字与服务器连接成功时会触发。 drain:当任意一端调用write()发送数据时,当前这端触发该事件。 error:当异常发送 close:当套接字完全关闭时触发 timeout:当一定时间后连接不再活跃时,触发该事件通知用户该连接被闲置了。 TCP针对网络中的小数据包有一定优化策略:Nagle算法,当数据达到一定量后才触发。UDP服务
UDP称为用户数据包协议,其不是面向连接的服务。Node中UDP只是一个EventEmitter实例,而非Stream的实例,具备以下自定义事件: (1) message:当UDP套接字监听网卡端口后,接受消息时触发,触发携带的数据为消息Buffer对象和一个远程地址信息。 (2) listening:当UDP套接字开始侦听时触发该事件。 (3) close:调用close()方法时触发该事件,并不再触发message事件。若需再次触发message事件,需要重新绑定。 (4) error:当异常发生时触发,若不监听直接抛出,使进程退出。HTTP服务
Node中http模块继承自tcp服务器(net模块),它能与多个客户端保持连接,由于其不为每个连接创建线程,保持很低的内存占用,所以能实现高并发。HTTP服务和TCP服务区别在于,在开启keepalive之后,一个TCP会话可以用于多次请求和响应。TCP服务以connection为单位进行服务,HTTP服务以request单位进行服务。http模块是将connection到request的过程进行封装。 http模块将连接所用的套接字的读写抽象为ServerRequest和ServerResponse对象,分别对应请求和响应操作。 (1) HTTP请求 对于TCP连接的读操作,http模块将其封装为ServerRequest对象。如报头部分req.method、req.url、req.headers,报文体数据部分抽象为一个只读的流对象,若业务逻辑需要读取报文体中的数据,则需要这个数据流结束后才能进行操作。 (2) HTTP响应 HTTP响应封装了底层连接的写操作,可以将其看成一个可写的流对象。 响应报文头部信息方法:res.setHeader()和res.writeHeader()方法,可以多次setHeader进行设置,但必须调用writeHeader写入连接才生效。 报文体部分方法:res.write()和res.end()方法 (3) HTTP服务端事件 connection:客户端与服务端建立TCP连接时,触发一次connection事件 request:建立TCP连接后,http模块底层将数据流中抽象出HTTP请求和HTTP响应,当请求数据发送到服务端,在解析出HTTP请求头后触发该事件;在res.end()后,TCP连接可用于下一次请求。 close:调用server.close方法停止接收新的连接,已有的连接都断开时触发该事件。checkContinue:某些客户端在发送较大数据时,先发送一个头部带有Expect: 100-continue的请求到服务器,服务触发该事件; connect:当客户端发起CONNECT请求时触发 upgrade:当客户端要求升级连接的协议时,需要和服务端协商,客户端会在请求头中带上Updagrade字段 clientError:连接的客户端发送错误,错误传到服务端此时触发该事件 (4) HTTP客户端 http模块提供http.request(options, connect),用于构造HTTP客户端。 HTTP客户端和服务端类似,在ClientRequest对象中,它的事件叫做response,ClientRequest在解析响应报文的时,一解析完响应头就触发response事件,同时传递一个响应对象ClientResponse供操作,后续响应报文以只读流的方式提供。 (5) HTTP客户端事件 response:与服务端的request事件对应的客户端在请求发出后得到响应时触发该事件。 socket:当底层连接池中建立的连接分配给当前请求对象时触发; connect:当客户端向服务器发送CONNECT请求时,若服务端响应了200状态码,客户端将会触发该事件。 upgrade:客户端享服务端发送Upgrade请求时,若服务端响应了101 Switching Protocols状态,客户端将会触发该事件。 continue:客户端向服务端发起Expect: 100-continue头信息后,以试图发送较大数据,若服务端响应100 continue状态,服务端将触发该事件WebSocket服务
WebSocket最早是作为HTML5重要特性出现的,相比HTTP有以下优点: (1) 客户端和服务端只建立一次TCP连接,可以使用更少的连接 (2) WebSocket服务端可以推送数据到客户端,这远比HTTP请求响应模式更灵活、更高效 (3) 更轻量级的协议头,减少数据传输 Node中没有内置WebSocket的库,但社区的ws模块封装了WebSocket的底层实现如著名的socket.ioNode.js为javascript语言提供了一个在服务端运行的平台,它以其事件驱动,非阻塞I/O机制使得它本身非常适合开发运行在在分布式设备上的I/O密集型应用,分布式应用要求Node.js必须对网络通信支持友好,事实上Node.js也提供了非常强大的网络通信功能,本文就主要探讨如何使用Node.js进行网络编程。
首先,网络编程的概念是"使用套接字来达到进程间通信的目的"。通常情况下,我们要使用网络提供的功能,可以有以下几种方式:
1.使用应用软件提供的网络通信功能来获取网络服务,最著名的就是浏览器,它在应用层上使用http协议,在传输层基于TCP协议;
2.在命令行方式下使用shell 命令获取系统提供的网络服务,如telnet,ftp等;
3.使用编程的方式通过系统调用获取操作系统提供给我们的网络服务。
本文主要目的就是要探讨如何在Node.js中通过编程来获取操作系统提供的网络服务来达到不同主机进程间通信。现在回过头来在看看网络编程的概念,这里涉及到一个套接字(socket)的概念,所谓套接字,实际上是两个不同进 程间进行通信的端口(这里的端口有别于IP地址中常用的端口),它是对网络层次模型中网络层及其下面各层操作的一个封装,为了让开发者能够使用各种语言调用操作系统提供的网络服务,在不同服务端语言中都使用了套接字这个概念,开发者只要获得一个套接字(socket),就可以使用套接字(socket)中各种方法来创建不同进程之间的连接进而达到通信目的。通常情况下,我们使用以下网络层次模型,
所谓的socket(套接字)就是将操作系统中对于传输层及其以下各层中对于网络操作的处理进行了封装,然后提供一个socket对象,供我们在应用程序中调用这个对象及其方法来达到进程间通信的目的。
基于以上概念,Node.js也提供了对socket的支持,它提供了一个net模块用来处理和TCP相关的操作,提供了dgram模块用来处理UDP相关操作,关于TCP和UDP的区别这里就不赘述了,属于老生常谈的话题了。
1.创建TCP服务端。
在Node.js中,可以很方便地创建一个socket服务端,利用如下函数
var server=net.createServer([options],[listener]);
其中net为我们引入的net模块,options参数为可选,它是一个对象,其中包含如下两个属性:
allowHalfOpen,该属性默认为false,这个时候如何TCP客户端发送一个FIN包的时候,服务端必须回送一个FIN包,这使得这个TCP连接两端同时关闭,这种情况下关闭后任何一方都不能再发送信息,而如果该属性为true,表示TCP客户端发送一个FIN包的时候,服务端不回发,这将导致TCP客户端关闭到服务端的通信,而服务端仍然可以向客户端发送信息。这种情况下如果要完全关闭这个TCP双向连接,则需要显式调用服务端socket的end方法。
pauseOnConnect,该属性默认为false,当它被设置为true的时候表示该TCP服务端与之相连接的客户端socket传输过来的数据将不被读取,即不会触发data事件。如果需要读取客户端传输的数据,可以使用socket的resume方法来设置该socket。
参数listener表示一个创建socket之后的回调函数,它有一个参数,表示当前创建的服务端socket,
function (socket) {
// to do sth..
}
最后这个方法返回的是被创建的服务器对象。
对于这个对象,它是继承了EventEmitter类,它具有几个重要的事件和方法,如下:
connection事件,用来监听客户端socket连接到这个TCP服务器的时候触发
server.on('connection',function(socket){ // to do sth...});
close事件,TCP服务器被关闭的时候触发。
server.on('close',function(){ console.log('TCP服务器被关闭');});
error事件,TCP连接出现错误的时候触发
listen方法,用来监听来自客户端的TCP连接请求。
下面是一个完整的创建TCP服务端的例子。
var net=require('net');var server=net.createServer(function(socket){ console.log('客户端和服务端建立连接'); server.getConnections(function(err,count){ console.log("当前连接数为%d",count); }); server.maxConnections=2; console.log('tcp最大连接数为%d',server.maxConnections);});server.on('error',function(e){ if(e.code=='EADDRINUSE'){ console.log('地址和端口被占用'); }});server.listen(2000,'localhost',function(){ //console.log('服务器端开始监听...'); var address=server.address(); console.log(address);});
这段代码创建了一个TCP服务端并将该服务端指定最多连接两个客户端,并监听本地的2000端口等待客户端连接,接着我们可以使用远程登录(Telnet基于TCP协议)来测试这个服务端,分别在两个命令行中输入
telnet loclhost 2000
结果如下:
当使用第三个命令行窗口进行登陆的时候,发现无法连接到服务端,因为这里我们设置了连接到服务端的TCP连接只能为最大两个。
2.创建TCP客户端并进行服务端与客户端的通信
创建一个独立的客户端需要调用net模块的Socket构造方法,如下:
var client=new net.Socket([options]);
这个构造函数接受一个可选的参数,是一个对象,它里面包含如下几个属性:
fd:用来制定一个已经存在的socket文件描述符来创建socket;
allowHalfOpen:作用同上
readable和writeable,当使用fd来创建socket的时候指定该socket是否可读写,默认为false。
实际上该client就是一个独立的socket对象。这个socket对象通常具有如下比较重要的方法和属性:
connect方法,用来连接指定的TCP服务端。
socket.connect(port,[host],[listener])
write方法,向另外一端的socket写入数据
socket.write(data,[encoding],[callback])
其中data可以是字符串,也可以是Buffer数据,如果是字符串需要指定第二个参数用来指定其编码方式。第三个参数为回调函数。
以下是一个完整的创建TCP客户端的代码:
var net=require('net');var client=new net.Socket();client.setEncoding('utf8');client.connect(2000,'localhost',function(){ console.log('已连接到服务端'); client.write('hello!'); setTimeout(function(){ client.end('bye'); },10000);}); client.on('error',function(err){ console.log('与服务端连接或通信发生错误,错误编码为%s',err.code); client.destroy(); });client.on('data',function(data){ console.log('已接收到服务端发送的数据为:'+data);});
该段代码创建了一个TCP客户端,并且连接本地2000端口的服务器,向服务器发送hello数据,然后过十秒之后再发送bye,最后关闭该TCP客户端的连接。并且监听它的data事件,当收到服务端发送来的数据时打印出来。
与该客户端对应的一个TCP服务端代码如下:
var net=require('net');var server=net.createServer({allowHalfOpen:true});server.on('connection',function(socket){ console.log('客户端已经连接到服务器'); socket.setEncoding('utf8'); socket.on('data',function(data){ console.log('接收到客户端发送的数据为:'+data); socket.write('确认数据:'+data); }); socket.on('error',function(err){ console.log('与客户端通信过程中发生错误,错误码为%s',err.code); socket.destroy(); }); socket.on('end',function(){ console.log('客户端连接被关闭'); socket.end(); //客户端连接全部关闭的时候退出引用程序 server.unref(); }); socket.on('close',function(has_error){ if(has_error){ console.log('由于一个错误导致socket连接被关闭'); server.unref(); }else{ console.log('socket连接正常关闭'); } });});server.getConnections(function(err,count){ if(count==2){ server.close(); }});server.listen(2000,'localhost');server.on('close',function(){ console.log('TCP服务器被关闭');});
该服务端接收到客户端发送来的数据之后再回发回去,并且当连接到该TCP服务端的所有socket连接都断开时,自动退出应用程序。
运行这两段代码,结果如下:
服务端:
客户端
从以上我们可以看出,基于TCP连接的通信具有以下特点:
1)面向连接,必须建立连接后才能够互相通信;
2)TCP连接是一对一的,就是说在TCP中,一个客户端socket连接一个服务端socket,并且两者可以相互通信,通信是双向的。
3)TCP连接关闭的时候是可以只关闭一方的连接而保留单向通信;
4)一个特定的IP加端口可以连接多个TCP客户端,也可以通过编程指定连接上限。
3.创建UDP的客户端和服务端
在Node.js中,提供了dgram模块用来处理UDP相关的操作与调用,我们知道UDP是一种非连接不可靠但高效的传输协议,所以这里实际上创建一个TCP客户端和服务端在函数调用上是没有区别的,
采用dgram模块的createSocket方法,如下所示:
var socket=dgram.createSocket(type,[callback])
该方法有两个参数,分别如下:
type:采用的udp协议类型,可以是udp4或udp6,该参数必须
callback:创建完成之后的回调函数,该参数可选。回调函数中有两个参数
function (msg,rinfo) { // 回调函数代码}
msg为一个Buffer对象,表示接收到的数据,rinfo也是一个对象,表示发送者的信息,它含有如下信息:
address:发送者IP
port:发送者端口
family:发送者IP地址类型,如IPV4或IPv6
size:发送者发送信息的字节数
调用创建方法之后返回一个UDP scoket,它 拥有如下几个重要方法和事件:
message事件,当接收到发送来的信息的时候触发,如下:
socket.on('message',function (msg,rinfo){ // 回调函数代码});
bind方法:为该socket绑定一个端口和ip,如下:
socket.bind(port,[address],[callback])
listening事件,当第一次接收到一个UDP socket发送来的数据的时候触发,如下:
socket.on('listening',function (){ // 回调函数代码});
send方法,向指定udp socket发送信息。如下:
socket.send(buf,offset,length,port,address,[callback])
该方法有六个参数,buf是一个Buffer对象或者字符串,表示要发送的数据,offset表示从哪个字节开始发送,length表示发送字节的长度,port表示接收socket的端口,address表示接收socket的IP,callback为回调函数,其中callback为可选的之外,其他参数都是必须的。
以下创建一个UDP客户端的完整代码
var dgram=require('dgram');var message=new Buffer('hello');var client=dgram.createSocket('udp4');client.send(message,0,message.length,2001,"localhost",function(err,bytes){ if(err) console.log('数据发送失败'); else console.log("已发送%d字节数据",bytes);});client.on("message",function(msg,rinfo){ console.log("已接收到服务端发送的数据%s",msg); console.log("服务器地址信息为%j",rinfo); client.close();});client.on("close",function(){ console.log("socket端口被关闭");});
这段代码创建一个客户端socket并向另外一个客户端发送hello,并将其他socket发送来的数据打印出来,然后关闭客户端socket。
下面是相应的服务端socket的代码:
var dgram=require('dgram');var server=dgram.createSocket('udp4');server.on("message",function(msg,rinfo){ console.log('已接收到客户端发送的数据为'+msg); console.log("客户端地址新信息为%j",rinfo); var buff=new Buffer("确认信息"+msg); server.send(buff,0,buff.length,rinfo.port,rinfo.address); setTimeout(function(){ server.unref(); },10000);});server.on("listening",function(){ var address=server.address(); console.log("服务器开始监听,地址信息为%j",address);});server.bind(2001,'localhost');
该段代码创建一个服务端socket,并将它绑定到本地2001端口上,监听它的listening事件,打印出客户端信息,并将接收到的客户端信息打印出来并回送给客户端,同时在10秒之后如果所有客户端关闭则退出应用程序。
结果如下,客户端:
服务端:
从上面我们可以看出,与TCP不同的是,我们不需要专门创建一个socket监听客户端连接,客户端也不用经过连接而是直接向指定服务端socket发送信息,这证明了socket是无连接的。
同时,对于udp来讲,它的无连接特性使得它能够一对一,多对多,一对多和多对一,这和TCP连接的一对一是有很大区别的。基于UDP这种特性,我们可以使用UDP来实现数据的广播和组播。
4.使用UDP来进行数据广播
在dgram模块中,使用socket的setBroadcast方法开启该socket的广播,如下:
socket.setBroadcast(flag)
其中flag默认为false,表示不开启广播,true表示开启。
所谓广播,指的是一个主机向本网络的其他主机上发送数据,本网络内的其他主机都可以接收到,同时按照对IP地址的分类,对于A,B,C类地址来讲,其所在网段的主机号全1的地址就是一个广播地址,我们需要将该数据广播到这个地址上,而不是直接发送给某个指定IP的主机。
基于以上认识,我们编写一个广播服务端如下:
var dgram=require('dgram');var server=dgram.createSocket("udp4");server.on("message",function(msg){ var buff=new Buffer("已接收到客户端数据为:"+msg); server.setBroadcast(true); server.send(buff,0,buff.length,2002,"192.168.56.255");});server.bind(2001,"192.168.56.1");
该段代码创建一个服务端socket,绑定IP和端口,接受客户端数据,并将客户端数据广播到本网络的广播地址上。
客户端代码如下:
var dgram=require('dgram');var client=dgram.createSocket('udp4');client.bind(2002,'192.168.56.2');var buf=new Buffer("client");client.send(buf,0,buf.length,2001,'192.168.56.1');client.on("message",function(msg,rinfo){ console.log('接收到的服务端数据为%s',msg);});
这段代码表示创建一个客户端socket,并为该socket绑定IP和端口,同时向服务端发送数据,并将接收到的数据打印出来。
在本地主机上运行服务端代码,并将客户端代码部署在不同主机上并修改客户端socket的IP地址和端口,则任意客户端发送来的消息都会广播给所有和该服务器通信的客户端。
5.使用UDP进行组播
所谓组播是指任意主机都可以加入到一个组中,这个组的地址是一个特殊的D类IP地址,范围为224.0.0.0--239.255.255.255,发送者只需要将发送的数据发送给一个组播地址,那么所有加入改组的主机都可以收到发送者的数据(注意这里不是该网络上的所有主机)。
对于组播地址,通常如下:
•局部组播地址:224.0.0.0~224.0.0.255,这是为路由协议和其他用途保留的地址。
•预留组播地址:224.0.1.0~238.255.255.255,可用于全球范围(如Internet)或网络协议。
•管理权限组播地址:239.0.0.0~239.255.255.255,可供组织内部使用,类似于私有IP地址,不能用于Internet,可限制组播范围。
Node.js中使用addMembership来让主机加入到该组中,从而实现IP组播,如下:
socket.addMembership(multicastAddress, [multicastInterface])
该方法第一个参数是组播地址,第二个参数可选,表示socket需要加入的网络接口IP地址,如果不指定,则会加入到所有有效的网络接口中。
一个socket加入组播组之后,可以使用dropMembership退出该组播组,如下:
socket.dropMembership(multicastAddress, [multicastInterface])
下面是一个完整的发送组播数据的udp服务端
var dgram=require('dgram');var server=dgram.createSocket('udp4');server.on('listening',function(){ server.setMulticastTTL(128); server.addMembership('230.185.192.108');});setInterval(broadCast,1000);function broadCast(){ var buf=new Buffer(new Date().toLocaleString()); server.send(buf,0,buf.length,8088,'230.185.192.108');}
这段代码创建一个发送组播数据的socket服务端,加入组播组230.185.192.108,并每隔一秒向该组发送服务端时间信息。
对应客户端代码如下:
var PORT=8088;var HOST="192.168.56.2";var dgram=require('dgram');var client=dgram.createSocket('udp4');client.on('listening',function(){ client.addMembership('230.185.192.108');});client.on('message',function(msg,remote){ console.log(msg.toString());});client.bind(PORT,HOST);
客户端创建一个socket并绑定自己的端口和IP,接收来自服务端发送的数据。在listening事件中将它加入该组播组之中。
在本地主机上运行服务端代码,在不同的网络主机上运行客户端代码并修改其IP和端口为不同主机自己的IP和端口,所有加入到该组播的客户端都会收到服务端发送的时间信息。
6.总结
综上所述,在Node.js中,我们把可以使用net模块来创建基于TCP的服务端和客户端的连接和通信,同时也可以使用dgram模块来处理基于UDP客户端和服务端的通信。
想知道如何在NodeJS中使用socket编程?在NodeJS中有三种socket:1. TCP,2. UDP,3. Unix域套接字,本文主要介绍NodeJS中TCP的基本编程知识。
你可以创建两种类型的TCP套接字:1. 服务端,2. 客户端。服务端TCP监听来自客户端的连接请求,并使用TCP连接向客户端发送数据;客户端TCP连接到服务端并与服务器交互数据。客户端与服务端之间依靠套接字进行双向通信。
在NodeJS中使用TCP需要引用net模块。net模块是NodeJS中异步网络编程的封装,可以做很多事情,本文仅关注于如何使用该模块创建服务端与客户端的TCP套接字。
创建TCP服务端
下面是一个在NodeJS中创建TCP服务端套接字的简单例子,相关说明见代码注释。
var net = require('net');var HOST = '127.0.0.1';var PORT = 6969;// 创建一个TCP服务器实例,调用listen函数开始监听指定端口// 传入net.createServer()的回调函数将作为”connection“事件的处理函数// 在每一个“connection”事件中,该回调函数接收到的socket对象是唯一的net.createServer(function(sock) { // 我们获得一个连接 - 该连接自动关联一个socket对象 console.log('CONNECTED: ' + sock.remoteAddress + ':' + sock.remotePort); // 为这个socket实例添加一个"data"事件处理函数 sock.on('data', function(data) { console.log('DATA ' + sock.remoteAddress + ': ' + data); // 回发该数据,客户端将收到来自服务端的数据 sock.write('You said "' + data + '"'); }); // 为这个socket实例添加一个"close"事件处理函数 sock.on('close', function(data) { console.log('CLOSED: ' + sock.remoteAddress + ' ' + sock.remotePort); });}).listen(PORT, HOST);console.log('Server listening on ' + HOST +':'+ PORT);
服务端也可以用稍不同的方式接受TCP连接,即显式处理"connection"事件:
var server = net.createServer();server.listen(PORT, HOST);console.log('Server listening on ' + server.address().address + ':' + server.address().port);server.on('connection', function(sock) { console.log('CONNECTED: ' + sock.remoteAddress +':'+ sock.remotePort); // 其它内容与前例相同});
上述两个例子只是写法不同,并无本质区别。
创建TCP客户端
现在让我们创建一个TCP客户端连接到刚创建的服务器上,该客户端向服务器发送一串消息,并在得到服务器的反馈后关闭连接。下面的代码描述了这一过程。
var net = require('net');var HOST = '127.0.0.1';var PORT = 6969;var client = new net.Socket();client.connect(PORT, HOST, function() { console.log('CONNECTED TO: ' + HOST + ':' + PORT); // 建立连接后立即向服务器发送数据,服务器将收到这些数据 client.write('I am Chuck Norris!');});// 为客户端添加“data”事件处理函数// data是服务器发回的数据client.on('data', function(data) { console.log('DATA: ' + data); // 完全关闭连接 client.destroy();});// 为客户端添加“close”事件处理函数client.on('close', function() { console.log('Connection closed');});
这就是NodeJS中进行TCP网络编程的基本过程,希望能对你有所帮助。需要注意的是,套接字编程要比这里的例子复杂得多,当你使用套接字发送大量数据或完成复杂任务的时候,你就会用到流(Streams)和缓冲区(Buffers)等相关模块。
- node网络编程基础
- Node -- 网络编程
- Node.js中的网络编程
- 网络编程:网络基础
- 网络编程基础
- 网络编程基础
- c#网络编程基础
- 网络编程基础(1)
- 网络编程基础(2)
- 网络编程基础(3)
- 网络编程知识基础
- VC网络编程基础
- linux网络编程基础
- 网络编程基础篇
- VC网络编程基础
- VC网络编程基础
- VC网络编程基础
- Linux 网络编程基础
- Android 控制屏幕全屏,标题栏,导航栏,布局属性
- 万年历
- html+css——网页布局
- javascript oo实现(面向对象)
- 外观模式
- node网络编程基础
- 坐标系变换公式(转http://blog.sina.com.cn/s/blog_3fd642cf0101cc8w.html)
- iOS进阶指南试读之UI篇
- 【hdu】2485 Destroying the bus stations【最小割】
- 安卓与ios界面设计规范
- 数学之美
- 《C#类设计手册》基础知识要点
- 通达OA2015中会签意见如何在流程的表单中并排显示
- fragment重叠现象的解决!