本章是重头戏:网络编程!
1、首先测试一下daytime服务:
telnet time.nist.gov 13 Trying 192.43.244.18... Connected to ntp.glb.nist.gov. Escape character is '^]'. 55965 12-02-08 12:34:39 00 0 0 612.3 UTC(NIST) * Connection closed by foreign host.
这个过程中,DNS将ntp.glb.nist.gov解析为IP地址,然后TCP连接端口13,接下来反复读取、交换数据,直到连接关闭。
2、用基本的Java的Socket模拟上述过程:
import java.io.*; import java.net.*; import java.util.*; public class SocketTest { public static void main(String [] args) { Socket socket = null; try { socket = new Socket("time.nist.gov", 13); InputStream in = socket.getInputStream(); Scanner scan = new Scanner(in); while(scan.hasNextLine()) { String line = scan.nextLine(); System.out.println(line); } } catch(Exception e) { e.printStackTrace(); } finally { try { if(socket!=null) { socket.close(); } } catch(Exception e1) { } } } }
3、上述代码,首先构造Socket,创建一个套接字。
如果失败会抛出各种异常,如UnknownHostException(解析错误)或者IOException(其他错误)。
然后通过getInputStream获取InputStream,这是从远程到本机的“数据管道 ”。
4、上述例子很简单,因为只有服务器主动向客户发送数据,之后就关闭了。一般的通信,是需要客户和服务器双方交互并进行复杂的协议规定。
5、在Socket建立起连接之后,InputStream的read可能需要一定的等待时间才能得到数据。对于有的应用,这是不可接受的,或者说这个的等待时间需要有上限,我们可以设置这个timeout时间:
socket.setSoTimeout(2*1000);
注意单位是ms,而且这个只对read()操作有效。
6、对于connect()(在Java对应的是构造Socket的过程),那么需要单独的使用connect来实现:
Socket socket = new Socket(); socket.connect(xxx, 2*1000);
7、IPV4地址为4个字节,IPV6地址为16个字节。Java支持V4和V6地址。解析域名到IP地址:
import java.net.*; public class DNSTest { public static void main(String [] args) throws Exception { InetAddress addrs[] = InetAddress.getAllByName("www.google.com"); for(int i=0; i<addrs.length; i++) { System.out.println(addrs[i].getHostAddress()); } } }
一些网站都会在一个域名上设置多个IP地址,以达到负载均衡,上面这个getAllByName就是可以取到全部的IP地址,比如google的有如下结果:
74.125.71.106 74.125.71.147 74.125.71.99 74.125.71.103 74.125.71.104 74.125.71.105
8、如果想要获得本机IP地址,可以用:
InetAddress addr = InetAddress.getLocalHost();
9、下面是创建一个最简单的网络服务器端的步骤:
(1)建立ServerSocket s = new ServerSocket(8888)
(2) Socket incoming = s.accept();
(3) income就是客户了,在此基础上进行交互,执行相应的逻辑,使用input/outputstream。
(4)关闭income的socket。
上面的逻辑部分,我们使用最简单的Echo,即客户发送什么,我们回显什么。
import java.util.*; import java.net.*; import java.io.*; public class EchoServer { public static void main(String [] args) { try { ServerSocket server = new ServerSocket(8888); byte [] buf = new byte[1024]; int len = 0; while(true) { Socket client = server.accept(); // accept client try { InputStream in = client.getInputStream(); OutputStream out = client.getOutputStream(); while((len = in.read(buf, 0, 1024))!=-1) { out.write(buf, 0, len); } } catch(Exception e1) { e1.printStackTrace(); } finally { if(client!=null) { client.close(); } } } } catch(Exception e) { e.printStackTrace(); } } }
我这里直接用的最裸的inputstream/outputstream,直接read/write了,没有用PrintWriter神马的。
10、上面这个Server有个问题,多个client同时请求时候,后面的会被hold住(挂起)。。。肿么办?最简单的是多线程搞起,一个新client给开一个新线程处理:
但是这种的效率不会特别高,建议尽量用nio的一些特性。
import java.util.*; import java.net.*; import java.io.*; class ThreadEchoHandler implements Runnable { public ThreadEchoHandler(Socket socket) { this.socket = socket; } public void run() { byte [] buf = new byte[1024]; int len = 0; try { InputStream in = socket.getInputStream(); OutputStream out = socket.getOutputStream(); while((len = in.read(buf, 0, 1024))!=-1) { out.write(buf, 0, len); } } catch(Exception e1) { e1.printStackTrace(); } finally { try { socket.close(); } catch(Exception e) { } } } private Socket socket; } public class ThreadEchoServer { public static void main(String [] args) { try { ServerSocket server = new ServerSocket(8888); while(true) { Socket client = server.accept(); // accept client Thread t = new Thread(new ThreadEchoHandler(client)); //new thread t.start(); } } catch(Exception e) { e.printStackTrace(); } } }
11、半关闭:套接字连接的一端终止其输出,同时仍旧可以接受来自另外一端的数据。应用场景,例如:我们要向服务器端发送数据,但不知道一共要传输多少。可以一直发送,发送后关闭写通道(但要保持读通道开启)。此时服务器端将会知道这一事件,做出对应处理。
12、半关闭适用于“一站式”协议,如HTTP,客户发送一个请求,然后服务器也回送一个响应。这时用半关闭很好:
Socket socket = new Socket("127.0.0.1", 8080); OutputStream output = socket.getOutputStream(); while(true) { out.write(...); } socket.shutdownOutput(); //半关闭! while(..) { in.read(...); } socket.close();
13、在交互式网络协议中,想实现“如果某个连接长期没有响应,变中止它”。这种需要用nio的SocketChannel来辅助实现。
SocketChannel channel = SocketChannel.open(new InetSocketAddress(host, port)); OutputStrem out = Channels.newOutputStream(channel);
然后在需要中断的时候:
thread.interrupt()就可以了,对应的OutputStream会退出,因为这个创建出来的是interruptible I/O。
14、发送E-Mail的例子。
SMTP协议发送E-Mail实际是客户端,相当于Foxmail等。
协议:
HELO sending host
MAIL FROM: <Sender email>
RCPT TO: <Recp email>
DATA
xxxx
xxxx
.
QUIT
每一行都是\r\n换行。
没什么特别的。。JDK已经有JavaMail提供了很完善的功能。
15、如果要进行更高层次的开发,如HTTP,可以考虑URL类。
和它长的很像的还有一个URI,这主要是用于记录地址的,比如:
mailto:xxx@xx.com、http://www.xxx.com
URI的格式是规定好的:
[schema]schemaSpcificPart[#fragmant]
可以使用“相对化”处理层次的URI。
16、如果要抓起Web页面,可以直接用URLConnection。
(1)URL url = ...
(2)conn = url.OpenConnection()
(3)conn.setXXX(UseCache/Modify/ReadTimeout)等HTTP请求的属性。
(4)conn.connect()
(5)连接之后,可以getContentType/getContentLength/...查询Response中HTTP的标准字段,也可以getHeaderFieldKey和getHeaderField来枚举所有的头。
(6)读、写用getInputStream和getOutputStream搞定。
下面是一个基本的例子,抓取网页,打印response中的字段。
import java.net.*; import java.io.*; import java.util.*; public class URLTest { public static void main(String [] args) throws Exception { URL url = new URL("http://www.ict.ac.cn"); URLConnection conn = url.openConnection(); InputStream in = conn.getInputStream(); StringBuilder sb = new StringBuilder(); byte [] buf = new byte[1024]; int len; while(( len = in.read(buf, 0, 1024))!=-1) { sb.append(new String(buf, 0, len)); } System.out.println(sb.toString()); Map<String,List<String>> header_map = conn.getHeaderFields(); for(String key:header_map.keySet()) { System.out.println(key+" = "+header_map.get(key)); } } }
17、如果要发送表单神马的必须conn.setDoInput(true) 。
18、定制请求头,conn.setRequestProperty():
public void setRequestProperty(String key, String value);
19、下面是一个向一个支持POST的页面发送POST的例子。
import java.net.*; import java.io.*; import java.util.*; public class PostTest { public static void main(String [] args) throws Exception { //POST DATA key & value URL url = new URL("http://www.coder4.com/post.php"); URLConnection conn = url.openConnection(); conn.setDoOutput(true); PrintWriter writer = new PrintWriter(conn.getOutputStream()); writer.print("post_key="); writer.println(URLEncoder.encode("这是一段POST数据", "UTF-8")); writer.close(); //Input InputStream in = conn.getInputStream(); StringBuilder sb = new StringBuilder(); byte [] buf = new byte[1024]; int len; while(( len = in.read(buf, 0, 1024))!=-1) { sb.append(new String(buf, 0, len)); } System.out.println(sb.toString()); Map<String,List<String>> header_map = conn.getHeaderFields(); for(String key:header_map.keySet()) { System.out.println(key+" = "+header_map.get(key)); } in.close(); } }
本章完毕。
(Java核心技术对于网络这部分不给力啊。。UDP、NIO都没写,改天找找资料自己补上)