Java中Socket用法詳解

1 問題引入

1.1 網絡架構模型

網絡架構模型主要有OSI參考模型和TCP/IP五層模型

1.1.1 OSI參考模型

OSI(Open System Interconnect),即開放式系統互聯。一般都叫OSI參考模型,是ISO(國際標準化組織)組織在1985年研究的網絡互連模型。ISO為瞭更好的使網絡應用更為普及,推出瞭OSI參考模型,這樣所有的公司都按照統一的標準來指定自己的網絡,就可以互通互聯瞭。

OSI定義瞭網絡互連的七層框架(物理層、數據鏈路層、網絡層、傳輸層、會話層、表示層、應用層)。

1.1.2 TCP/IP五層模型

TCP/IP五層協議(物理層、數據鏈路層、網絡層、傳輸層、應用層)

1.1.3 各協議層的說明

應用層

應用層最靠近用戶的一層,是為計算機用戶提供應用接口,也為用戶直接提供各種網絡服務。我們常見應用層的網絡服務協議有:HTTP,HTTPS,FTP,TELNET等。

傳輸層

建立瞭主機端到端的鏈接,傳輸層的作用是為上層協議提供端到端的可靠和透明的數據傳輸服務,包括處理差錯控制和流量控制等問題。該層向高層屏蔽瞭下層數據通信的細節,使高層用戶看到的隻是在兩個傳輸實體間的一條主機到主機的、可由用戶控制和設定的、可靠的數據通路。我們通常說的,TCP UDP就是在這一層。端口號既是這裡的“端”。

網絡層

本層通過IP尋址來建立兩個節點之間的連接,為源端的運輸層送來的分組,選擇合適的路由和交換節點,正確無誤地按照地址傳送給目的端的運輸層。就是通常說的IP層。這一層就是我們經常說的IP協議層。IP協議是Internet的基礎。

1.2 網絡編程中的問題

常見的網絡編程中的問題主要是怎麼定位網絡上的一臺主機或多臺主機,另一個是定位後如何進行數據的傳輸。對於前者,在網絡層中主要負責網絡主機的定位,數據傳輸的路由,由IP地址可以唯一地確定Internet上的一臺主機。對於後者,在傳輸層則提供面向應用的可靠(tcp)的或非可靠(UDP)的數據傳輸機制。

對於客戶端/服務器(C/S)結構。 即通信雙方一方作為服務器等待客戶提出請求並予以響應。客戶則在需要服務時向服務器提出申請。服務器一般作為守護進程始終運行,監聽網絡端口,一旦有客戶請求,就會啟動一個服務進程來響應該客戶,同時自己繼續監聽服務端口,使後來的客戶也能及時得到服務。

對於瀏覽器/服務器(B/S)結構。 客戶則在需要服務時向服務器進行請求。服務器響應後及時返回,不需要實時監聽端口。

1.3 TCP協議與UDP協議

1.3.1 TCP

TCP是(Tranfer Control Protocol)的簡稱,是一種面向連接的保證可靠傳輸的協議。通過TCP協議傳輸,得到的是一個順序的無差錯的數據流。發送方和接收方的成對的兩個socket之間必須建立連接,當一個socket(通常都是server socket)等待建立連接時,另一個socket可以要求進行連接,一旦這兩個socket連接起來,它們就可以進行雙向數據傳輸,雙方都可以進行發送或接收操作。

TCP的三次握手

建立起一個TCP連接需要經過“三次握手”:第一次握手:客戶端發送syn包(syn=j)到服務器,並進入SYN_SEND狀態,等待服務器確認;第二次握手:服務器收到syn包,必須確認客戶的SYN(ack=j+1),同時自己也發送一個SYN包(syn=k),即SYN+ACK包,此時服務器進入SYN_RECV狀態;第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=k+1),此包發送完畢,客戶端和服務器進入ESTABLISHED狀態,完成三次握手。

握手過程中傳送的包裡不包含數據,三次握手完畢後,客戶端與服務器才正式開始傳送數據。理想狀態下,TCP連接一旦建立,在通信雙方中的任何一方主動關閉連接之前,TCP 連接都將被一直保持下去。斷開連接時服務器和客戶端均可以主動發起斷開TCP連接的請求。

1.3.2 UDP

UDP是(User Datagram Protocol)的簡稱,是一種無連接的協議,每個數據報都是一個獨立的信息,包括完整的源地址或目的地址,它在網絡上以任何可能的路徑傳往目的地,因此能否到達目的地,到達目的地的時間以及內容的正確性都是不能被保證的。

