今日內容介紹
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();
}
}
}