網(wǎng)絡編程
為什么需要網(wǎng)絡編程?
現(xiàn)在網(wǎng)絡普及程序越來越高,網(wǎng)絡上保存著我們?nèi)粘I钪行枰母鞣N資源,使用程序通過網(wǎng)絡來獲取這些資源的過程就需要網(wǎng)絡編程來實現(xiàn)。
什么是網(wǎng)絡編程?
網(wǎng)絡編程,指網(wǎng)絡上的主機,通過不同的進程以程序的方式實現(xiàn)網(wǎng)絡通信(網(wǎng)絡數(shù)據(jù)傳輸)。
注意也可以是同一個主機的不同進程,比如,MySQL的服務端也客戶端,在開發(fā)環(huán)境一般都是在同一臺主機上運行的兩個不同的程序。
怎么進行網(wǎng)絡編程?
針對網(wǎng)絡編程,操作系統(tǒng)提供了用于網(wǎng)絡編程的技術,稱為Socket套接字,是系統(tǒng)提供的專門用來實現(xiàn)網(wǎng)絡編程的一套API。Java對每種操作系統(tǒng)做了進一步的封裝。應用程序在應用層,操作系統(tǒng)工作在傳輸層,Socket套接字就是傳輸層對應用層提供的API支持。傳輸層中最知名的協(xié)議就是TCP和 UDP。
客戶端和服務器
客戶端:服務的使用方。請求一般是客戶端主動發(fā)起,表示目的。
服務器:服務的提供方。響應一般是服務器根據(jù)客戶端的請求計算出來的結(jié)果。
Socket套接字
Socket套接字,是由系統(tǒng)提供用于網(wǎng)絡通信的技術,是基于TCP/IP協(xié)議的網(wǎng)絡通信的基本操作單元?;赟ocket套接字的網(wǎng)絡程序開發(fā)就是網(wǎng)絡編程。
流套接字TCP
使用傳輸層TCP協(xié)議
TCP,即Transmission Control Protocol (傳輸控制協(xié)議) ,傳輸層協(xié)議。
TCP協(xié)議的特點:
1.有連接
2.可靠傳輸
3.面向字節(jié)流
4.全雙工(有接收緩沖區(qū),也有發(fā)送緩沖區(qū))
5.大小不限
傳輸數(shù)據(jù)是基于IO流,沒有邊界多次發(fā)送,多次接收。
數(shù)據(jù)報套接字UDP
使用傳輸層UDP協(xié)議
UDP,即User Datagram Protocol (用戶數(shù)據(jù)報協(xié)議),傳輸層協(xié)議。
UDP協(xié)議的特點:
1無連接
2.不可靠傳輸
3.面向數(shù)據(jù)報
4.全雙式(有接收緩沖區(qū),也有發(fā)送緩沖區(qū))
5.大小受限,一次最多傳輸64K
傳輸數(shù)據(jù)是一個整體一個整體,不能分開發(fā)送。
對比TCP與UDP
1.有無連接:TCP相當于打電話,接聽方必須要接通電話雙方才能通信。UDP相當于發(fā)短信,對方有沒有開機都不重要。
2.可靠傳輸: 打電話時必須要經(jīng)過撥號,接聽后才可以通話,發(fā)短信發(fā)出去就不管了。如果數(shù)據(jù)包在傳輸?shù)倪^程中丟了,TCP會有重傳機制,UDP沒有。
3.面向字節(jié)流和面向數(shù)據(jù)報: 打電話說一個字對方就可以聽到一個字,發(fā)短信發(fā)一條條的信息對接收到才可以閱讀。
4.全雙工(有接收緩沖區(qū),也有發(fā)送緩沖區(qū)): 可以打電話也可以接電話,可以發(fā)短信也可以收短信。
5.大小是否受限:電話打完了說好了我掛了啊,時間不受限,短信的大小有限制比如150個字。
UDP編程
DatagramSocket
DatagramSocket API:DatagramSocket 是UDP Socket,用于發(fā)送和接收UDP數(shù)據(jù)報。
構造方法:
方法 | 說明 |
---|---|
DatagramSocket() | 創(chuàng)建一個UDP數(shù)據(jù)報套接字的Socket,綁定到本機任意一個隨機端口(一般用于客戶端,客戶端的端口一般是系統(tǒng)隨機分配的) |
DatagramSocket(int port) | 創(chuàng)建一個UDP數(shù)據(jù)報套接字的Socket,綁定到本機任意一個隨機端口(一般用于服務端) |
普通方法:
方法 | 說明 |
---|---|
void receive(DatagramPacket p) | 從此套接字接收數(shù)據(jù)報(如果沒有接收到數(shù)據(jù)報,該方法會阻塞待) |
void send(DatagramPacket p) | 從此套接字接收數(shù)據(jù)報(不會阻塞等待,直接發(fā)送) |
void close() | 關閉此數(shù)據(jù)報套接字 |
Socket的本質(zhì)也是文件,之前介紹了狹義上的文件是硬盤上的文件,廣義上的文件是計算機上的各種硬盤設備。
Socket對應到網(wǎng)卡這個硬件設備,操作系統(tǒng)把網(wǎng)卡也當做一個文件來管理。
通過網(wǎng)卡發(fā)數(shù)據(jù)就是寫文件, 通過網(wǎng)卡接收數(shù)據(jù)就是讀文件。
DatagramPacket
DatagramPacket是UDP Socket發(fā)送和接收的數(shù)據(jù)報,存放消息的具體內(nèi)容。
構造方法:
方法 | 說明 |
---|---|
DatagramPacket(bytel] buf, int length) | 構造一個DatagramPacket以用來接收數(shù)據(jù)報,接收的數(shù)據(jù)保存在字節(jié)數(shù)組(第一個參數(shù)buf)中,接收指定長度(第二個參數(shù)length) |
DatagramPacket(bytel] buf,int offset, int length,SocketAddress address) | 構造一個DatagramPacket以用來發(fā)送數(shù)據(jù)報,發(fā)送的數(shù)據(jù)為字節(jié)數(shù)組(第一個參數(shù)buf)中,從0到指定長度(第二個參數(shù)length)。address指定目的主機的IP和端口號。 |
普通方法:
方法 | 說明 |
---|---|
lnetAddress getAddress() | 從接收的數(shù)據(jù)報中,獲取發(fā)送端主機IP地址;或從發(fā)送的數(shù)據(jù)報中,獲取接收端主機IP地址 |
int getPort() | 從接收的數(shù)據(jù)報中,獲取發(fā)送端主機的端口號;或從發(fā)送的數(shù)據(jù)報中,獲取接收端主機端口號 |
bytell getData() | 獲取數(shù)據(jù)報中的數(shù)據(jù) |
構造UDP發(fā)送的數(shù)據(jù)報時,需要傳入 SocketAddress,該對象可以使用InetSocketAddress來創(chuàng)建。
實現(xiàn)
實現(xiàn)一個簡單的UDP回顯服務器與客戶端(發(fā)送什么就接收什么)。
服務端:
public class UDPEchoServer {
// 定義一個用于服務器端的DatagramSocket
private DatagramSocket server;
/**
* 構造方法,完成服務器的初始化
* @param port 端口號
*/
public UDPEchoServer (int port) throws Exception {
if (port > 65535 || port < 1024) {
throw new Exception("端口號必須在1024 ~ 65535之間");
}
// 初始化服務器端的UDP服務
this.server = new DatagramSocket(port);
}
/**
* 對外提供服務
*/
public void start () throws IOException {
System.out.println("服務器已啟動....");
// 循環(huán)接收用戶的請求
while (true) {
// 1. 創(chuàng)建一個用于接收請求數(shù)據(jù)的DatagramPacket
DatagramPacket requestPacket = new DatagramPacket(new byte[1024], 1024);
// 2. 接收請求, 把真實的內(nèi)容填充到requestPacket
server.receive(requestPacket);
// 3. 從requestPacket獲取數(shù)據(jù)
String request = new String(requestPacket.getData(), 0, requestPacket.getLength(), "UTF-8");
// 4. 根據(jù)請求獲取響應
String response = processor (request);
// 5. 把響應封裝到DatagramPacket
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(StandardCharsets.UTF_8),
response.getBytes().length, requestPacket.getSocketAddress());
// 6. 發(fā)送數(shù)據(jù)
server.send(responsePacket);
// 7. 打印日志
System.out.printf("[%s:%d] request: %s, response: %s.\n", requestPacket.getAddress().toString(),
requestPacket.getPort(), request, response);
}
}
public String processor(String request) {
return request;
}
public static void main(String[] args) throws Exception {
// 初始化服務器
UDPEchoServer server = new UDPEchoServer(9999);
// 啟動服務
server.start();
}
}
詞典服務端:
public class UDPDicServer extends UDPEchoServer {
private Map<String, String> map = new HashMap<>();
/**
* 構造方法,完成服務器的初始化
*
* @param port 端口號
*/
public UDPDicServer(int port) throws Exception {
super(port);
map.put("dog", "小狗");
map.put("cat", "小貓");
map.put("pig", "小豬");
map.put("tiger", "大老虎");
map.put("veryGood", "牛P");
}
@Override
public String processor(String request) {
return map.getOrDefault(request, "查無此詞");
}
public static void main(String[] args) throws Exception {
UDPDicServer server = new UDPDicServer(9999);
server.start();
}
}
客戶端:
public class UDPEchoClient {
// 定義一個用于客戶端的DatagramSocket
private DatagramSocket client;
// 定義服務器的IP地址
private String serverIp;
// 定義服務器的端口號
private int port;
private SocketAddress address;
/**
* 構造方法,指定服務器的Ip地址和端口號
*
* @param serverIp 服務器IP
* @param port 端口號
*/
public UDPEchoClient (String serverIp, int port) throws SocketException {
this.client = new DatagramSocket();
this.serverIp = serverIp;
this.port = port;
this.address = new InetSocketAddress(serverIp, port);
}
public void start () throws IOException {
System.out.println("客戶端已啟動.");
// 循環(huán)接收用戶的輸入
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("->");
// 接收用戶輸入
String request = scanner.next();
// 1. 把請求內(nèi)容包裝成DatagramPacket
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(StandardCharsets.UTF_8),
request.getBytes().length, address);
// 2. 發(fā)送數(shù)據(jù)
client.send(requestPacket);
// 3. 接收響應
DatagramPacket responsePacket = new DatagramPacket(new byte[1024], 1024);
// 4. 在receive方法中填充響應數(shù)據(jù)
client.receive(responsePacket);
// 5. 解析響應數(shù)據(jù)
String response = new String(responsePacket.getData(), 0, responsePacket.getLength(), "UTF-8");
// 6. 打印日志
System.out.printf("request: %s, response: %s.\n", request, response);
}
}
public static void main(String[] args) throws IOException {
UDPEchoClient client = new UDPEchoClient("127.0.0.1", 9999);
// 啟動服務
client.start();
}
}
服務端與客戶端的執(zhí)行關系:
服務端 | 服務端 |
---|---|
構造Socket對象,啟動Socket服務 | |
socket.receive()進入接收狀態(tài),阻塞等待請求到來 | 啟動客戶端 |
指定服務器IP和端口,構造packet,socket.sent()發(fā)送請求 | |
receive得到數(shù)據(jù),線程喚醒,開始處理 | 等待服務端響應socket.receive(),進入阻塞,等待服務器響應。 |
響應處理結(jié)果socket.sent() | receive接收響應,線程喚醒 |
進入下一次循環(huán),繼續(xù)在receive阻塞等待請求 | 展示結(jié)果 |
阻塞等待:
TCP編程
ServerSocket
ServerSocket 是創(chuàng)建TCP服務端Socket的API。
構造方法
方法 | 說明 |
---|---|
ServerSocket(int port) | 創(chuàng)建一個服務端流套接字Socket,并綁定到指定端口。操作系統(tǒng)中端口號會和進程綁定。 |
普通方法
方法 | 說明 |
---|---|
Socket accept() | 開始監(jiān)聽指定端口(創(chuàng)建時綁定的端口),有客戶端連接后,返回一個服務端Socket對象,并基于該Socket建立與客戶進行連接,否則阻塞等待。返回的Socket對象包含了封裝好的數(shù)據(jù)。 |
void close() | 關閉此套接字 |
Socket
socket 是客戶端Socket,或服務端中接收到客戶端建立連接 (accept方法)的請求后,返回的服務端Socket。不管是客戶端還是服務端Socket,都是雙方建立連接以后,保存的對端信息,及用來與對方收發(fā)數(shù)據(jù)的。
構造方法
方法 | 方法 |
---|---|
Socket(String host, intport) | 創(chuàng)建一個客戶端流套接字Socket,并與對應IP的主機和對應端口的進程建立連接 |
普通方法
方法 | 說明 |
---|---|
lnetAddress getlnetAddress() | 返回套接字所連接的地址 |
InputStream getlnputStream() | 返回此套接字的輸入流 |
OutputStream getOutputStream() | 返回此套接字的輸出流 |
InputStream輸入流,從網(wǎng)卡緩沖區(qū)讀取數(shù)據(jù);OutputStream輸出流,把數(shù)據(jù)寫到網(wǎng)卡緩沖區(qū),相當于發(fā)數(shù)據(jù)。
實現(xiàn)
實現(xiàn)一個簡單的TCP聊天服務器與客戶端。
服務端:
public class TCPEchoServer {
// 聲明一個用于服務端的Socket對象
private ServerSocket server;
/**
* 通過指定端口號實例化服務
*
* @param port 端口號
* @throws IOException
*/
public TCPEchoServer(int port) throws IOException {
if (port < 1025 || port > 65535) {
throw new RuntimeException("端口號要在 1025 ~ 65535之間.");
}
// 實例化ServerSocket并指定端口號
this.server = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服務器啟動成功...");
// 循環(huán)接收客戶端的連接
while (true) {
Socket clientSocket = server.accept();
// 處理Socket中的數(shù)據(jù)
processConnections(clientSocket);
}
}
// 處理數(shù)據(jù)
private void processConnections(Socket clientSocket) throws IOException {
// 打印日志
String clientInfo = MessageFormat.format("[{0}:{1}] 客戶端已上線", clientSocket.getInetAddress(),
clientSocket.getPort());
System.out.println(clientInfo);
// 處理數(shù)據(jù)之前要獲取一下輸入輸出流
try (InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()) {
// 循環(huán)處理用戶的請求
while (true) {
// 通過Scanner讀取用戶請求中的數(shù)據(jù)
Scanner requestScanner = new Scanner(inputStream);
if (!requestScanner.hasNextLine()) {
// 日志
clientInfo = MessageFormat.format("[{0}:{1}] 客戶端已下線.", clientSocket.getInetAddress(),
clientSocket.getPort());
System.out.println(clientInfo);
break;
}
// 獲取真實的用戶請求數(shù)據(jù)
String request = requestScanner.nextLine();
// 根據(jù)請求計算響應
String response = process(request);
// 把響應寫回客戶端
PrintWriter printWriter = new PrintWriter(outputStream);
// 寫入輸出流
printWriter.println(response);
// 強制刷新緩沖區(qū)
printWriter.flush();
// 打印日志
clientInfo = MessageFormat.format("[{0}:{1}], request: {2}, response: {3}",
clientSocket.getInetAddress(), clientSocket.getPort(), request, response);
System.out.println(clientInfo);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
clientSocket.close();
}
}
private String process(String request) {
System.out.println("收到新消息:" + request);
Scanner scanner = new Scanner(System.in);
String response = scanner.nextLine();
return response;
}
public static void main(String[] args) throws IOException {
TCPEchoServer server = new TCPEchoServer(9999);
server.start();
}
}
客戶端:
public class TCPEchoClient {
// 定義一個用于客戶端的Socket對象
private Socket clientSocket;
/**
* 初始化客戶端的Socket
*
* @param serverIp 服務器IP地址
* @param serverPort 服務器的端口號
* @throws IOException
*/
public TCPEchoClient (String serverIp, int serverPort) throws IOException {
this.clientSocket = new Socket(serverIp, serverPort);
}
public void start () throws IOException {
System.out.println("客戶端已啟動...");
// 獲取Socket中的輸入輸出流
try (InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()) {
// 循環(huán)處理用戶的輸入
while (true) {
System.out.println("->");
// 接收用戶的輸入內(nèi)容
Scanner requestScanner = new Scanner(System.in);
String request = requestScanner.nextLine();
// 發(fā)送用戶的請求
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(request);
// 強制刷新緩沖區(qū)
printWriter.flush();
// 接收服務器的響應
Scanner responseScanner = new Scanner(inputStream);
// 獲取響應數(shù)據(jù)
String response = responseScanner.nextLine();
// 打印響應內(nèi)容
System.out.println("接收到服務器的響應:" + response);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
clientSocket.close();
}
}
public static void main(String[] args) throws IOException {
TCPEchoClient client = new TCPEchoClient("127.0.0.1", 9999);
client.start();
}
}
當打開多個客戶端時,發(fā)現(xiàn)服務器并不能同時處理多個請求。為了解決這個問題,可以先讓客戶端1斷開連接。為了實現(xiàn)服務器可以處理多個客戶端連接,那么可以為每一個客戶端連接創(chuàng)建一個新的線程,讓請求的處理在單獨的子線程中去執(zhí)行。但是如果說每接收到客戶端連接就創(chuàng)建新的線程,當有1萬個連接就要創(chuàng)建1萬個線程,會消耗非常大的系統(tǒng)資源。所以為了優(yōu)化這個問題,可以使用線程池的方式。
客戶端start()方法優(yōu)化:文章來源:http://www.zghlxwxcb.cn/news/detail-584720.html
public void start() throws IOException {
System.out.println("服務器啟動成功...");
// 創(chuàng)建一個線程池
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(3, 10, 1, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10));
// 循環(huán)接收客戶端的連接
while (true) {
Socket clientSocket = server.accept();
// 每接收到一個新連接請求,就創(chuàng)建一個新的子線程
// Thread thread = new Thread(() -> {
// // 處理Socket中的數(shù)據(jù)
// try {
// processConnections(clientSocket);
// } catch (IOException e) {
// e.printStackTrace();
// }
// });
// // 啟動線程
// thread.start();
// 提交任務到線程池中
poolExecutor.submit(() -> {
try {
processConnections(clientSocket);
} catch (IOException e) {
e.printStackTrace();
}
});
}
}
繼續(xù)加油~文章來源地址http://www.zghlxwxcb.cn/news/detail-584720.html
到了這里,關于【Java】網(wǎng)絡編程與Socket套接字、UDP編程和TCP編程實現(xiàn)客戶端和服務端通信的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!