1.3.3 TCP和UDP的區別

UDP:
  • 1、每個數據報中都給出瞭完整的地址信息,因此無需要建立發送方和接收方的連接。
  • 2、UDP傳輸數據時是有大小限制的,每個被傳輸的數據報必須限定在64KB之內。
  • 3、UDP是一個不可靠的協議,發送方所發送的數據報並不一定以相同的次序到達接收方
TCP:
  • 1、面向連接的協議,在socket之間進行數據傳輸之前必然要建立連接,所以在TCP中需要連接時間。
  • 2、TCP傳輸數據沒有大小限制,一旦連接建立起來,雙方的socket就可以按統一的格式傳輸大的數據。
  • 3、TCP是一個可靠的協議,它確保接收方完全正確地獲取發送方所發送的全部數據。
應用:
  • 1、TCP在網絡通信上有極強的生命力,例如遠程連接(Telnet)和文件傳輸(FTP)都需要不定長度的數據被可靠地傳輸。但是可靠的傳輸是要付出代價的,對數據內容正確性的檢驗必然占用計算機的處理時間和網絡的帶寬,因此TCP傳輸的效率不如UDP高。
  • 2、UDP操作簡單,而且僅需要較少的監護,因此通常用於局域網高可靠性的分散系統中client/server應用程序。例如視頻會議系統,並不要求音頻視頻數據絕對的正確,隻要保證連貫性就可以瞭,這種情況下顯然使用UDP會更合理一些。

2 socket網絡編程

2.1什麼是socket?

Socket的英文原義是“孔”或“插座”。在網絡編程中,網絡上的兩個程序通過一個雙向的通信連接實現數據的交換,這個連接的一端稱為一個socket。

Socket套接字是通信的基石,是支持TCP/IP協議的網絡通信的基本操作單元。它是網絡通信過程中端點的抽象表示,包含進行網絡通信必須的五種信息:連接使用的協議,本地主機的IP地址,本地進程的協議端口,遠地主機的IP地址,遠地進程的協議端口。

Socket本質是編程接口(API),對TCP/IP的封裝,TCP/IP也要提供可供程序員做網絡開發所用的接口,這就是Socket編程接口;HTTP是轎車,提供瞭封裝或者顯示數據的具體形式;Socket是發動機,提供瞭網絡通信的能力。

2.2 Socket的原理

Socket實質上提供瞭進程通信的端點。進程通信之前,雙方首先必須各自創建一個端點,否則是沒有辦法建立聯系並相互通信的。正如打電話之前,雙方必須各自擁有一臺電話機一樣。

套接字之間的連接過程可以分為三個步驟:服務器監聽,客戶端請求,連接確認。

  • 1、服務器監聽:是服務器端套接字並不定位具體的客戶端套接字,而是處於等待連接的狀態,實時監控網絡狀態。
  • 2、客戶端請求:是指由客戶端的套接字提出連接請求,要連接的目標是服務器端的套接字。為此,客戶端的套接字必須首先描述它要連接的服務器的套接字,指出服務器端套接字的地址和端口號,然後就向服務器端套接字提出連接請求。
  • 3、連接確認:是指當服務器端套接字監聽到或者說接收到客戶端套接字的連接請求,它就響應客戶端套接字的請求,建立一個新的線程,把服務器端套接字的描述發給客戶端,一旦客戶端確認瞭此描述,連接就建立好瞭。而服務器端套接字繼續處於監聽狀態,繼續接收其他客戶端套接字的連接請求。

3 基於java的socket網絡編程實現

Server端Listen監聽某個端口是否有連接請求,Client端向Server 端發出連接請求,Server端向Client端發回Accept接受消息。這樣一個連接就建立起來瞭。Server端和Client端都可以通過Send,Write等方法與對方通信。

對於一個功能齊全的Socket,都要包含以下基本結構,其工作過程包含以下四個基本的步驟:

  • 1、創建Socket;
  • 2、 打開連接到Socket的輸入/出流;
  • 3、按照一定的協議對Socket進行讀/寫操作;
  • 4、關閉Socket。

3.1 基於TCP的socket實現

SocketClient.java

public class SocketClient {
	
