網絡基本知識:
在java中網絡程序有兩種協議:TCP和UDP,TCP通過握手協議進行可靠的連接,UDP則是不可靠連接。
IP地址:用於標記一臺計算機的身份證。
IP地址由網絡地址(確定網絡)和主機地址(網絡中的主機)組成。
子網掩碼:爲了區分網絡地址和主機地址。
IP地址分爲A類地址、B類地址、C類地址(常用)、D類地址、E類地址。
127.0.0.1(localhost)是本機地址。
IPV4和IPV6
IPV4使用4個十進制數表示,即32位二進制。
SMTP是簡單郵件傳輸協議,端口號是25.
telnet用於連接遠程計算機或者因特網計算機提供的服務。每個服務都會設定一個端口。
給出類似 telnet ip port 即可和特定的服務進行通信
如果要連接因特網的服務,不僅要給出端口,還要給出計算機的名稱,只有給出IP地址和端口號時,才能夠請求服務,並接收到應答。
URL和URI
URI:統一資源標識符,用於標識一個web資源,包含了兩個部分。
(1)URL:統一資源定位符。能夠精確的定位數據的URI
(2)URN:統一資源名稱。除了URL的URI
在java中URI和URL是分開的兩個類,URI類專門用於解析,URL用於通信。
URL
1.URI分類
絕對和相對:
(1)絕對URI是指有確定的協議。比如http,ftp。後面以/進行分隔
(2)相對URI是沒有scheme的。
透明和不透明:
(1)不透明URI是不能夠被解析的URI。不透明URI是絕對URI。scheme後面的部分不是以/進行分割。
分層和不分層:
(1)分層是絕對透明URI或相對URI。
所有的網頁端口都是80.
2.URI的作用:
(1)解析
URI的格式:
[scheme:]scheme-specific-part[#fragment]
scheme表示用的協議,可以是http\https\ftp\file等。
scheme-specific-part是其餘部分。
進一步細分:
[scheme:][//authority][path][?query][#fragment]
常用方法:
- getScheme()獲得scheme;
- getSchemeSpecificPart()
- getPath()
- getAuthority()
(2)相對標識符和絕對標識符的轉換
resolve和relative函數。
示例代碼:
任務1:取得特定網址的html代碼。
任務2:分析地址信息。
任務3:絕對地址和相對地址轉換
- package org.core;
- import java.net.*;
- import java.util.*;
- public class URLTest1 {
- public static void main(String[] args) throws Exception {
- URL url = new URL("http://www.ecnu.edu.cn");
- Scanner in = new Scanner(url.openStream());
- while (in.hasNextLine()) {
- String str = in.nextLine();
- System.out.println(str);
- }
- URI uri = new URI("http://blog.csdn.net/xiazdong");
- System.out.println(uri.getScheme());
- System.out.println(uri.getSchemeSpecificPart());
- System.out.println(uri.getAuthority());
- System.out.println(uri.getUserInfo());
- System.out.println(uri.getHost());
- System.out.println(uri.getPort());
- System.out.println(uri.getPath());
- System.out.println(uri.getQuery());
- System.out.println(uri.getFragment());
- String str = "/article/details/6705033";
- URI combined = uri.resolve(str);// 根據uri的路徑把str變成絕對地址
- System.out.println(combined.getScheme()
- + combined.getSchemeSpecificPart());
- URI relative = uri.relativize(new URI(str));
- System.out.println(relative.getSchemeSpecificPart());
- }
- }
URL和URLConnection
URL的作用
1.如果想要獲取某個網頁的html源代碼,比如http://blog.csdn.net/xiazdong 則只需要:
(1)URL url = new URL("http://blog.csdn.net/xiazdong");
(2)Scanner in = new Scanner(url.openStream());
即可.
2.獲取消息頭信息
- URLConnection connection = url.openConnection();
- connection.getHeaderFields()返回一個Map<String,List<String>>
- connection.getContentLength();
- connection.getContentType();
- connection.setDoOutput(true)獲得輸出流
- connection.getOutputStream();
- connection.getInputStream();
代碼示例:
- package org.core;
- import java.net.*;
- import sun.misc.*;
- import java.util.*;
- import java.io.*;
- public class URLConnectionTest {
- public static void main(String[] args) throws Exception {
- String urlName = "http://java.sun.com";
- URL url = new URL(urlName);
- URLConnection connection = url.openConnection();
- Map<String, List<String>> map = connection.getHeaderFields();
- for (Map.Entry<String, List<String>> entry : map.entrySet()) {
- String key = entry.getKey();
- List<String> value = entry.getValue();
- System.out.println(key + ":" + value);
- }
- }
- }
在網頁中如果要提交數據給web服務器,通常要把數據發送給web服務器,然後web服務器委派一個腳本對數據進行處理,返回一個相應。
通常發送數據的方法有兩種:get和post。
(1)get方法是直接把數據跟在url的後面,以name=value進行傳輸,
每個數據之間用&進行分割,value中的空格用+替換,非字母數字用%替換,並後跟兩個16進制數,這種編碼方式稱爲URL編碼。URLEncoder和URLDecoder
(2)post方法是通過URLConnection發送給服務器,編碼方式和get一樣。URLEncoder.encode(VALUE,"UTF-8");
一般在傳輸中文時會運用編碼和解碼。
示例:通過URLEncoder和URLDecoder編碼和解碼
略
InetAddress 根據域名得到IP地址或名稱
沒有構造方法,通過:
(1)InetAddress i1 = InetAddress.getByName(String)返回一個InetAddress實例。
(2)如果一個地址有多個ip地址,比如google,有3個ip地址,就調用InetAddress[] i2 = InetAddress.getAllByName(String);
InetAddress.getLocalhost()獲得本機的InetAddress實例。
代碼實例:
- package org.core;
- import java.net.InetAddress;
- public class InetAddressTest {
- public static void main(String[] args) throws Exception{
- InetAddress local = InetAddress.getLocalHost();
- System.out.println("本機地址:"+local.getHostAddress());
- System.out.println("本機名稱:"+local.getHostName());
- InetAddress[] remote = InetAddress.getAllByName("www.google.com");
- for(InetAddress a : remote)
- {
- System.out.println("地址:"+a.getHostAddress());
- System.out.println("名稱:"+a.getHostName());
- }
- }
- }
Socket(TCP)
Socket是一個用於機器之間通信的類。
Socket客戶端:
(1)Socket s = new Socket(ip,port);打開一個套接字,發送請求
(2)InputStream istream = s.getInputStream();接收數據
(3)OutputStream ostream = s.getOutputStream();發送數據
需要用PrintWriter和Scanner進行包裝,並且注意PrintWriter的自動緩衝。
Socket服務器:注意多個客戶端同時訪問服務器的問題:多線程
(1)ServerSocket server = new ServerSocket(port);創建一個端口
(2)Socket s = server.accept(); 只有當有客戶端請求並連接,函數纔會返回
(3)InputStream istream = s.getInputStream();接收數據
(4)OutputStream ostream = s.getOutputStream();發送數據
需要用PrintWriter和Scanner進行包裝,並且注意PrintWriter的自動緩衝。
我們在使用PrintWriter時需要使用println()函數;
當服務器或客戶端任意一方請求結束通信,則立刻停止。
問題1:在套接字中會發生阻塞的地方:
(1)實例化Socket時,會阻塞。
(2)在in.nextLine()類似操作時會阻塞。
解決方法:
(1)對於第一個問題,解決方法:
- Socket s = new Socket();建立無連接socket
- s.connect(new InetSocketAddress(host,port),timeout);設置超時。
(2)對於第二個問題,解決方法是設置s.setSoTimeout(long)設置超時時間
問題2:當客戶端想要關閉套接字時,但卻不能確定服務器是否還在發送數據,但是隻要一關閉就立刻斷開。
解決方法:
socket.shutdownOutput()關閉輸出流
socket.shutdownInput()關閉輸入流
半關閉示例代碼:客戶端發送hello給服務器,同時關閉輸出流,服務器接收到後關閉輸入流,等待5秒發送ECHO hello給客戶端。
Client:
- import java.net.*;
- import java.io.*;
- import java.util.*;
- public class shutdownOutputClient {
- public static void main(String[] args)throws Exception {
- Socket s = new Socket("localhost",8819);
- Scanner in = new Scanner(s.getInputStream());
- PrintWriter out = new PrintWriter(s.getOutputStream(),true);
- out.println("Hello");//輸出hello
- s.shutdownOutput(); //關閉輸出流
- System.out.println("關閉連接");
- while(in.hasNextLine()){
- System.out.println(in.nextLine());
- }
- s.close();
- }
- }
Server:
- import java.net.*;
- import java.io.*;
- import java.util.*;
- public class shutdownOutputServer {
- public static void main(String[] args)throws Exception {
- ServerSocket server = new ServerSocket(8819);
- Socket s = server.accept();
- Scanner in = new Scanner(s.getInputStream());
- PrintWriter out = new PrintWriter(s.getOutputStream(),true);
- String str = in.nextLine();
- System.out.println(str);
- s.shutdownInput();
- System.out.println("關閉輸入流");
- Thread.sleep(5000);
- out.println("Echo:"+str);
- s.close();
- }
- }
綜合代碼舉例:實現一個簡單的對等通信程序,通過多線程,一個線程接收數據,一個線程發送數據。
用戶1:
- import java.util.*;
- import java.io.*;
- import java.net.*;
- public class Client{
- public static void main(String[]args)throws Exception{
- Socket s = new Socket("localhost",8819);
- PrintWriter out = new PrintWriter(s.getOutputStream(),true);
- Thread t = new Thread(new Receive(s));
- t.start();
- //以下代碼用於發送數據
- Scanner in = new Scanner(System.in);//鍵盤輸入
- while(in.hasNextLine()){ //一直不斷
- out.println(in.nextLine()); //發送鍵盤輸入數據
- }
- }
- }
- class Receive implements Runnable //這個類用於接收數據
- {
- private Socket s;
- public Receive(Socket s)
- {
- this.s = s;
- }
- public void run()
- {
- try{
- Scanner in = new Scanner(s.getInputStream()); //in:接收數據
- String str = null;
- while(true)
- {
- str = in.nextLine();
- System.out.println("服務器說:"+str); //打印接收數據
- }
- }
- catch(Exception e){}
- }
- }
用戶2:
- import java.util.*;
- import java.io.*;
- import java.net.*;
- public class Server{
- public static void main(String[]args)throws Exception{
- ServerSocket server = new ServerSocket(8819);
- Socket s = server.accept();
- PrintWriter out = new PrintWriter(s.getOutputStream(),true);
- Thread t = new Thread(new Receive1(s));
- t.start();
- //以下代碼用於發送數據
- Scanner in = new Scanner(System.in);//鍵盤輸入
- while(in.hasNextLine()){ //一直不斷
- out.println(in.nextLine()); //發送鍵盤輸入數據
- }
- }
- }
- class Receive1 implements Runnable //這個類用於接收數據
- {
- private Socket s;
- public Receive1(Socket s)
- {
- this.s = s;
- }
- public void run()
- {
- try{
- Scanner in = new Scanner(s.getInputStream()); //in:接收數據
- String str = null;
- while(true)
- {
- str = in.nextLine();
- System.out.println("客戶端說:"+str); //打印接收數據
- }
- }
- catch(Exception e){}
- }
- }
以上的程序屬於C/S,需要同時維護客戶端和服務器的代碼。
B/S:瀏覽器和服務器,只需要維護一方代碼即可。
聊天工具使用UDP非常多,因爲我們通常也會遇到我們發給另一個人一條消息,另一個人卻沒有收到的情況。
DatagramPacket和DatagramSocket 數據報
代碼舉例:實現服務器發送數據報到客戶端。
Client:
- package org.core;
- import java.net.*;
- import java.io.*;
- public class DatagramClient {
- public static void main(String[] args) throws Exception{
- byte[]buf = new byte[1024];
- DatagramPacket packet = new DatagramPacket(buf,1024);
- DatagramSocket client = new DatagramSocket(9000);
- client.receive(packet);
- String str = new String(buf,0,packet.getLength());
- System.out.println(packet.getAddress().getHostName()+":"+str);
- client.close();
- }
- }
Server:
- package org.core;
- import java.net.DatagramPacket;
- import java.net.DatagramSocket;
- import java.net.InetAddress;
- public class DatagramServer {
- public static void main(String[] args)throws Exception {
- DatagramSocket server = new DatagramSocket(3000);
- String str = "hello world";
- DatagramPacket packet = new DatagramPacket(str.getBytes(),str.length(),InetAddress.getLocalHost(),9000);
- server.send(packet);
- server.close();
- }
- }
QQ聊天應用
Server端
- package org.xiazdong.server;
- import java.awt.BorderLayout;
- import java.awt.event.ActionEvent;
- import java.awt.event.ActionListener;
- import java.io.BufferedReader;
- import java.io.InputStreamReader;
- import java.io.PrintStream;
- import java.net.InetAddress;
- import java.net.ServerSocket;
- import java.net.Socket;
- import javax.swing.JButton;
- import javax.swing.JFrame;
- import javax.swing.JPanel;
- import javax.swing.JScrollPane;
- import javax.swing.JTextArea;
- import javax.swing.JTextField;
- public class Server3 extends JFrame{
- static JTextArea area;
- JTextField field;
- JButton button;
- static PrintStream writer;
- public Server3(){
- this.setTitle("服務器");
- this.setSize(400,500);
- area = new JTextArea(25,30);
- area.setEditable(false);
- field = new JTextField(20);
- button = new JButton("提交");
- JPanel panel = new JPanel();
- JScrollPane sp = new JScrollPane(area);
- this.add(sp,BorderLayout.CENTER);
- panel.add(field);
- panel.add(button);
- this.add(panel,BorderLayout.SOUTH);
- this.setVisible(true);
- this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- button.addActionListener(new ActionListener(){
- @Override
- public void actionPerformed(ActionEvent e) {
- String text = field.getText();
- writer.println(text);
- area.append("我:"+text+"\n");
- field.setText("");
- }
- });
- }
- public static void main(String[] args) throws Exception {
- Server3 s = new Server3();
- ServerSocket server = new ServerSocket(8899);
- System.out.println("開始監聽...");
- Socket socket = server.accept();
- InetAddress address = socket.getInetAddress();
- String name = address.getLocalHost().getHostName();
- System.out.println(name+"已連接");
- BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
- writer = new PrintStream(socket.getOutputStream(), true);
- while (true) {
- String line = null;
- line = reader.readLine();
- if (line != null) {
- area.append("客戶端:"+line+"\n");
- }
- }
- }
- }
Client端
- package org.xiazdong.client;
- import java.awt.BorderLayout;
- import java.awt.event.ActionEvent;
- import java.awt.event.ActionListener;
- import java.io.BufferedReader;
- import java.io.InputStreamReader;
- import java.io.OutputStream;
- import java.io.PrintStream;
- import java.io.PrintWriter;
- import java.net.Socket;
- import javax.swing.JButton;
- import javax.swing.JFrame;
- import javax.swing.JPanel;
- import javax.swing.JScrollPane;
- import javax.swing.JTextArea;
- import javax.swing.JTextField;
- public class Client3 extends JFrame{
- static JTextArea area;
- JTextField field;
- JButton button;
- static PrintWriter writer;
- public Client3(){
- this.setTitle("客戶端");
- this.setSize(400,500);
- area = new JTextArea(25,30);
- area.setEditable(false);
- field = new JTextField(20);
- button = new JButton("提交");
- JScrollPane sp = new JScrollPane(area);
- JPanel panel = new JPanel();
- this.add(sp,BorderLayout.CENTER);
- panel.add(field);
- panel.add(button);
- this.add(panel,BorderLayout.SOUTH);
- this.setVisible(true);
- this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- button.addActionListener(new ActionListener(){
- @Override
- public void actionPerformed(ActionEvent e) {
- String text = field.getText();
- writer.println(text);
- area.append("我:"+text+"\n");
- field.setText("");
- }
- });
- }
- public static void main(String[] args) throws Exception{
- Client3 c = new Client3();
- Socket socket = new Socket("127.0.0.1",8899);
- OutputStream out = socket.getOutputStream();
- BufferedReader reader1 = new BufferedReader(new InputStreamReader(socket.getInputStream()));
- writer = new PrintWriter(out,true);
- System.out.println("已經成功和服務器連接...");
- while(true){
- String line = reader1.readLine();
- area.append("服務器:"+line+"\n");
- }
- }
- }
PrintWriter的autoflush
如果PrintWriter writer = new PrintWriter(out,true);
則調用println()、printf()、format()函數時會自動刷新,其他函數都不會,比如write()、print()函數時不會自動刷新
網絡編程常見異常
第1個異常是java.net.BindException:Address already in use: JVM_Bind。該異常發生在服務器端進行new ServerSocket(port)(port是一個0,65536的整型值)操作時。異常的原因是以爲與port一樣的一個端口已經被啓動,並進行監聽。此時用netstat
–an命令,可以看到一個Listending狀態的端口。只需要找一個沒有被佔用的端口就能解決這個問題。
第2個異常是java.net.ConnectException: Connection refused: connect。該異常發生在客戶端進行new Socket(ip, port)操作時,該異常發生的原因是或者具有ip地址的機器不能找到(也就是說從當前機器不存在到指定ip路由),或者是該ip存在,但找不到指定的端口進行監聽。出現該問題,首先檢查客戶端的ip和port是否寫錯了,如果正確則從客戶端ping一下服務器看是否能ping通,如果能ping通(服務服務器端把ping禁掉則需要另外的辦法),則看在服務器端的監聽指定端口的程序是否啓動,這個肯定能解決這個問題。
第3個異常是java.net.SocketException: Socket is closed,該異常在客戶端和服務器均可能發生。異常的原因是己方主動關閉了連接後(調用了Socket的close方法)再對網絡連接進行讀寫操作。
第4個異常是java.net.SocketException: (Connection reset或者Connect reset by peer:Socket write error)。該異常在客戶端和服務器端均有可能發生,引起該異常的原因有兩個,第一個就是如果一端的Socket被關閉(或主動關閉或者因爲異常退出而引起的關閉),另一端仍發送數據,發送的第一個數據包引發該異常(Connect
reset by peer)。另一個是一端退出,但退出時並未關閉該連接,另一端如果在從連接中讀數據則拋出該異常(Connection reset)。簡單的說就是在連接斷開後的讀和寫操作引起的。
第5個異常是java.net.SocketException: Broken pipe。該異常在客戶端和服務器均有可能發生。在第4個異常的第一種情況中(也就是拋出SocketExcepton:Connect reset by peer:Socket write error後),如果再繼續寫數據則拋出該異常。前兩個異常的解決方法是首先確保程序退出前關閉所有的網絡連接,其次是要檢測對方的關閉連接操作,發現對方關閉連接後自己也要關閉該連接。