本章是重头戏:网络编程!
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都没写,改天找找资料自己补上)