	public static void main(String[] args) throws InterruptedException {
		try {
			// 和服務器創建連接
			Socket socket = new Socket("localhost",8088);
			
			// 要發送給服務器的信息
			OutputStream os = socket.getOutputStream();
			PrintWriter pw = new PrintWriter(os);
			pw.write("客戶端發送信息");
			pw.flush();
			
			socket.shutdownOutput();
			
			// 從服務器接收的信息
			InputStream is = socket.getInputStream();
			BufferedReader br = new BufferedReader(new InputStreamReader(is));
			String info = null;
			while((info = br.readLine())!=null){
				System.out.println("我是客戶端,服務器返回信息:"+info);
			}
			
			br.close();
			is.close();
			os.close();
			pw.close();
			socket.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
 
}

SocketServer.java

public class SocketServer {
	
	public static void main(String[] args) {
		try {
			// 創建服務端socket
			ServerSocket serverSocket = new ServerSocket(8088);
			
			// 創建客戶端socket
			Socket socket = new Socket();	
			
			//循環監聽等待客戶端的連接
            while(true){
            	// 監聽客戶端
            	socket = serverSocket.accept();
            	
            	ServerThread thread = new ServerThread(socket);
            	thread.start();
            	
            	InetAddress address=socket.getInetAddress();
                System.out.println("當前客戶端的IP:"+address.getHostAddress());
            }
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}
 
}

ServerThread.java

public class ServerThread extends Thread{
	
	private Socket socket = null;
	
	public ServerThread(Socket socket) {
		this.socket = socket;
	}
 
	@Override
	public void run() {
		InputStream is=null;
        InputStreamReader isr=null;
        BufferedReader br=null;
        OutputStream os=null;
        PrintWriter pw=null;
        try {
			is = socket.getInputStream();
			isr = new InputStreamReader(is);
			br = new BufferedReader(isr);
			
			String info = null;
			
			while((info=br.readLine())!=null){
				System.out.println("我是服務器,客戶端說:"+info);
			}
			socket.shutdownInput();
			
			os = socket.getOutputStream();
			pw = new PrintWriter(os);
			pw.write("服務器歡迎你");
			
			pw.flush();
        } catch (Exception e) {
			// TODO: handle exception
		} finally{
			//關閉資源
            try {
                if(pw!=null)
                    pw.close();
                if(os!=null)
                    os.close();
                if(br!=null)
                    br.close();
                if(isr!=null)
                    isr.close();
                if(is!=null)
                    is.close();
                if(socket!=null)
                    socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
		}
	}
 
}

在運行時,若先執行SocketClient會提示無法連接到服務器,因為此時沒有服務在監聽8088端口。此demo是多線程實現,在先啟動SocketServer後,服務器會一直監聽8088端口,再執行SocketClient就會正常輸出結果。

3.2 基於UDP的socket實現

SocketClient.java

public class SocketClient {
	
	public static void main(String[] args) {
		try {
            // 要發送的消息
            String sendMsg = "客戶端發送的消息";
            
            // 獲取服務器的地址
            InetAddress addr = InetAddress.getByName("localhost");
            
            // 創建packet包對象,封裝要發送的包數據和服務器地址和端口號
            DatagramPacket packet = new DatagramPacket(sendMsg.getBytes(),
            		sendMsg.getBytes().length, addr, 8088); 
            
            // 創建Socket對象
            DatagramSocket socket = new DatagramSocket();
            
            // 發送消息到服務器
            socket.send(packet);
 
            // 關閉socket
            socket.close();
            
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
	}
}

SocketServer.java

public class SocketServer {
	
	public static void main(String[] args) {
		try {
			 // 要接收的報文
			byte[] bytes = new byte[1024];
			DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
			
			// 創建socket並指定端口
			DatagramSocket socket = new DatagramSocket(8088);
			
			// 接收socket客戶端發送的數據。如果未收到會一致阻塞
			socket.receive(packet);
			String receiveMsg = new String(packet.getData(),0,packet.getLength());
			System.out.println(packet.getLength());
			System.out.println(receiveMsg);
			
			// 關閉socket
			socket.close();
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}
 
}

運行時,先啟動SocketServer,再啟動SocketClient,會正常打印數據。在先啟動SocketServer時,代碼執行到socket.receive(packet)時會一致阻塞在這裡,直到啟動SocketClient後,SocketServer會繼續執行,並將收到SocketClient的信息打印出來。如果是先啟動SocketClient,會立即執行完畢,再執行SocketServer時,依舊會阻塞在receive方法處,直到下一次SocketClient的執行。

參考

Java Scoket編程

WebSocket與消息推送

JAVA 通過 Socket 實現 TCP 編程

OSI七層模型與TCP/IP五層模型

以上就是本文的全部內容,希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。

推薦閱讀: