JavaDay15 網絡編程

今日內容介紹
1、網絡三要素及傳輸協議
2、實現UDP協議的發送端和接收端
3、實現TCP協議的客戶端和服務器
4、TCP上傳文件案例


1. 網絡通信協議

1.1 網絡模型
     TCP/IP協議中的四層分別是應用層、傳輸層、網絡層和鏈路層,每層分別負責不同的通信功能,接下來針對這四層進行詳細地講解。
       鏈路層:鏈路層是用於定義物理傳輸通道,通常是對某些網絡連接設備的驅動協議,例如針對光纖、網線提供的驅動。
       網絡層:網絡層是整個TCP/IP協議的核心,它主要用於將傳輸的數據進行分組,將分組數據發送到目標計算機或者網絡。
       傳輸層:主要使網絡程序進行通信,在進行網絡通信時,可以採用TCP協議,也可以採用UDP協議。
       應用層:主要負責應用程序的協議,例如HTTP協議、FTP協議等。

1.2 IP地址

      在TCP/IP協議中,這個標識號就是IP地址,它可以唯一標識一臺計算機,
      目前,IP地址廣泛使用的版本是IPv4,它是由4個字節大小的二進制數來表示,如:00001010000000000000000000000001。
      由於二進制形式表示的IP地址非常不便記憶和處理,因此通常會將IP地址寫成十進制的形式,
      每個字節用一個十進制數字(0-255)表示,數字間用符號“.”分開,如 “192.168.1.100”
      127.0.0.1 爲本地主機地址(本地迴環地址)

1.3 端口號

    通過IP地址可以連接到指定計算機,但如果想訪問目標計算機中的某個應用程序,還需要指定端口號。
    在計算機中,不同的應用程序是通過端口號區分的。
    端口號是用兩個字節(16位的二進制數)表示的,它的取值範圍是0~65535,
    其中,0~1023之間的端口號已被操作系統內的網絡服務所佔用,用戶的普通應用程序需要使用1024以上的端口號,從而避免端口號被另外一個應用或服務所佔用

1.4 InetAddress類:該類用於封裝一個IP地址,並提供了一系列與IP地址相關的方法,下表中列出了InetAddress類的一些常用方法。

package inetaddress;

import java.net.InetAddress;
import java.net.UnknownHostException;

/*
 * 	表示互聯網中的IP地址
 * 		java.net.InetAddress
 * 	靜態方法
 * 		1. static InetAddress getLocalHost();  LocalHost本地主機
 * 			返回本地主機,返回值InetAddress對象
 *		2. static InetAddress getByName(String hostName)傳遞主機名,獲取IP地址對象 
 * 	非晶態方法
 * 		String getHoustAddress() 獲取主機IP地址
 * 		String getHoustName()	獲取主機名
 * */

public class InetAddressDemo{

	public static void main(String[] args) throws UnknownHostException {
		function_1();
	}
	
	public static void function() throws UnknownHostException{
		// 本地主機
		InetAddress inet = InetAddress.getLocalHost();
		System.out.println(inet.toString());
		
		String ip = inet.getHostAddress();
		String name = inet.getHostName();
		System.out.println(ip+"   "+name); 
	}
	
	public static void function_1() throws  UnknownHostException{
		// 傳遞主機名
		InetAddress inet = InetAddress.getByName("www.baidu.com");
		System.out.println(inet);
	}
}

2. UDP協議與TCP協議

2.1 UDP協議

     a:UDP協議概述:
      UDP是無連接通信協議,即在數據傳輸時,數據的發送端和接收端不建立邏輯連接。
      簡單來說,當一臺計算機向另外一臺計算機發送數據時,發送端不會確認接收端是否存在,就會發出數據,同樣接收端在收到數據時,也不會向發送端反饋是否收到數據。
     b:UDP協議特點:
      由於使用UDP協議消耗資源小,通信效率高,所以通常都會用於音頻、視頻和普通數據的傳輸例如視頻會議都使用UDP協議,
      因爲這種情況即使偶爾丟失一兩個數據包,也不會對接收結果產生太大影響。

注: UDP協議傳送數據上限爲64KB

2.2 TCP協議

    TCP協議是面向連接的通信協議,即在傳輸數據前先在發送端和接收端建立邏輯連接,然後再傳輸數據,它提供了兩臺計算機之間可靠無差錯的數據傳輸。
    在TCP連接中必須要明確客戶端與服務器端,
      由客戶端向服務端發出連接請求,每次連接的創建都需要經過“三次握手”。
      第一次握手,客戶端向服務器端發出連接請求,等待服務器確認
      第二次握手,服務器端向客戶端回送一個響應,通知客戶端收到了連接請求
      第三次握手,客戶端再次向服務器端發送確認信息,確認連接

