Java核心技术卷II(第8版) – 读书笔记 – 第3章

本章是重头戏:网络编程!

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都没写,改天找找资料自己补上)

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *