Java對於網絡通信有着很是強大的支持。不只能夠獲取網絡資源,傳遞參數到遠程服務器,還能夠經過Socket對象實現TCP協議,經過DatagramSocket對象實現UDP協議。同時,對於多點廣播以及代理服務器也有着很是強大的支持。如下是本人在學習過程當中的總結和概括。編程
1. Java的基本網絡支持數組
1.1 InetAddress
Java中的InetAddress是一個表明IP地址的對象。IP地址能夠由字節數組和字符串來分別表示,InetAddress將IP地址以對象的形式進行封裝,能夠更方便的操做和獲取其屬性。InetAddress沒有構造方法,能夠經過兩個靜態方法得到它的對象。代碼以下:服務器
-
- InetAddress ip = InetAddress.getByName("www.oneedu.cn");
-
- System.out.println("oneedu是否可達:" + ip.isReachable(2000));
-
- System.out.println(ip.getHostAddress());
-
- InetAddress local = InetAddress.getByAddress(new byte[]
- {127,0,0,1});
- System.out.println("本機是否可達:" + local.isReachable(5000));
-
- System.out.println(local.getCanonicalHostName());
|
1.2 URLDecoder和URLEncoder
這兩個類能夠別用於將application/x-www-form-urlencoded MIME類型的字符串轉換爲普通字符串,將普通字符串轉換爲這類特殊型的字符串。使用URLDecoder類的靜態方法decode()用於解碼,URLEncoder類的靜態方法encode()用於編碼。具體使用方法以下。
-
-
- String keyWord = URLDecoder.decode(
- "%E6%9D%8E%E5%88%9A+j2ee", "UTF-8");
- System.out.println(keyWord);
-
-
- String urlStr = URLEncoder.encode(
- "ROR敏捷開發最佳指南" , "GBK");
- System.out.println(urlStr);
|
1.3 URL和URLConnection
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,而參數經過須要經過輸出流來傳遞。另外還須要設置頭字段。如下是兩種方式的代碼。
-
- String urlName = url + "?" + param;
- URL realUrl = new URL(urlName);
-
- 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();
-
-
- URL realUrl = new 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)");
-
- conn.setDoOutput(true);
- conn.setDoInput(true);
-
- out = new PrintWriter(conn.getOutputStream());
-
- out.print(param);
-
|
另外須要注意的是,若是既須要讀取又須要發送,必定要先使用輸出流,再使用輸入流。由於遠程資源不會主動向本地發送請求,必需要先請求資源。
2. 基於TCP協議的網絡編程
TCP協議是一種可靠的通絡協議,通訊兩端的Socket使得它們之間造成網絡虛擬鏈路,兩端的程序能夠經過虛擬鏈路進行通信。Java使用socket對象表明兩端的通訊端口,並經過socket產生的IO流來進行網絡通訊。
2.1 ServerSocket
在兩個通訊端沒有創建虛擬鏈路以前,必須有一個通訊實體首先主動監聽來自另外一端的請求。ServerSocket對象使用accept()方法用於監聽來自客戶端的Socket鏈接,若是收到一個客戶端Socket的鏈接請求,該方法將返回一個與客戶端Socket對應的Socket對象。若是沒有鏈接,它將一直處於等待狀態。一般狀況下,服務器不該只接受一個客戶端請求,而應該經過循環調用accept()不斷接受來自客戶端的全部請求。
這裏須要注意的是,對於屢次接收客戶端數據的狀況來講,一方面能夠每次都在客戶端創建一個新的Socket對象而後經過輸入輸出通信,這樣對於服務器端來講,每次循環所接收的內容也不同,被認爲是不一樣的客戶端。另外,也能夠只創建一次,而後在這個虛擬鏈路上通訊,這樣在服務器端一次循環的內容就是通訊的全過程。
服務器端的示例代碼:網絡
-
- ServerSocket ss = new ServerSocket(30000);
-
- while (true)
- {
-
- Socket s = ss.accept();
-
- PrintStream ps = new PrintStream(s.getOutputStream());
-
- ps.println("您好,您收到了服務器的新年祝福!");
-
- ps.close();
- s.close();
- }
|
2.2 Socket
使用Socket能夠主動鏈接到服務器端,使用服務器的IP地址和端口號初始化以後,服務器端的accept即可以解除阻塞繼續向下執行,這樣就創建了一對互相鏈接的Socket。
客戶端示例代碼:多線程
- Socket socket = new Socket("127.0.0.1" , 30000);
-
- BufferedReader br = new BufferedReader(
- new InputStreamReader(socket.getInputStream()));
-
- String line = br.readLine();
- System.out.println("來自服務器的數據:" + line);
-
- br.close();
- socket.close();
|
2.3 使用多線程
在複雜的通信中,使用多線程很是必要。對於服務器來講,它須要接收來自多個客戶端的鏈接請求,處理多個客戶端通信須要併發執行,那麼就須要對每個傳過來的Socket在不一樣的線程中進行處理,每條線程須要負責與一個客戶端進行通訊。以防止其中一個客戶端的處理阻塞會影響到其餘的線程。對於客戶端來講,一方面要讀取來自服務器端的數據,另外一方面又要向服務器端輸出數據,它們一樣也須要在不一樣的線程中分別處理。
具體代碼以下,服務器端:
- public class MyServer
- {
-
- 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);
-
- new Thread(new ServerThread(s)).start();
- }
- }
- }
|
客戶端:
- public class MyClient
- {
- public static void main(String[] args)
- throws IOException
- {
- Socket s = s = new Socket("127.0.0.1" , 30000);
-
- new Thread(new ClientThread(s)).start();
-
- PrintStream ps = new PrintStream(s.getOutputStream());
- String line = null;
-
- BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
- while ((line = br.readLine()) != null)
- {
-
- ps.println(line);
- }
- }
- }
|
2.4 使用協議字符
協議字符用於標識一些字段的特定功能,用於說明傳輸內容的特性。它能夠由用戶自定義。通常狀況下,能夠定義一個存放這些協議字符的接口。以下:併發
- public interface YeekuProtocol
- {
-
- int PROTOCOL_LEN = 2;
-
-
- String MSG_ROUND = "§γ";
- String USER_ROUND = "∏∑";
- String LOGIN_SUCCESS = "1";
- String NAME_REP = "-1";
- String PRIVATE_ROUND = "★【";
- String SPLIT_SIGN = "※";
- }
|
在字段時能夠加上這些字符,以下代碼:
- while(true)
- {
- String userName = JOptionPane.showInputDialog(tip + "輸入用戶名");
-
- ps.println(YeekuProtocol.USER_ROUND + userName
- + YeekuProtocol.USER_ROUND);
-
- String result = brServer.readLine();
-
- if (result.equals(YeekuProtocol.NAME_REP))
- {
- tip = "用戶名重複!請從新";
- continue;
- }
-
- if (result.equals(YeekuProtocol.LOGIN_SUCCESS))
- {
- break;
- }
- }
|
收到發送來的字段時候,也再次拆分紅所須要的部分,以下代碼:
- if (line.startsWith(YeekuProtocol.PRIVATE_ROUND)
- && line.endsWith(YeekuProtocol.PRIVATE_ROUND))
- {
-
- String userAndMsg = getRealMsg(line);
-
- String user = userAndMsg.split(YeekuProtocol.SPLIT_SIGN)[0];
- String msg = userAndMsg.split(YeekuProtocol.SPLIT_SIGN)[1];
-
- Server.clients.get(user).println(
- Server.clients.getKeyByValue(ps) + "悄悄地對你說:" + msg);
- }
|
3. UDP協議的網絡編程
UDP協議是一種不可靠的網絡協議,它在通信實例的兩端個創建一個Socket,但這兩個Socket之間並無虛擬鏈路,這兩個Socket只是發送和接受數據報的對象,Java提供了DatagramSocket對象做爲基於UDP協議的Socket,使用DatagramPacket表明DatagramSocket發送和接收的數據報。
3.1 使用DatagramSocket發送、接收數據
DatagramSocket自己並不負責維護狀態和產生IO流。它僅僅負責接收和發送數據報。使用receive(DatagramPacket p)方法接收,使用send(DatagramPacket p)方法發送。
這裏須要首先明確的是,DatagramPacket對象的構造。DatagramPacket的內部實際上採用了一個字節型數組來保存數據,它的初始化方法以下:app
-
- Private DatagaramSocket udpSocket=new DatagaramSocket(buf,buf.length);
-
- 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的初始化須要傳遞端口號做爲參數,特別對於須要接受信息的端來講,它的端口號須要與發送端數據報中包含的端口號一致。具體代碼以下:socket
-
-
- socket = new MulticastSocket(BROADCAST_PORT);
- broadcastAddress = InetAddress.getByName(BROADCAST_IP);
-
- socket.joinGroup(broadcastAddress);
-
- socket.setLoopbackMode(false);
-
- outPacket = new DatagramPacket(new byte[0] , 0 ,
- broadcastAddress , BROADCAST_PORT);
|
4. 使用代理服務器
Java中可使用Proxy直接建立鏈接代理服務器,具體使用方法以下:
- 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();
- }
- }
- }
-
- }
|
5. 編碼中的問題總結ide
a. 雙方初始化套接字之後,就等於創建了連接,表示雙方互相能夠知曉對方的狀態。服務器端能夠調用接收到的客戶端套接字進行輸入輸出流操做,客戶端能夠調用自身內部的套接字對象進行輸入輸出操做。這樣能夠保持輸入輸出的流暢性。例如,客戶端向服務器端發送消息時,能夠隔一段的時間輸入一段信息,而後服務器端使用循環不斷的讀取傳過來的輸入流。
b. 對於可能出現阻塞的方法,例如客戶端進行循環不斷讀取來自服務器端的響應信息時,若是此時服務器端並無向客戶端進行輸出,那麼讀取的方法將處於阻塞狀態,直到收到信息爲止才向下執行代碼。那麼對於這樣容易產生阻塞的代碼,就須要將它放在一個單獨的線程中處理。
c. 有一些流是順承的。例如,服務器端在收到客戶端的消息之後,就將消息再經過輸出流向其餘全部服務器發送。那麼,這個來自客戶端的輸入流和發向客戶端的輸出流就是順接的關係,沒必要對它們分在兩個不一樣的線程。
d. println()方法對應readLine()。
e. 在JFrame類中,通常不要將本身的代碼寫進main方法中,能夠將代碼寫到自定義的方法中,而後在main方法中調用。
oop