3. UDP通信

3.1 數據包與發送對象介紹

    DatagramPacket數據包的作用就如同是“集裝箱”,
       可以將發送端或者接收端的數據封裝起來。然而運輸貨物只有“集裝箱”是不夠的,還需要有碼頭。
       在程序中需要實現通信只有DatagramPacket數據包也同樣不行,爲此JDK中提供的一個DatagramSocket類。
    DatagramSocket類的作用就類似於碼頭,使用這個類的實例對象就可以發送和接收DatagramPacket數據包
3.1.1 DatagramPacket:封裝數據

構造方法摘要
DatagramPacket(byte[] buf, int length) 
          構造 DatagramPacket,用來接收長度爲 length 的數據包。
DatagramPacket(byte[] buf, int length, InetAddress address, int port) 
          構造數據報包,用來將長度爲 length 的包發送到指定主機上的指定端口號。
DatagramPacket(byte[] buf, int offset, int length) 
          構造 DatagramPacket,用來接收長度爲 length 的包,在緩衝區中指定了偏移量。
DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port) 
          構造數據報包,用來將長度爲 length 偏移量爲 offset 的包發送到指定主機上的指定端口號。
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address) 
          構造數據報包,用來將長度爲 length 偏移量爲 offset 的包發送到指定主機上的指定端口號。
DatagramPacket(byte[] buf, int length, SocketAddress address) 
          構造數據報包,用來將長度爲 length 的包發送到指定主機上的指定端口號。
方法摘要
 InetAddress getAddress() 
          返回某臺機器的 IP 地址,此數據報將要發往該機器或者是從該機器接收到的。
 byte[] getData() 
          返回數據緩衝區。
 int getLength() 
          返回將要發送或接收到的數據的長度。
 int getOffset() 
          返回將要發送或接收到的數據的偏移量。
 int getPort() 
          返回某臺遠程主機的端口號,此數據報將要發往該主機或者是從該主機接收到的。
 SocketAddress getSocketAddress() 
          獲取要將此包發送到的或發出此數據報的遠程主機的 SocketAddress(通常爲 IP 地址 + 端口號)。
 void setAddress(InetAddress iaddr) 
          設置要將此數據報發往的那臺機器的 IP 地址。
 void setData(byte[] buf) 
          爲此包設置數據緩衝區。
 void setData(byte[] buf, int offset, int length) 
          爲此包設置數據緩衝區。
 void setLength(int length) 
          爲此包設置長度。
 void setPort(int iport) 
          設置要將此數據報發往的遠程主機上的端口號。
 void setSocketAddress(SocketAddress address) 
          設置要將此數據報發往的遠程主機的 SocketAddress(通常爲 IP 地址 + 端口號)。

3.1.2 DatagramSocket:發送DatagramPacket

構造方法摘要
  DatagramSocket() 
          構造數據報套接字並將其綁定到本地主機上任何可用的端口。
protected DatagramSocket(DatagramSocketImpl impl) 
          創建帶有指定 DatagramSocketImpl 的未綁定數據報套接字。
  DatagramSocket(int port) 
          創建數據報套接字並將其綁定到本地主機上的指定端口。
  DatagramSocket(int port, InetAddress laddr) 
          創建數據報套接字,將其綁定到指定的本地地址。
  DatagramSocket(SocketAddress bindaddr) 
          創建數據報套接字,將其綁定到指定的本地套接字地址。
