Java的基本網絡支持
1. InetAddress.java
Java中的InetAddress是一個代表IP地址的對象。IP地址可以由字節數組和字符串來分別表示,InetAddress將IP地址以對象的形式進行封裝,可以更方便的操作和獲取其屬性。InetAddress沒有構造方法,可以通過兩個靜態方法獲得它的對象。該類的兩個直接子類Inet4Address, Inet6Address 分別用於標識32位IP地址跟64位IP地址 測試代碼:
Java代碼
InetAddress ip = InetAddress.getByName("<a href="/">http://jack22.iteye.com</a>");
//判斷是否可達
System.out.println("oneedu是否可達:" + ip.isReachable(2000));
//獲取該InetAddress實例的IP字符串
System.out.println(ip.getHostAddress());
//根據原始IP地址(字節數組形式)來獲取對應的InetAddress實例
InetAddress local = InetAddress.getByAddress(new byte[]{127,0,0,1});
System.out.println("本機是否可達:" + local.isReachable(5000));
//獲取該InetAddress實例對應的全限定域名
System.out.println(local.getCanonicalHostName());
URL和URLConnection
統一資源定位符(URL,英語UniformResourceLocator的縮寫)也被稱為網頁地址,是因特網上標準的資源的地址。URL可以被認為是指向互聯網資源的“指針”,通過URL可以獲得互聯網資源相關信息,包括獲得URL的InputStream對象獲取資源的信息,以及一個到URL所引用遠程對象的連接URLConnection。
URLConnection對象可以向所代表的URL發送請求和讀取URL的資源。通常,創建一個和URL的連接,需要如下幾個步驟:
a. 創建URL對象,並通過調用openConnection方法獲得URLConnection對象;
b. 設置URLConnection參數和普通請求屬性;
c. 向遠程資源發送請求;
d. 遠程資源變為可用,程序可以訪問遠程資源的頭字段和通過輸入流來讀取遠程資源返回的信息。
這裡需要重點討論一下第三步:如果隻是發送GET方式請求,使用connect方法建立和遠程資源的連接即可;如果是需要發送POST方式的請求,則需要獲取URLConnection對象所對應的輸出流來發送請求。這裡需要註意的是,由於GET方法的參數傳遞方式是將參數顯式追加在地址後面,那麼在構造URL對象時的參數就應當是包含瞭參數的完整URL地址,而在獲得瞭URLConnection對象之後,就直接調用connect方法即可發送請求。
而POST方法傳遞參數時僅僅需要頁面URL,而參數通過需要通過輸出流來傳遞。另外還需要設置頭字段。以下是兩種方式的代碼。
Java代碼
//1. 向指定URL發送GET方法的請求
String urlName = url + "?" + param;
URL realUrl = new URL(urlName);
//打開和URL之間的連接
URLConnection conn = realUrl.openConnection();
//設置通用的請求屬性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)");
//建立實際的連接
conn.connect();
//2. 向指定URL發送POST方法的請求
URL realUrl = new URL(url);
//打開和URL之間的連接
URLConnection conn = realUrl.openConnection();
//設置通用的請求屬性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)");
//發送POST請求必須設置如下兩行
conn.setDoOutput(true);
conn.setDoInput(true);
//獲取URLConnection對象對應的輸出流
out = new PrintWriter(conn.getOutputStream());
//發送請求參數
out.print(param);
URLDecoder和URLEncoder
這兩個類可以別用於將application/x-www-form-urlencoded MIME類型的字符串轉換為普通字符串,將普通字符串轉換為這類特殊型的字符串。使用URLDecoder類的靜態方法decode()用於解碼,URLEncoder類的靜態方法encode()用於編碼。具體使用方法如下。
Java代碼
//將application/x-www-form-urlencoded字符串
//轉換成普通字符串
String keyWord = URLDecoder.decode(
"%E6%9D%8E%E5%88%9A+j2ee", "UTF-8");
System.out.println(keyWord);
//將普通字符串轉換成
//application/x-www-form-urlencoded字符串
String urlStr = URLEncoder.encode(
"ROR敏捷開發最佳指南" , "GBK");
System.out.println(urlStr);
基於TCP協議的網絡編程
TCP協議是一種可靠的通絡協議,通信兩端的Socket使得它們之間形成網絡虛擬鏈路,兩端的程序可以通過虛擬鏈路進行通訊。Java使用socket對象代表兩端的通信端口,並通過socket產生的IO流來進行網絡通信。
2.1 ServerSocket
在兩個通信端沒有建立虛擬鏈路之前,必須有一個通信實體首先主動監聽來自另一端的請求。ServerSocket對象使用accept()方法用於監聽來自客戶端的Socket連接,如果收到一個客戶端Socket的連接請求,該方法將返回一個與客戶端Socket對應的Socket對象。如果沒有連接,它將一直處於等待狀態。通常情況下,服務器不應隻接受一個客戶端請求,而應該通過循環調用accept()不斷接受來自客戶端的所有請求。
這裡需要註意的是,對於多次接收客戶端數據的情況來說,一方面可以每次都在客戶端建立一個新的Socket對象然後通過輸入輸出通訊,這樣對於服務器端來說,每次循環所接收的內容也不一樣,被認為是不同的客戶端。另外,也可以隻建立一次,然後在這個虛擬鏈路上通信,這樣在服務器端一次循環的內容就是通信的全過程。
服務器端的示例代碼:
Java代碼
//創建一個ServerSocket,用於監聽客戶端Socket的連接請求 監聽端口為30000
ServerSocket ss = new ServerSocket(30000);
//采用循環不斷接受來自客戶端的請求
while (true)
{
//每當接受到客戶端Socket的請求,服務器端也對應產生一個Socket
Socket s = ss.accept();
//將Socket對應的輸出流包裝成PrintStream
PrintStream ps = new PrintStream(s.getOutputStream());
//進行普通IO操作
ps.println("您好,今天服務器的大姨夫來瞭!");
//關閉輸出流,關閉Socket
ps.close();
s.close();
}
2.2 Socket
使用Socket可以主動連接到服務器端,使用服務器的IP地址和端口號初始化之後,服務器端的accept便可以解除阻塞繼續向下執行,這樣就建立瞭一對互相連接的Socket。
客戶端示例代碼:
Java代碼
Socket socket = new Socket("127.0.0.1" , 30000);
//將Socket對應的輸入流包裝成BufferedReader
BufferedReader br = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
//進行普通IO操作
String line = br.readLine();
System.out.println("來自服務器的數據:" + line);
//關閉輸入流、socket
br.close();
socket.close();
2.3 使用多線程
在復雜的通訊中,使用多線程非常必要。對於服務器來說,它需要接收來自多個客戶端的連接請求,處理多個客戶端通訊需要並發執行,那麼就需要對每一個傳過來的Socket在不同的線程中進行處理,每條線程需要負責與一個客戶端進行通信。以防止其中一個客戶端的處理阻塞會影響到其他的線程。對於客戶端來說,一方面要讀取來自服務器端的數據,另一方面又要向服務器端輸出數據,它們同樣也需要在不同的線程中分別處理。
具體代碼如下,服務器端:
Java代碼
public class MyServer
{
//定義保存所有Socket的ArrayList
public static ArrayList<Socket> socketList = new ArrayList<Socket>();
public static void main(String[] args)
throws IOException
{
ServerSocket ss = new ServerSocket(30000);
while(true)
{
//此行代碼會阻塞,將一直等待別人的連接
Socket s = ss.accept();
socketList.add(s);
//每當客戶端連接後啟動一條ServerThread線程為該客戶端服務
new Thread(new ServerThread(s)).start();
}
}
}
客戶端:
Java代碼
public class MyClient
{
public static void main(String[] args)
throws IOException
{
Socket s = s = new Socket("127.0.0.1" , 30000);
//客戶端啟動ClientThread線程不斷讀取來自服務器的數據
new Thread(new ClientThread(s)).start();
//獲取該Socket對應的輸出流
PrintStream ps = new PrintStream(s.getOutputStream());
String line = null;
//不斷讀取鍵盤輸入
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
while ((line = br.readLine()) != null)
{
//將用戶的鍵盤輸入內容寫入Socket對應的輸出流
ps.println(line);
}
} }
UDP協議的網絡編程
UDP協議是一種不可靠的網絡協議,它在通訊實例的兩端個建立一個Socket,但這兩個Socket之間並沒有虛擬鏈路,這兩個Socket隻是發送和接受數據報的對象,Java提供瞭DatagramSocket對象作為基於UDP協議的Socket,使用DatagramPacket代表DatagramSocket發送和接收的數據報。
3.1 使用DatagramSocket發送、接收數據
DatagramSocket本身並不負責維護狀態和產生IO流。它僅僅負責接收和發送數據報。使用receive(DatagramPacket p)方法接收,使用send(DatagramPacket p)方法發送。
這裡需要首先明確的是,DatagramPacket對象的構造。DatagramPacket的內部實際上采用瞭一個字節型數組來保存數據,它的初始化方法如下:
Java代碼
//接收端的DatagaramSocket內部包含一個空的數組,接收傳遞過來的數據報中的數組信息。可以通過DatagaramSocket對象的getData()方法返回的數組來獲取其中的包含的數組。
Private DatagaramSocket udpSocket=new DatagaramSocket(buf,buf.length);
//發送端的DatagaramSocket內部包含一個將要傳遞的數組,同時需要包含目標IP和端口。如果初始化時傳遞的數組參數是空,可以通過調用DatagaramSocket對象的setData()方法設置內容。
Private DatagaramSocket udpSocket=new DatagaramSocket(buf,buf.length,IP,PORT);
udpSocket。setData(outBuf);
作為這兩個方法的參數,作用和構造不同的。作為接收方法中的參數,DatagramPacket中的數組一個空的數組,用來存放接收到的DatagramPacket對象中的數組;而作為發送方法參數,DatagramPacket本身含有瞭目的端的IP和端口,以及存儲瞭要發送內容的指定瞭長度的字節型數組。
另外,DatagramPacket對象還提供瞭setData(Byte[] b)和Byte[] b= getData()方法,用於設置DatagramPacket中包含的數組內容和獲得其中包含數組的內容。
使用TCP和UDP通訊的編碼區別:
a. 在TCP中,目標IP和端口由Socket指定包含;UDP中,目標IP由DatagramPacket包含指定,DatagramSocket隻負責發送和接受。
b. 在TCP中,通訊是通過Socket獲得的IO流來實現;在UDP中,則通過DatagramSocket的send和receive方法
3.2 使用MulticastSocket實現多點廣播
MulticastSocket是DatagramSocket的子類,可以將數據報以廣播形式發送到數量不等的多個客戶端。實現策略就是定義一個廣播地址,使得每個MulticastSocket都加入到這個地址中。從而每次使用MulticastSocket發送數據報(包含的廣播地址)時,所有加入瞭這個廣播地址的MulticastSocket對象都可以收到信息。
MulticastSocket的初始化需要傳遞端口號作為參數,特別對於需要接受信息的端來說,它的端口號需要與發送端數據報中包含的端口號一致。具體代碼如下://創建用於發送、接收數據的MulticastSocket對象
Java代碼
//因為該MulticastSocket對象需要接收,所以有指定端口
socket = new MulticastSocket(BROADCAST_PORT);
broadcastAddress = InetAddress.getByName(BROADCAST_IP);
//將該socket加入指定的多點廣播地址
socket.joinGroup(broadcastAddress);
//設置本MulticastSocket發送的數據報被回送到自身
socket.setLoopbackMode(false);
//初始化發送用的DatagramSocket,它包含一個長度為0的字節數組
outPacket = new DatagramPacket(new byte[0] , 0 ,
broadcastAddress , BROADCAST_PORT);
使用代理服務器
Java中可以使用Proxy直接創建連接代理服務器,具體使用方法如下
Java代碼
public class ProxyTest
{
Proxy proxy;
URL url;
URLConnection conn;
//從網絡通過代理讀數據
Scanner scan;
PrintStream ps ;
//下面是代理服務器的地址和端口,
//換成實際有效的代理服務器的地址和端口
String proxyAddress = "202.128.23.32";
int proxyPort;
//下面是你試圖打開的網站地址
String urlStr = "http://www.oneedu.cn";
public void init()
{
try
{
url = new URL(urlStr);
//創建一個代理服務器對象
proxy = new Proxy(Proxy.Type.HTTP,
new InetSocketAddress(proxyAddress , proxyPort));
//使用指定的代理服務器打開連接
conn = url.openConnection(proxy);
//設置超時時長。
conn.setConnectTimeout(5000);
scan = new Scanner(conn.getInputStream());
//初始化輸出流
ps = new PrintStream("Index.htm");
while (scan.hasNextLine())
{
String line = scan.nextLine();
//在控制臺輸出網頁資源內容
System.out.println(line);
//將網頁資源內容輸出到指定輸出流
ps.println(line);
}
}
catch(MalformedURLException ex)
{
System.out.println(urlStr + "不是有效的網站地址!");
}
catch(IOException ex)
{
ex.printStackTrace();
}
//關閉資源
finally
{
if (ps != null)
{
ps.close();
}
}
}
}
編碼中的問題總結
a. 雙方初始化套接字以後,就等於建立瞭鏈接,表示雙方互相可以知曉對方的狀態。服務器端可以調用接收到的客戶端套接字進行輸入輸出流操作,客戶端可以調用自身內部的套接字對象進行輸入輸出操作。這樣可以保持輸入輸出的流暢性。例如,客戶端向服務器端發送消息時,可以隔一段的時間輸入一段信息,然後服務器端使用循環不斷的讀取傳過來的輸入流。
b. 對於可能出現阻塞的方法,例如客戶端進行循環不斷讀取來自服務器端的響應信息時,如果此時服務器端並沒有向客戶端進行輸出,那麼讀取的方法將處於阻塞狀態,直到收到信息為止才向下執行代碼。那麼對於這樣容易產生阻塞的代碼,就需要將它放在一個單獨的線程中處理。
c. 有一些流是順承的。例如,服務器端在收到客戶端的消息以後,就將消息再通過輸出流向其他所有服務器發送。那麼,這個來自客戶端的輸入流和發向客戶端的輸出流就是順接的關系,不必對它們分在兩個不同的線程。
d. println()方法對應readLine()。
e. 在JFrame類中,一般不要將自己的代碼寫進main方法中,可以將代碼寫到自定義的方法中,然後在main方法中調用。