InetAddress
java.net.InetAddress類是Java對IP地址的高層表示。大多數其他網絡類都要用到這個類,包括Socket、ServerSocket、URL、DatagramSocket、DatagramPacket等。一般地講,它包括一個主機名和一個IP地址。
InetAddress address = InetAddress.getByName("www.baidu.com");
這段代碼會建立與本地DNS服務器的一個連接,來查找名字和數字地址。
DNS的查找的開銷可能相當大,所以InetAddress類會緩存查找的結果。
是線程安全的。
127.0.0.1是本地回送地址。
224.0.0.0到239.255.255.255範圍內的IPv4地址是組播地址,可以同時發送到多個訂購的主機。
可達性
public boolean isReachable(int timeout) throws IOException
public boolean isReachable(NetworkInterface interface,int ttl,int timeout) throws IOException
InetAddress類會緩存請求過的地址。如果再次請求相同的地址,它可以從緩存中獲取,這比從DNS獲取要快的多。
Object方法
如果兩個InetAddress對象有相同的地址,就會有相同的散列碼,即使它們的主機名有所不同。
如果一個對象本身是InetAddress類的實例,而且與一個InetAddress對象有相同的IP地址,只有此時纔會與該InetAddress對象相等,並不要求兩個對象有相同的主機名。
NetworkInterface
NetworkInterface類表示一個本地IP地址。
NetworkInterface類提供了一些方法可以枚舉所有的本地地址(而不考慮接口),並由它們創建InetAddress對象,然後這些InetAddress對象可用於創建socket、服務器socket等。
示例代碼
import java.net.InetAddress;
import java.net.UnknownHostException;
public class MyAddress {
public static void main(String[] args) throws UnknownHostException {
try {
InetAddress address = InetAddress.getLocalHost();
System.out.println(address);
} catch (UnknownHostException e) {
System.out.println("Could not find this computer's address.");
}
InetAddress address = InetAddress.getByName("www.sina.com");
byte[] addresses = address.getAddress();
if (addresses.length == 4) {
System.out.println("4");
} else if (addresses.length == 16) {
System.out.println("16");
} else {
System.out.println("-1");
}
}
}
import java.net.InetAddress;
import java.net.UnknownHostException;
public class IPCharacteristics {
public static void main(String[] args) {
try {
InetAddress address = InetAddress.getByName(args[0]);
// 通配地址
if (address.isAnyLocalAddress()) {
System.out.println(address + " is a wildcard address.");
}
// 回送地址
if (address.isLoopbackAddress()) {
System.out.println(address + " is loopback address.");
}
// 連接本地地址
if (address.isLinkLocalAddress()) {
System.out.println(address + " is a link-local address.");
} else if (address.isSiteLocalAddress()) {
System.out.println(address + " is a site-local address.");
} else {
System.out.println(address + " is a global address.");
}
//IP組播地址
if (address.isMulticastAddress()) {
// 多播地址是否具有全局範圍的使用程序
if (address.isMCGlobal()) {
System.out.println(address + " is a global multicast address.");
} else if (address.isMCOrgLocal()) { // 組播地址是否具有節點範圍
System.out.println(address + " is an organization wide multicast address.");
} else if (address.isMCSiteLocal()) { // 多播地址是否具有站點範圍的實用程序
System.out.println(address + " is a site wide multicast address.");
} else if (address.isMCLinkLocal()) {
System.out.println(address + " is a subnet wide multicast address.");
} else if (address.isMCNodeLocal()) {
System.out.println(address + " is an interface-local multicast address.");
} else {
System.out.println(address + " is an unknown multicast address type.");
}
} else {
System.out.println(address + "is a unicast address.");
}
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
}
URL和URI
URL
相對鏈接以“/”開頭,那麼它相對於文檔根目錄,而不是相對於當前文件。
URL是不可變的(final類)。構造一個URL對象後,其字段不再改變。這有一個副作用:可以保證它們是“線程安全”的。
URL由以下5部分組成:
- 模式,也稱爲協議
- 授權機構(用戶信息、主機和端口)
- 路徑
- 片段標識符,也稱爲段或ref(錨點)
- 查詢字符串
http://www.ibiblio.org/javafaq/books/jnp/index.html?isbn=1565922069#toc
模式:http
授權機構:www.ibiblio.org
路徑:/javafaq/books/jnp/index.html
片段標識符:toc
查詢字符串:isbn=1565922069
URL equals():當且僅當兩個URL都指向相同主機、端口和路徑上的相同的資源,而且有相同的片段標識符和查詢字符串,才認爲這兩個URL是相等的。equals()方法會嘗試用DNS解析主機,來判斷兩個主機是否相同。
URI
URI由以下三個部分組成:
- 模式
- 模式特定部分
- 片段標識符
示例代碼
URLDemo.java:
import java.io.IOException;
import java.net.URL;
public class URLDemo {
public static void main(String[] args) throws IOException {
URL hp = new URL("http://www.HerbSchildt.com/WhatsNew");
System.out.println("Protocol:" + hp.getProtocol());
System.out.println("Port:" + hp.getPort());
System.out.println("Host:" + hp.getHost());
System.out.println("File:" + hp.getFile());
System.out.println("Ext:" + hp.toExternalForm());
System.out.println(hp.openConnection());
}
}
UCDemo.java:
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.Date;
public class UCDemo {
public static void main(String[] args) throws IOException {
int c;
URL hp = new URL("http://www.internic.net");
URLConnection hpCon = hp.openConnection();
long d = hpCon.getDate();
if (d == 0) {
System.out.println("No date information.");
} else {
System.out.println("Date: " + new Date(d));
}
System.out.println("Content-Type:" + hpCon.getContentType());
d = hpCon.getExpiration();
if (d == 0) {
System.out.println("No expiration information.");
} else {
System.out.println("Expires:" + new Date(d));
}
d = hpCon.getLastModified();
if (d == 0) {
System.out.println("No last-modified information.");
} else {
System.out.println("Last-Modified:" + new Date(d));
}
long len = hpCon.getContentLengthLong();
if (len == -1) {
System.out.println("Content length unavailable.");
} else {
System.out.println("Content-length:" + len);
}
if (len != 0) {
System.out.println("=== Content ====");
InputStream input = hpCon.getInputStream();
while ((c = input.read()) != -1) {
System.out.print((char) c);
}
input.close();
} else {
System.out.println("No content available.");
}
}
}
HttpURLDemo.java:
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class HttpURLDemo {
public static void main(String[] args) throws IOException {
URL hp = new URL("https://www.google.com");
HttpURLConnection hpCon = (HttpURLConnection) hp.openConnection();
System.out.println("Request method is " + hpCon.getRequestMethod());
System.out.println("Response code is " + hpCon.getResponseCode());
System.out.println("Response Message is " + hpCon.getResponseMessage());
// 獲取標題字段列表和一組標題鍵。
Map<String, List<String>> hdrMap = hpCon.getHeaderFields();
Set<String> hdrField = hdrMap.keySet();
System.out.println("\nHere is the header:");
for (String k : hdrField) {
System.out.println("Key:" + k + " value:" + hdrMap.get(k));
}
}
}
Exam1.java:
import java.net.URI;
public class Exam1 {
public static void main(String[] args) {
URI uri = URI.create("http://username:[email protected]:80/path/to/file?p1=p1&p2=v2#hash");
System.out.println(String.format("%s %s %s %s %d %s %s %s",
uri.getScheme(),
uri.getAuthority(),
uri.getUserInfo(),
uri.getHost(),
uri.getPort(),
uri.getPath(),
uri.getQuery(),
uri.getFragment()));
System.out.println("uri.getScheme():" + uri.getScheme());
// 授權機構,在大多數的情況下,授權機構包括用戶信息、主機和端口
System.out.println("uri.getAuthority():" + uri.getAuthority());
System.out.println("uri.getUserInfo():" + uri.getUserInfo());
// URI會自動編解其中的字符串,原生值獲取使用下面的
System.out.println("uri.getRawUserInfo():" + uri.getRawUserInfo());
System.out.println("uri.getHost():" + uri.getHost());
System.out.println("uri.getPort():" + uri.getPort());
System.out.println("uri.getPath():" + uri.getPath());
System.out.println("uri.getQuery():" + uri.getQuery());
//fragment 片段標識符
System.out.println("uri.getFragment():" + uri.getFragment());
}
}
Exam2.java:
import java.net.MalformedURLException;
import java.net.URL;
public class Exam2 {
public static void main(String[] args) throws MalformedURLException {
URL url = new URL("http://www.ibiblio.org/javafaq/books/jnp/index.html?isbn=1565922069#toc");
System.out.println("getFile():" + url.getFile());
System.out.println("getHost():" + url.getHost());
System.out.println("getPort():" + url.getPort());
System.out.println("getProtocol():" + url.getProtocol());
System.out.println("getRef():" + url.getRef());
System.out.println("getQuery():" + url.getQuery());
System.out.println("getPath():" + url.getPath());
System.out.println("getUserInfo():" + url.getUserInfo());
System.out.println("getAuthority():" + url.getAuthority());
}
}
URLConnection
使用URLConnection類的程序遵循以下基本步驟:
- 構造一個URL對象。
- 調用這個URL對象的openConnection( )獲取一個對應URL的URLConnection對象。
- 配置這個URLConnection。
- 讀取首部字段。
- 獲得輸入流並讀取數據。
- 獲得輸出流並寫入數據。
- 關閉連接。
URL和URLConnection之間的區別:
- URLConnection提供了對HTTP首部的訪問
- URLConnection可以配置發送給服務器的請求參數
- URLConnection除了讀取服務器數據外,還可以向服務器寫入數據。
示例代碼
SourceViewer2.java:
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
public class SourceViewer2 {
public static void main(String[] args) {
if (args.length > 0) {
try {
// 構造URL對象
URL u = new URL(args[0]);
// 調用URL對象的openConnection()方法,獲取對應的URL的URLConnection對象
URLConnection uc = u.openConnection();
// 調用URLConnection的getInputStream()方法
try (InputStream raw = uc.getInputStream()) {
// 使用通常的流API讀取輸入輸入流
InputStream buffer = new BufferedInputStream(raw);
// 將InputStream串聯到一個Reader
Reader reader = new InputStreamReader(buffer);
int c;
while ((c = reader.read()) != -1) {
System.out.print((char) c);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
HTTP
從客戶端到服務器的每一個請求,都有四個步驟:
- 默認情況下,客戶端在端口80打開與服務器的一個TCP連接,URL中還可以指定其他端口。
- 客戶端向服務器發送消息,請求指定路徑上的資源。這個請求包括一個首部,可選地還可以有一個空行,後面是這個請求的數據。
- 服務器向客戶端發送響應。響應以響應碼開頭,後面是包含元數據的首部、一個空行以及所請求的文檔或錯誤消息。
- 服務器關閉連接。
# 請求行;包括一個方法、資源的路徑以及HTTP版本。
GET /index.html HTTP/1.1
# 瀏覽器類型,操作系統版本
User-Agent: Mozilla/5.0 (Macintosh;Intel Mac OS X 10.8; rv:20.0)
Gecko/20100101 Firefox/20.0
# 指定服務器的名
Host: en.wikipedia.org
Connection: keep-alive
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
# 告訴客戶端服務器可以處理哪些數據類型
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
HttpURLConnection
HttpURLConnection類是URLConnection的抽象子類,使用方式如下:
URL u = new URL("http://www.baidu.com");
HttpURLConnection http = (HttpURLConnection) u.openConnection();
示例代碼
LastModified.java:
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;
public class LastModified {
public static void main(String[] args) {
String[] list = {"http://www.ibiblio.org/xml","https://www.baidu.com"};
for (int i = 0; i < list.length; i++) {
try {
URL u = new URL(list[i]);
HttpURLConnection http = (HttpURLConnection) u.openConnection();
// 通過setRequestMethod()來改變請求的方法。參數可以是:GET、POST、HEAD、PUT、DELETE、OPTIONS、TRACE
http.setRequestMethod("HEAD");
System.out.println(u + " was last modified at " + new Date(http.getLastModified()));
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println();
}
}
}
Socket
Socket允許程序員將網絡連接看作是另外一個可以讀/寫字節的流。Socket對程序員掩蓋了網絡的底層細節,如錯誤檢測、包大小、包分解、包重傳、網絡地址等。
Socket是兩臺主機之間的一個連接。它可以完成7個基本操作:
- 連接遠程機器
- 發送數據
- 接收數據
- 關閉連接
- 綁定端口
- 監聽入站數據
- 在綁定端口上接受來自遠程機器的連接
半關閉Socket
close()方法同時關閉Socket的輸入和輸出。shutdownInput()和shutdownOutput()方法可以只關閉連接的一半。
關閉輸入之後再讀取輸入流會返回-1。關閉輸出之後再寫入Socket則會拋出一個IOException異常。
即使半關閉了連接,或將連接的兩半都關閉,使用結束後仍需要關閉該Socket。shutdown方法隻影響Socket的流。它們並不釋放與Socket關聯的資源,如所佔用的端口等。
Socket類是Java完成客戶端TCP操作的基礎類。其他建立TCP網絡連接的面向客戶端的類(如URL、URLConnection)最終都會調用這個類的方法。這個類本身使用原生代碼與主機操作系統的本地TCP棧進行通信。
Socket地址
SocketAddress類的主要用途是爲暫時的socket連接信息提供一個方便的存儲,即使最初的socket已斷開並被垃圾回收,這些信息也可以重用來創建新的Socket。
Socket關閉還是連接
查看一個Socket當前是否打開:
- isConnected()要返回true(它指出Socket是否從未鏈接過一個遠程主機,鏈接過返回true,未返回false)
- isClosed()要返回false。
設置Socket選項
- TCP_NODELY:設置爲true可確保包會儘可能快地發送,而無論包的大小
- SO_BINDADDR:綁定地址
- SO_TIMEOUT:設置超時時間,單位爲毫秒
- SO_LINGER:Socket關閉時如何處理尚未發送的數據報
- SO_SNDBUF:使用緩衝區提升網絡性能
- SO_RCVBUF:使用緩衝區提升網絡性能
- SO_KEEPALIVE:測試包,確保服務器未崩潰
- OOBINLINE:發送緊急數據
- IP_TOPS:差分服務
示例代碼
DictClient.java:
import java.io.*;
import java.net.Socket;
public class DictClient {
public static final String SERVER = "dict.org";
public static final int PORT = 2628;
public static final int TIMEOUT = 15000;
static void define(String word, Writer writer, BufferedReader reader) throws IOException {
writer.write("DEFINE eng-lat " + word + "\r\n");
// 刷新輸出,從而確保命令會通過網絡發送:
writer.flush();
for (String line = reader.readLine(); line != null; line = reader.readLine()) {
if (line.startsWith("250 ")) {
return;
} else if (line.startsWith("552 ")) {
System.out.println("No definition found for " + word);
return;
} else if (line.matches("\\d\\d\\d .*")) {
continue;
} else if (line.trim().equals(".")) {
continue;
} else {
System.out.println(line);
}
}
}
public static void main(String[] args) {
Socket socket = null;
try {
socket = new Socket(SERVER, PORT);
socket.setSoTimeout(TIMEOUT);
OutputStream out = socket.getOutputStream();
Writer writer = new OutputStreamWriter(out, "UTF-8");
writer = new BufferedWriter(writer);
InputStream in = socket.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF-8"));
for (String word : args) {
define(word, writer, reader);
}
writer.write("quit\r\n");
writer.flush();
} catch (IOException e) {
System.out.println(e);
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
ServerSocket
在Java中,服務器程序的基本生命週期如下:
- 使用一個ServerSocket()構造函數在一個特定端口創建一個新的ServerSocket。
- ServerSocket使用其accept()方法監聽這個端口的入站連接。accept()會一直阻塞,直到一個客戶端嘗試建立連接,此時accept()將返回一個客戶端和服務器的Socket對象。
- 根據服務器的類型,會調用Socket的getInputStream()方法或getOutputStream()方法,或者這兩個方法都調用,以獲得與客戶端通信的輸入和輸出流。
- 服務端和客戶端根據已協商的協議交互,直到要關閉連接。
- 服務器或客戶端(或二者)關閉連接。
- 服務器回到步驟2,等待下一次連接。
使用InputStream讀取客戶端,另外使用OutputStream寫入客戶端。要明確何時寫入和何時讀取。
關閉ServerSocket會釋放本地主機的一個端口,允許另一個服務器綁定到這個端口。
如果需要測試ServerSocket是否打開,就必須同時檢查isBound()返回true,而且isClosed()返回false。
關閉ServerSocket會釋放本地主機的一個端口,允許另一個服務器綁定到這個端口。它還會中斷該ServerSocket已經接受的目前處於打開狀態的所有Socket。
示例代碼
Daytime.java:
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;
public class Daytime {
public static void main(String[] args) {
int port = 1024;
ServerSocket server = null;
try {
server = new ServerSocket(port);
while(true){
Socket connection = null;
try {
connection = server.accept();
Writer out = new OutputStreamWriter(connection.getOutputStream());
Date now = new Date();
// 需要使用一個回車/換行對來結束這一行。網絡服務器中幾乎都會這樣做
out.write(now.toString()+"\r\n");
out.flush();
// 關閉連接
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
finally {
try {
if(connection!= null){
connection.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (server != null) {
server.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
MultithreadDaytimeServer.java:
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;
public class MultithreadDaytimeServer {
public final static int PORT = 1024;
private static class DaytimeThread extends Thread {
private Socket connection;
public DaytimeThread(Socket connection) {
this.connection = connection;
}
@Override
public void run() {
try {
Writer out = new OutputStreamWriter(connection.getOutputStream());
Date now = new Date();
out.write(now.toString() + "\r\n");
out.flush();
} catch (IOException e) {
System.err.println(e);
} finally {
try {
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
try (ServerSocket server = new ServerSocket(PORT)) {
while (true) {
Socket connection = server.accept();
Thread task = new DaytimeThread(connection);
task.start();
}
} catch (IOException e) {
System.err.println("Couldn't start server");
}
}
}
PooledDaytimeServer.java:
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class PooledDaytimeServer {
public final static int PORT = 1024;
private static class DaytimeTask implements Callable<Void> {
private Socket connection;
public DaytimeTask(Socket connection) {
this.connection = connection;
}
@Override
public Void call() throws Exception {
try {
Writer out = new OutputStreamWriter(connection.getOutputStream());
Date now = new Date();
out.write(now.toString() + "\r\n");
out.flush();
} catch (IOException e) {
System.err.println(e);
} finally {
try {
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(50);
try (ServerSocket server = new ServerSocket(PORT)) {
while (true) {
try {
Socket connection = server.accept();
Callable<Void> task = new DaytimeTask(connection);
pool.submit(task);
} catch (IOException e) {
e.printStackTrace();
}
}
} catch (IOException e) {
System.err.println("Cloudn't start server");
}
}
}
EchoServer.java:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class EchoServer {
public static int DEFAULT_PORT = 1024;
public static void main(String[] args) {
int port;
port = DEFAULT_PORT;
System.out.println("Listening for connections on port " + port);
ServerSocketChannel serverChannel;
Selector selector;
try {
// 打開ServerSocketChannel
serverChannel = ServerSocketChannel.open();
// 綁定端口
ServerSocket ss = serverChannel.socket();
InetSocketAddress address = new InetSocketAddress(port);
ss.bind(address);
// 設置爲非阻塞
serverChannel.configureBlocking(false);
// 打開選擇器
selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException e) {
e.printStackTrace();
return;
}
while (true) {
try {
// 檢查是否有可操作的數據
selector.select();
} catch (IOException e) {
e.printStackTrace();
break;
}
// 如果選擇器確實找到了一個就緒的通道,其selectedKeys()方法會返回一個Set
Set<SelectionKey> readKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = readKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
// 從集合中刪除這個鍵,從而不會處理兩次
iterator.remove();
try {
if (key.isAcceptable()) {
// 如果就緒的是服務器通道,程序就會接受一個新的Socket通道,將其添加到選擇器
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
System.out.println("Accepted connection from " + client);
client.configureBlocking(false);
SelectionKey clientKey = client.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ);
ByteBuffer buffer = ByteBuffer.allocate(100);
clientKey.attach(buffer);
}
if (key.isReadable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer output = (ByteBuffer) key.attachment();
client.read(output);
}
if (key.isWritable()) {// 向通道寫入數據
SocketChannel client = (SocketChannel) key.channel();
// 獲取鍵的附件轉換爲ByteBuffer
ByteBuffer output = (ByteBuffer) key.attachment();
output.flip();
client.write(output);
output.compact();
}
} catch (IOException e) {
key.channel();
try {
key.channel().close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
}
}
安全Socket
Java安全Socket擴展(Java Secure Sockets Extension,JSSE)可以使用安全Socket層(Secure Socket Layer,SSL)版本3和傳輸層安全(Transport Layer Security,TLS)協議及相關的算法來保護網絡通信的安全。
SocketFactory factory = SSLSocketFactory.getDefault();
Socket socket = factory.createSocket("login.ibiblio.org",7000);
非阻塞I/O
構造一個新的Selector,只需要調用Selector.open()靜態工廠方法:
Selector selector = Selector.open();
使用每個通道的register()方法向監視這個通道的選擇器進行註冊。
serverChannel.register(selector,SelectionKey.OP_ACCEPT);