方法摘要
 void bind(SocketAddress addr) 
          將此 DatagramSocket 綁定到特定的地址和端口。
 void close() 
          關閉此數據報套接字。
 void connect(InetAddress address, int port) 
          將套接字連接到此套接字的遠程地址。
 void connect(SocketAddress addr) 
          將此套接字連接到遠程套接字地址(IP 地址 + 端口號)。
 void disconnect() 
          斷開套接字的連接。
 boolean getBroadcast() 
          檢測是否啓用了 SO_BROADCAST。
 DatagramChannel getChannel() 
          返回與此數據報套接字關聯的唯一 DatagramChannel 對象(如果有)。
 InetAddress getInetAddress() 
          返回此套接字連接的地址。
 InetAddress getLocalAddress() 
          獲取套接字綁定的本地地址。
 int getLocalPort() 
          返回此套接字綁定的本地主機上的端口號。
 SocketAddress getLocalSocketAddress() 
          返回此套接字綁定的端點的地址,如果尚未綁定則返回 null
 int getPort() 
          返回此套接字的端口。
 int getReceiveBufferSize() 
          獲取此 DatagramSocket 的 SO_RCVBUF 選項的值,該值是平臺在 DatagramSocket 上輸入時使用的緩衝區大小。
 SocketAddress getRemoteSocketAddress() 
          返回此套接字連接的端點的地址,如果未連接則返回 null
 boolean getReuseAddress() 
          檢測是否啓用了 SO_REUSEADDR。
 int getSendBufferSize() 
          獲取此 DatagramSocket 的 SO_SNDBUF 選項的值,該值是平臺在 DatagramSocket 上輸出時使用的緩衝區大小。
 int getSoTimeout() 
          獲取 SO_TIMEOUT 的設置。
 int getTrafficClass() 
          爲從此 DatagramSocket 上發送的包獲取 IP 數據報頭中的流量類別或服務類型。
 boolean isBound() 
          返回套接字的綁定狀態。
 boolean isClosed() 
          返回是否關閉了套接字。
 boolean isConnected() 
          返回套接字的連接狀態。
 void receive(DatagramPacket p) 
          從此套接字接收數據報包。
 void send(DatagramPacket p) 
          從此套接字發送數據報包。
 void setBroadcast(boolean on) 
          啓用/禁用 SO_BROADCAST。
static void setDatagramSocketImplFactory(DatagramSocketImplFactory fac) 
          爲應用程序設置數據報套接字實現工廠。
 void setReceiveBufferSize(int size) 
          將此 DatagramSocket 的 SO_RCVBUF 選項設置爲指定的值。
 void setReuseAddress(boolean on) 
          啓用/禁用 SO_REUSEADDR 套接字選項。
 void setSendBufferSize(int size) 
          將此 DatagramSocket 的 SO_SNDBUF 選項設置爲指定的值。
 void setSoTimeout(int timeout) 
          啓用/禁用帶有指定超時值的 SO_TIMEOUT,以毫秒爲單位。
 void setTrafficClass(int tc) 
          爲從此 DatagramSocket 上發送的數據報在 IP 數據報頭中設置流量類別 (traffic class) 或服務類型八位組 (type-of-service octet)。

3.2 UDP通信程序

3.2.1 UDP發送端

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

/*
 * 	實現UDP協議的發送端:
 * 		實現封裝數據的類 java.net.DatagramPAcket
 * 		實現數據傳輸的類 java.net.DatagramSocket
 * 
 * 	實現步驟:
 * 		1. 創建DatagramPacket對象,封裝數據,接收地址與端口
 * 		2. 創建DatagramSocket
 * 		3. 調用DatagramSocket類方法send,發送數據包
 * 		4. 關閉資源
 * 	
 * 	DatagramPacket構造方法:
 * 		DatagramPacketP{byte[] buf, int length, InetAddress address, int port}
 * 
 * 	DatagramSocket構造方法:
 * 		DatagramSocket()空參數
 * 		方法:send(DatagramPacket d)
 * */

public class UDPSend {
	public static void main(String[] args) throws IOException {
		// 創建數據包對象,封裝要發送的數據,接收端IP,端口
		byte[] date = "你好UDP".getBytes();
		// 創建InetAddress對象,封裝自己的IP地址
		InetAddress inet  = InetAddress.getByName("127.0.0.1");
		DatagramPacket dp = new DatagramPacket(date,date.length,inet,6000);
		// 創建DatagramSocket對象,數據包的發送和接收對象
		DatagramSocket ds = new DatagramSocket();
		// 調用ds對象的方法send,發送數據包
		ds.send(dp);
		// 關閉資源
		ds.close();
	}	
}

3.2.2 UDP接收端

package udpdemo;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

/*
 *  實現UDP接收端
 *    實現封裝數據包 java.net.DatagramPacket 將數據接收
 *    實現輸出傳輸     java.net.DatagramSocket 接收數據包
 *    
 *  實現步驟:
 *     1. 創建DatagramSocket對象,綁定端口號
 *         要和發送端端口號一致
 *     2. 創建字節數組,接收發來的數據
 *     3. 創建數據包對象DatagramPacket
 *     4. 調用DatagramSocket對象方法
 *        receive(DatagramPacket dp)接收數據,數據放在數據包中
 *     5. 拆包
 *          發送的IP地址
 *            數據包對象DatagramPacket方法getAddress()獲取的是發送端的IP地址對象
 *            返回值是InetAddress對象
 *          接收到的字節個數
 *            數據包對象DatagramPacket方法 getLength()
 *          發送方的端口號
 *            數據包對象DatagramPacket方法 getPort()發送端口
 *     6. 關閉資源
 */
public class UDPReceive {
    public static void main(String[] args)throws IOException {
      //創建數據包傳輸對象DatagramSocket 綁定端口號
      DatagramSocket ds = new DatagramSocket(6000);
      //創建字節數組
      byte[] data = new byte[1024];
      //創建數據包對象,傳遞字節數組
      DatagramPacket dp = new DatagramPacket(data, data.length);
      //調用ds對象的方法receive傳遞數據包
      ds.receive(dp);   
      //獲取發送端的IP地址對象
      String ip=dp.getAddress().getHostAddress();    
      //獲取發送的端口號
      int port = dp.getPort();
      //獲取接收到的字節個數
      int length = dp.getLength();
      //爲避免資源浪費,根據字節長度打印
      System.out.println(new String(data,0,length)+"..."+ip+":"+port);
      ds.close();
    }
  }

3.2.3 案例:通過UDP發送實現聊天

發送端:

package qqdemo;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;

/*
 * 	實現UDP發送,添加鍵盤輸入,實現聊天功能
 * 	輸入完畢後,發送給接收端;
 * 	重複以上過程;
 * */

public class UDPQQSend {
	public static void main(String[] args) throws IOException {
		// 鍵盤輸入,實現聊天功能
		Scanner sc = new Scanner(System.in);
		
		// 創建DatagramSocket對象,數據包的發送和接收對象
		DatagramSocket ds = new DatagramSocket();
		// 創建InetAddress對象,封裝自己的IP地址
		InetAddress inet  = InetAddress.getByName("127.0.0.1");

		while(true) {
			String message = sc.nextLine();
			// 創建數據包對象,封裝要發送的數據,接收端IP,端口
			byte[] date = message.getBytes();	
			DatagramPacket dp = new DatagramPacket(date,date.length,inet,6000);
			// 調用ds對象的方法send,發送數據包
			ds.send(dp);
		}
		// 關閉資源 		ds.close();
	}	
}

接收端:

package qqdemo;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

/*
 *  實現UDP接收,永不停歇的進行接收
 */
public class UDPQQReceive {
    public static void main(String[] args)throws IOException {
      //創建數據包傳輸對象DatagramSocket 綁定端口號
      DatagramSocket ds = new DatagramSocket(6000);
      //創建字節數組
      byte[] data = new byte[1024];
      //創建數據包對象,傳遞字節數組
      while (true) {
	      DatagramPacket dp = new DatagramPacket(data, data.length);
	      //調用ds對象的方法receive傳遞數據包
	      ds.receive(dp);   
	      //獲取發送端的IP地址對象
	      String ip=dp.getAddress().getHostAddress();    
	      //獲取發送的端口號
	      int port = dp.getPort();
	      //獲取接收到的字節個數
	      int length = dp.getLength();
	      //爲避免資源浪費,根據字節長度打印
	      System.out.println(new String(data,0,length)+"..."+ip+":"+port);	
      }
		//	ds.close();
    }
 }

由於沒有圖形化界面,爲了方便查看效果,可以在控制檯建立多個console

4. TCP通信

TCP通信同UDP通信一樣,都能實現兩臺計算機之間的通信,通信的兩端都需要創建socket對象。

區別在於,UDP中只有發送端和接收端,不區分客戶端與服務器端,計算機之間可以任意地發送數據。

而TCP通信是嚴格區分客戶端與服務器端的,在通信時,必須先由客戶端去連接服務器端才能實現通信,服務器端不可以主動連接客戶端,並且服務器端程序需要事先啓動,等待客戶端的連接。

在JDK中提供了兩個類用於實現TCP程序,一個是ServerSocket類,用於表示服務器端,一個是Socket類,用於表示客戶端

通信時,首先創建代表服務器端的ServerSocket對象,該對象相當於開啓一個服務,並等待客戶端的連接,然後創建代表客戶端的Socket對象向服務器端發出連接請求,服務器端響應請求,兩者建立連接開始通信。

4.1 Socket類與ServerSocket類

4.1.1 socket類:用於實現TCP客戶端程序

在Socket類的常用方法中,getInputStream()和getOutStream()方法分別用於獲取輸入流和輸出流。當客戶端和服務端建立連接後,數據是以IO流的形式進行交互的,從而實現通信。

