Java網路程式設計之Socket
阿新 • • 發佈:2018-12-22
原文博主禁止轉載,不過我還是希望把一些關鍵的地方筆記下來,閱讀請移步 原文
以下是學習之後的個人筆記
一、Socket通訊基本例項
通過伺服器-客戶端模式引入Socket通訊
伺服器端
package cn.itcast.net; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; public class ChatServer { public static void main(String[] args) throws Exception { // 監聽指定的埠 int port = 55533; ServerSocket server = new ServerSocket(port); // server將一直等待連線的到來 System.out.println("server將一直等待連線的到來"); // while (true) { // 如果伺服器是不斷的等待連線,那麼客戶端就能傳送多次 Socket socket = server.accept(); // 建立好連線後,從socket中獲取輸入流 InputStream inputStream = socket.getInputStream(); byte[] bytes = new byte[1024]; int len; StringBuilder sb = new StringBuilder(); while ((len = inputStream.read(bytes)) != -1) { //注意指定編碼格式,傳送方和接收方一定要統一,建議使用UTF-8 sb.append(new String(bytes, 0, len,"UTF-8")); } System.out.println("get message from client: " + sb); inputStream.close(); socket.close(); // } server.close(); } }
客戶端
127.0.0.1不理解的看我部落格吧~這裡
package cn.itcast.net; import java.io.OutputStream; import java.net.Socket; public class Client { public static void main(String args[]) throws Exception { // 要連線的服務端IP地址和埠 String host = "127.0.1.1"; int port = 55533; // 與服務端建立連線 Socket socket = new Socket(host, port); // 建立連線後獲得輸出流 // OutputStream outputStream = socket.getOutputStream(); //不理解這句存在的必要,希望路過的大神指出 String message="你好 yiwangzhibujian?"; socket.getOutputStream().write(message.getBytes("UTF-8")); //outputStream.close(); socket.close(); } }
輸出
server將一直等待連線的到來
get message from client: 你好 yiwangzhibujian
二、訊息通訊優化
2.1 雙向通訊,傳送訊息並接收訊息
伺服器端
和上一個的區別是,在接收完資料的後,也能傳送資料給客戶端。也就是說可收可發
package cn.itcast.net; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; public class ChatServer { public static void main(String[] args) throws Exception { // 監聽指定的埠 int port = 55533; ServerSocket server = new ServerSocket(port); // server將一直等待連線的到來 System.out.println("server將一直等待連線的到來"); Socket socket = server.accept(); // 建立好連線後,從socket中獲取輸入流,並建立緩衝區進行讀取 InputStream inputStream = socket.getInputStream(); byte[] bytes = new byte[1024]; int len; StringBuilder sb = new StringBuilder(); //只有當客戶端關閉它的輸出流的時候,服務端才能取得結尾的-1 while ((len = inputStream.read(bytes)) != -1) { // 注意指定編碼格式,傳送方和接收方一定要統一,建議使用UTF-8 sb.append(new String(bytes, 0, len, "UTF-8")); } System.out.println("get message from client: " + sb); OutputStream outputStream = socket.getOutputStream(); outputStream.write("Hello Client,I get the message.".getBytes("UTF-8")); inputStream.close(); outputStream.close(); socket.close(); server.close(); } }
客戶端
在客戶端也添加了接收來自伺服器端訊息的功能
package cn.itcast.net;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class Client {
public static void main(String args[]) throws Exception {
// 要連線的服務端IP地址和埠
String host = "127.0.0.1";
int port = 55533;
// 與服務端建立連線
Socket socket = new Socket(host, port);
// 建立連線後獲得輸出流
OutputStream outputStream = socket.getOutputStream();
String message = "你好 yiwangzhibujian";
socket.getOutputStream().write(message.getBytes("UTF-8"));
//通過shutdownOutput高速伺服器已經發送完資料,後續只能接受資料
socket.shutdownOutput();
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len;
StringBuilder sb = new StringBuilder();
while ((len = inputStream.read(bytes)) != -1) {
//注意指定編碼格式,傳送方和接收方一定要統一,建議使用UTF-8
sb.append(new String(bytes, 0, len,"UTF-8"));
}
System.out.println("get message from server: " + sb);
inputStream.close();
outputStream.close();
socket.close();
}
}
2.2 雙向通訊的必要性
客戶端開啟一個輸出流,如果不做約定,也不關閉它,那麼服務端永遠不知道客戶端是否傳送完訊息,那麼服務端會一直等待下去,直到讀取超時。所以怎麼告知服務端已經發送完訊息就顯得特別重要。
2.2.1 指定傳送規則
文章中推薦一種方法規定傳送方式,這種方法是:先指明你接下去要傳送的訊息的長度,然後傳送這麼長的訊息。具體的看原文部落格,現在使用簡易版本。用前兩個位元組表示長度。
伺服器端:
package cn.itcast.net;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class ChatServer {
public static void main(String[] args) throws Exception {
// 監聽指定的埠
int port = 55533;
ServerSocket server = new ServerSocket(port);
// server將一直等待連線的到來
System.out.println("server將一直等待連線的到來");
Socket socket = server.accept();
// 建立好連線後,從socket中獲取輸入流,並建立緩衝區進行讀取
InputStream inputStream = socket.getInputStream();
byte[] bytes;
while (true) {
int first = inputStream.read();
if (first == -1) {
break;
}
int second = inputStream.read();
int len = (first << 8) + second;
bytes = new byte[len];
inputStream.read(bytes);
System.out.println("get message from client: " + new String(bytes, "UTF-8"));
}
//反饋訊息給客戶端,也適用本方法,這裡就先不演示了
OutputStream outputStream = socket.getOutputStream();
outputStream.write("Hello Client,I get the message.".getBytes("UTF-8"));
inputStream.close();
outputStream.close();
socket.close();
server.close();
}
}
客戶端:
package cn.itcast.net;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class Client {
public static void main(String args[]) throws Exception {
// 要連線的服務端IP地址和埠
String host = "127.0.0.1";
int port = 55533;
// 與服務端建立連線
Socket socket = new Socket(host, port);
// 建立連線後獲得輸出流
OutputStream outputStream = socket.getOutputStream();
String message = "你好 yiwangzhibujian";
//計算訊息的長度,轉換成位元組陣列
byte[] sendBytes = message.getBytes("UTF-8");
//實現傳送訊息的長度,兩個位元組
// 1、先發送長度的高8位
outputStream.write(sendBytes.length >> 8);
// 2、傳送長度的低8位
outputStream.write(sendBytes.length);
// 這才傳送訊息
outputStream.write(sendBytes);
outputStream.flush();
//==========此處重複傳送一次,實際專案中為多個命名,此處只為展示用法
message = "第二條訊息";
sendBytes = message.getBytes("UTF-8");
outputStream.write(sendBytes.length >>8);
outputStream.write(sendBytes.length);
outputStream.write(sendBytes);
outputStream.flush();
//==========此處重複傳送一次,實際專案中為多個命名,此處只為展示用法
message = "the third message!";
sendBytes = message.getBytes("UTF-8");
outputStream.write(sendBytes.length >>8);
outputStream.write(sendBytes.length);
outputStream.write(sendBytes);
//通過shutdownOutput高速伺服器已經發送完資料,後續只能接受資料,也可使用這種方法,這裡先不演示了
socket.shutdownOutput();
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len;
StringBuilder sb = new StringBuilder();
while ((len = inputStream.read(bytes)) != -1) {
//注意指定編碼格式,傳送方和接收方一定要統一,建議使用UTF-8
sb.append(new String(bytes, 0, len,"UTF-8"));
}
System.out.println("get message from server: " + sb);
inputStream.close();
outputStream.close();
socket.close();
}
}
三、伺服器併發處理
通常伺服器會處理多個Scoket請求,而不是像上面的接收一個Scoket連線後,就關閉伺服器,因此模板大概是這樣。
package cn.itcast.net;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class ChatServer {
public static void main(String args[]) throws IOException {
// 監聽指定的埠
int port = 55533;
ServerSocket server = new ServerSocket(port);
// server將一直等待連線的到來
System.out.println("server將一直等待連線的到來");
while(true){
Socket socket = server.accept();
// 建立好連線後,從socket中獲取輸入流,並建立緩衝區進行讀取
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len;
StringBuilder sb = new StringBuilder();
while ((len = inputStream.read(bytes)) != -1) {
// 注意指定編碼格式,傳送方和接收方一定要統一,建議使用UTF-8
sb.append(new String(bytes, 0, len, "UTF-8"));
}
System.out.println("get message from client: " + sb);
inputStream.close();
socket.close();
}
}
}
這種一般也是新手寫法,但是能夠迴圈處理多個Socket請求,不過當一個請求的處理比較耗時的時候,後面的請求將被阻塞,所以一般都是用多執行緒的方式來處理Socket,即每有一個Socket請求的時候,就建立一個執行緒來處理它。
不過在實際生產中,建立的執行緒會交給執行緒池來處理,為了:
- 執行緒複用,建立執行緒耗時,回收執行緒慢
- 防止短時間內高併發,指定執行緒池大小,超過數量將等待,方式短時間建立大量執行緒導致資源耗盡,服務掛掉
執行緒池開啟執行緒處理
package cn.itcast.net;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ChatServer {
public static void main(String args[]) throws IOException {
// 監聽指定的埠
int port = 55533;
ServerSocket server = new ServerSocket(port);
// server將一直等待連線的到來
System.out.println("server將一直等待連線的到來");
//如果使用多執行緒,那就需要執行緒池,防止併發過高時建立過多執行緒耗盡資源
ExecutorService threadPool = Executors.newFixedThreadPool(100);
while (true) {
Socket socket = server.accept();
Runnable runnable = () -> {
// 建立好連線後,從socket中獲取輸入流,並建立緩衝區進行讀取
InputStream inputStream = null;
try {
System.out.println("執行緒開啟");
inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len;
StringBuilder sb = new StringBuilder();
while ((len = inputStream.read(bytes)) != -1) {
// 注意指定編碼格式,傳送方和接收方一定要統一,建議使用UTF-8
sb.append(new String(bytes, 0, len, "UTF-8"));
}
System.out.println("get message from client: " + sb);
inputStream.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
};
threadPool.submit(runnable);
}
}
}
lambda被我替換成下面這段,應該也是可以的 lambda學習連結
threadPool.submit(new Runnable() {
@Override
public void run() {
InputStream inputStream = null;
try {
System.out.println("執行緒開啟");
inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len;
StringBuilder sb = new StringBuilder();
while ((len = inputStream.read(bytes)) != -1) {
// 注意指定編碼格式,傳送方和接收方一定要統一,建議使用UTF-8
sb.append(new String(bytes, 0, len, "UTF-8"));
}
System.out.println("get message from client: " + sb);
inputStream.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
});