構造方法摘要
  Socket() 
          通過系統默認類型的 SocketImpl 創建未連接套接字
  Socket(InetAddress address, int port) 
          創建一個流套接字並將其連接到指定 IP 地址的指定端口號。
  Socket(InetAddress host, int port, boolean stream) 
          已過時。 Use DatagramSocket instead for UDP transport.
  Socket(InetAddress address, int port, InetAddress localAddr, int localPort) 
          創建一個套接字並將其連接到指定遠程地址上的指定遠程端口。
  Socket(Proxy proxy) 
          創建一個未連接的套接字並指定代理類型(如果有),該代理不管其他設置如何都應被使用。
protected Socket(SocketImpl impl) 
          使用用戶指定的 SocketImpl 創建一個未連接 Socket。
  Socket(String host, int port) 
          創建一個流套接字並將其連接到指定主機上的指定端口號。
  Socket(String host, int port, boolean stream) 
          已過時。 使用 DatagramSocket 取代 UDP 傳輸。
  Socket(String host, int port, InetAddress localAddr, int localPort) 
          創建一個套接字並將其連接到指定遠程主機上的指定遠程端口。
方法摘要
 void bind(SocketAddress bindpoint) 
          將套接字綁定到本地地址。
 void close() 
          關閉此套接字。
 void connect(SocketAddress endpoint) 
          將此套接字連接到服務器。
 void connect(SocketAddress endpoint, int timeout) 
          將此套接字連接到服務器,並指定一個超時值。
 SocketChannel getChannel() 
          返回與此數據報套接字關聯的唯一 SocketChannel 對象(如果有)。
 InetAddress getInetAddress() 
          返回套接字連接的地址。
 InputStream getInputStream() 
          返回此套接字的輸入流。
 boolean getKeepAlive() 
          測試是否啓用 SO_KEEPALIVE。
 InetAddress getLocalAddress() 
          獲取套接字綁定的本地地址。
 int getLocalPort() 
          返回此套接字綁定到的本地端口。
 SocketAddress getLocalSocketAddress() 
          返回此套接字綁定的端點的地址,如果尚未綁定則返回 null
 boolean getOOBInline() 
          測試是否啓用 OOBINLINE。
 OutputStream getOutputStream() 
          返回此套接字的輸出流。
 int getPort() 
          返回此套接字連接到的遠程端口。
 int getReceiveBufferSize() 
          獲取此 Socket 的 SO_RCVBUF 選項的值,該值是平臺在 Socket 上輸入時使用的緩衝區大小。
 SocketAddress getRemoteSocketAddress() 
          返回此套接字連接的端點的地址,如果未連接則返回 null
 boolean getReuseAddress() 
          測試是否啓用 SO_REUSEADDR。
 int getSendBufferSize() 
          獲取此 Socket 的 SO_SNDBUF 選項的值,該值是平臺在 Socket 上輸出時使用的緩衝區大小。
 int getSoLinger() 
          返回 SO_LINGER 的設置。
 int getSoTimeout() 
          返回 SO_TIMEOUT 的設置。
 boolean getTcpNoDelay() 
          測試是否啓用 TCP_NODELAY。
 int getTrafficClass() 
          爲從此 Socket 上發送的包獲取 IP 頭中的流量類別或服務類型。
 boolean isBound() 
          返回套接字的綁定狀態。
 boolean isClosed() 
          返回套接字的關閉狀態。
 boolean isConnected() 
          返回套接字的連接狀態。
 boolean isInputShutdown() 
          返回是否關閉套接字連接的半讀狀態 (read-half)。
 boolean isOutputShutdown() 
          返回是否關閉套接字連接的半寫狀態 (write-half)。
 void sendUrgentData(int data) 
          在套接字上發送一個緊急數據字節。
 void setKeepAlive(boolean on) 
          啓用/禁用 SO_KEEPALIVE。
 void setOOBInline(boolean on) 
          啓用/禁用 OOBINLINE(TCP 緊急數據的接收者) 默認情況下,此選項是禁用的,即在套接字上接收的 TCP 緊急數據被靜默丟棄。
 void setPerformancePreferences(int connectionTime, int latency, int bandwidth) 
          設置此套接字的性能偏好。
 void setReceiveBufferSize(int size) 
          將此 Socket 的 SO_RCVBUF 選項設置爲指定的值。
 void setReuseAddress(boolean on) 
          啓用/禁用 SO_REUSEADDR 套接字選項。
 void setSendBufferSize(int size) 
          將此 Socket 的 SO_SNDBUF 選項設置爲指定的值。
static void setSocketImplFactory(SocketImplFactory fac) 
          爲應用程序設置客戶端套接字實現工廠。
 void setSoLinger(boolean on, int linger) 
          啓用/禁用具有指定逗留時間(以秒爲單位)的 SO_LINGER。
 void setSoTimeout(int timeout) 
          啓用/禁用帶有指定超時值的 SO_TIMEOUT,以毫秒爲單位。
 void setTcpNoDelay(boolean on) 
          啓用/禁用 TCP_NODELAY(啓用/禁用 Nagle 算法)。
 void setTrafficClass(int tc) 
          爲從此 Socket 上發送的包在 IP 頭中設置流量類別 (traffic class) 或服務類型八位組 (type-of-service octet)。
 void shutdownInput() 
          此套接字的輸入流置於“流的末尾”。
 void shutdownOutput() 
          禁用此套接字的輸出流。
 String toString() 
          將此套接字轉換爲 String

4.1.2 ServerSocket類:該類的實例對象可以實現一服務器端的程序

ServerSocket對象負責監聽某臺計算機的某個端口號,在創建ServerSocket對象後,需要繼續調用該對象的accept()方法,接收來自客戶端的請求。當執行了accept()方法之後,服務器端程序會發生阻塞,直到客戶端發出連接請求,accept()方法纔會返回一個Scoket對象用於和客戶端實現通信,程序才能繼續向下執行。

構造方法摘要
ServerSocket() 
          創建非綁定服務器套接字。
ServerSocket(int port) 
          創建綁定到特定端口的服務器套接字。
ServerSocket(int port, int backlog) 
          利用指定的 backlog 創建服務器套接字並將其綁定到指定的本地端口號。
ServerSocket(int port, int backlog, InetAddress bindAddr) 
          使用指定的端口、偵聽 backlog 和要綁定到的本地 IP 地址創建服務器。
方法摘要
 Socket accept() 
          偵聽並接受到此套接字的連接。
 void bind(SocketAddress endpoint) 
          將 ServerSocket 綁定到特定地址(IP 地址和端口號)。
 void bind(SocketAddress endpoint, int backlog) 
          將 ServerSocket 綁定到特定地址(IP 地址和端口號)。
 void close() 
          關閉此套接字。
 ServerSocketChannel getChannel() 
          返回與此套接字關聯的唯一 ServerSocketChannel 對象(如果有)。
 InetAddress getInetAddress() 
          返回此服務器套接字的本地地址。
 int getLocalPort() 
          返回此套接字在其上偵聽的端口。
 SocketAddress getLocalSocketAddress() 
          返回此套接字綁定的端點的地址,如果尚未綁定則返回 null
 int getReceiveBufferSize() 
          獲取此 ServerSocket 的 SO_RCVBUF 選項的值,該值是將用於從此 ServerSocket 接受的套接字的建議緩衝區大小。
 boolean getReuseAddress() 
          測試是否啓用 SO_REUSEADDR。
 int getSoTimeout() 
          獲取 SO_TIMEOUT 的設置。
protected  void implAccept(Socket s) 
          ServerSocket 的子類使用此方法重寫 accept() 以返回它們自己的套接字子類。
 boolean isBound() 
          返回 ServerSocket 的綁定狀態。
 boolean isClosed() 
          返回 ServerSocket 的關閉狀態。
 void setPerformancePreferences(int connectionTime, int latency, int bandwidth) 
          設置此 ServerSocket 的性能首選項。
 void setReceiveBufferSize(int size) 
          爲從此 ServerSocket 接受的套接字的 SO_RCVBUF 選項設置默認建議值。
 void setReuseAddress(boolean on) 
          啓用/禁用 SO_REUSEADDR 套接字選項。
static void setSocketFactory(SocketImplFactory fac) 
          爲應用程序設置服務器套接字實現工廠。
 void setSoTimeout(int timeout) 
          通過指定超時值啓用/禁用 SO_TIMEOUT,以毫秒爲單位。
 String toString() 
          作爲 String 返回此套接字的實現地址和實現端口。

對於網絡編程,最終都要的部分是IO流的使用;Socket / ServerSocket 的使用就只需要連接好服務器即可。

4.2 TCP通信程序:實現客戶端與服務器的數據交換

4.2.1 TCP的客戶端程序

package tcpdemo;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/*
 * 	實現TCP客戶端,連接服務器
 * 	和服務器實現數據交換
 * 	實現TCP客戶端程序中的類 java.net.Socket
 * 
 * 	構造方法:
 * 		Socket(String host , int port) 傳遞服務器IP和端口號
 * 		注意:構造方法只要運行,就會和服務器進行連接,連接失敗,拋出異常
 * 		
 * 		OutputStream getOutputStream()	返回Socket的輸出流
 * 		作用:將數據輸出,輸出到服務器
 * 
 * 		InputStream getInputStream() 
 * 		作用:從服務器端讀取數據
 * 
 * 		客戶端服務器數據交換,必須使用Socket中擴區的IO流
 * */
public class TCPClient {

	public static void main(String[] args) throws IOException{
		// 創建Socket對象,連接服務器
		Socket socket = new Socket("127.0.0.1",8888);
		// 通過客戶端的套接字對象Socket方法,獲取字節輸出流,將數據斜寫向服務器
		OutputStream os = socket.getOutputStream();
		os.write("Hi ~ ".getBytes());
		
		// 讀取服務器發回的數據,使用socket套接字對象中的字節輸入流
		InputStream is = socket.getInputStream();
		byte[] data = new byte[1024];
		int len = is.read(data);
		System.out.println(new String(data,0,len));
		
		socket.close();
	}
}

4.2.2 TCP服務器端程序

package tcpdemo;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/*
 *	實現TCP服務器程序 
 *	表示服務器程序的類 java.net。ServerSocket
 *
 *	構造方法:
 *		ServerSocket(int port)	傳遞端口號
 *
 *	有一點很重要:必須要獲得客戶端的的套接字對象Socket,知道是與誰連接了
 *	Socket accept()	
 * */
	
	public class TCPServer {
	
		public static void main(String[] args) throws IOException {
			ServerSocket server = new ServerSocket(8888);
			// 調用服務器套接字對象中的方法accept()	獲取客戶端套接字對象
			Socket socket = server.accept();
			// 通過客戶端套接字對象,socket獲取字節輸入流,讀取的是客戶端發送來的數據d
			InputStream is = socket.getInputStream();
			byte[] data = new byte[1024];
			int len = is.read(data);
			System.out.println(new String (data,0,len));
			
			// 服務器向客戶端回數據,字節輸出流,通過客戶端套接字對象獲取字節輸出流
			OutputStream os = socket.getOutputStream();
			os.write("嚶嚶嚶~ ".getBytes());
			
			socket.close();
			server.close();
	}

}

4.3 TCP圖片上傳案例

本質其實就是文件的複製。

Server端:


import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Random;


/*
 * 	TCP圖片上傳服務器
 *		1. ServerSocket套接字對象,監聽端口8000
 *		2. 方法accept() 獲取客戶端的連接對象
 *		3. 客戶端連接對象獲取字節輸入流,讀取 圖片數據源 FileInputStream
 *		4. 創建File對象,綁定長傳文件夾:判斷文件夾是否存在,如果爲否則創建文件夾
 *		5. 創建字節輸出流,數據目的File對象所在文件夾
 *		6. 字節流讀取圖片,字節流將圖片寫入目的文件夾
 *		7. 將上傳成功的信息反饋給客戶端		
 *		8. 關閉資源		
 * */
public class TCPServer {

	public static void main(String[] args) throws IOException {
		ServerSocket server = new ServerSocket(8000);
		Socket socket = server.accept();
		// 通過客戶端連接對象,獲取字節輸入流,讀取客戶端圖片
		InputStream in = socket.getInputStream();
		// 將目的文件夾封裝到File對象f
		File upload = new File("D:\\Study\\Java\\JavaTest\\upload");
		// 判斷文件是否存在,若不存在則創建
		if (!upload.exists()) {
			upload.mkdirs();
		}
		// 爲防止文件同名被覆蓋,重新定義文件名字:域名 + 毫秒值 + 6位隨機數
		String filename = System.currentTimeMillis() + new Random().nextInt(999999) + ".jpg";
		// 創建字節輸出流,將圖片寫入目的文件夾中
		FileOutputStream fos = new FileOutputStream(upload + File.separator + filename);
		// 讀寫字節數組
		byte [] bytes = new byte[1024];
		int len = 0 ;
		while((len = in.read(bytes)) != -1) {
			fos.write(bytes,0,len);
		}
		// 通過客戶端連接對象獲取字節輸出流;並將上傳成功信息反饋回客戶端
		socket.getOutputStream().write("上傳成功".getBytes());
		
		fos.close();
		socket.close();
		server.close();
	}

}

Client端:


import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

/*
 * 	實現步驟:
 * 		1. socket套接字連接服務器
 * 		2. 通過socket獲取字節輸出流,將本地數據上傳到服務器
 * 		3. 使用自己的流對象,讀取圖片數據源: FileInputStream
 * 		4. 本地客戶端讀取圖片的字節輸入,使用字節輸出流,將圖片寫到服務器(採取字節數組進行緩衝)
 * 		5. 通過socket套接字獲取字節輸入流,讀取服務器發回來的長傳成功反饋
 * 		6. 關閉資源
 * */

public class TCPClient {

	public static void main(String[] args) throws IOException{
		Socket socket = new Socket("127.0.0.1",8000);
		// 獲取字節輸出流,圖片寫到服務器
		OutputStream os = socket.getOutputStream();
		// 創建字節輸入流,讀取本機上的數據源圖片
		FileInputStream fis = new FileInputStream("C:\\Users\\jayzh\\Pictures\\Camera Roll\\TCP上傳圖片.png");
		// 開始讀寫字節數組
		int len = 0;
		byte[] bytes = new byte[1024];
		while ((len = fis.read(bytes)) != -1) {
			os.write(bytes, 0, len);
		}
		// 給服務器終止序列
		socket.shutdownOutput();
		
		// 獲取字節輸入流,讀取服務器的上傳成功
		InputStream in = socket.getInputStream();
		len = in.read(bytes);
		System.out.println(new String(bytes,0,len));
		
		fis.close();
		socket.close();
	}

}

4.4 多線程上傳案例

將服務器上傳的操作用多線程技術實現併發;


import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.Socket;
import java.util.Random;

public class Upload implements Runnable {
	private Socket socket;
	
	public Upload(Socket socket) {
		this.socket = socket;
	}
	
	@Override
	public void run() {
		try {
			// 通過客戶端連接對象,獲取字節輸入流,讀取客戶端圖片
			InputStream in = socket.getInputStream();
			// 將目的文件夾封裝到File對象f
			File upload = new File("D:\\Study\\Java\\JavaTest\\upload");
			// 判斷文件是否存在,若不存在則創建
			if (!upload.exists()) {
				upload.mkdirs();
			}
			// 爲防止文件同名被覆蓋,重新定義文件名字:域名 + 毫秒值 + 6位隨機數
			String filename = System.currentTimeMillis() + new Random().nextInt(999999) + ".jpg";
			// 創建字節輸出流,將圖片寫入目的文件夾中
			FileOutputStream fos = new FileOutputStream(upload + File.separator + filename);
			// 讀寫字節數組
			byte [] bytes = new byte[1024];
			int len = 0 ;
			while((len = in.read(bytes)) != -1) {
				fos.write(bytes,0,len);
			}
			// 通過客戶端連接對象獲取字節輸出流;並將上傳成功信息反饋回客戶端
			socket.getOutputStream().write("上傳成功".getBytes());
			
			fos.close();
			socket.close();
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
			throw new RuntimeException("上傳失敗");
		}	
	}
}

客戶端:保持不變


import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class TCPThreadClient {

	public static void main(String[] args) throws IOException{
		Socket socket = new Socket("127.0.0.1",8000);
		// 獲取字節輸出流,圖片寫到服務器
		OutputStream os = socket.getOutputStream();
		// 創建字節輸入流,讀取本機上的數據源圖片
		FileInputStream fis = new FileInputStream("C:\\Users\\jayzh\\Pictures\\Camera Roll\\TCP上傳圖片.png");
		// 開始讀寫字節數組
		int len = 0;
		byte[] bytes = new byte[1024];
		while ((len = fis.read(bytes)) != -1) {
			os.write(bytes, 0, len);
		}
		// 給服務器終止序列
		socket.shutdownOutput();
		
		// 獲取字節輸入流,讀取服務器的上傳成功
		InputStream in = socket.getInputStream();
		len = in.read(bytes);
		System.out.println(new String(bytes,0,len));
		
		fis.close();
		socket.close();
	}

}

服務器:每次獲取一個客戶端,開啓一個新線程

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class TCPThreadServer {

	public static void main(String[] args) throws IOException {
		ServerSocket server = new ServerSocket(8000);
		while (true){
			// 獲取到一個客戶端,必須開啓新線程
			Socket socket = server.accept();
			new Thread(new Upload(socket)).start();
		}
	}
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章