解釋:BIO → Block input output(阻塞式的輸入和輸出)
使用場景:一般應用于傳統(tǒng)的網(wǎng)絡通信型宝;請求(客戶端)
臭挽、響應(服務端)
兩方捂襟,約定好使用TCP/IP通信;雙方約定好報文格式(報文頭+報文體)
及編碼格式(這里用UTF-8)
欢峰,報文頭內(nèi)容(約定好長度比如8位葬荷,不夠前面補零)
里面內(nèi)容為報文體長度,再根據(jù)報文頭內(nèi)容纽帖,獲取后面的報問體的內(nèi)容宠漩。
例如:報文示例:00000007aaaaaaa
;報文體內(nèi)容為7個a懊直,所以報文頭長度為7不夠八位前面補零扒吁。
一、 作為服務端
SocketServerThread: 用來監(jiān)聽端口的服務類
ThreadOperate:處理每一個請求的新線程
/**
* serverSocket服務
*
* 此線程只負責接收客戶端請求室囊,接收到以后再創(chuàng)造新的線程去處理
* @author zhb
*
*/
public class SocketServerThread extends Thread{
// 此服務端線程只會在啟動時創(chuàng)建一個
private ServerSocket serversocket;
/**
* 創(chuàng)建服務端線程
* @param serversocket 服務端的一個socket
* @throws IOException
*/
public SocketServerThread(ServerSocket serversocket) throws IOException{
if(serversocket==null){
this.serversocket = new ServerSocket(Constant.serverSocketPort);
}
}
// 線程運行體
@Override
public void run(){
// 如果此服務線程沒有被中斷
while(!this.isInterrupted()){
try {
System.out.println("服務線程開啟成功雕崩,正在等待客戶端的請求");
// 服務線程一直處于阻塞狀態(tài),直到有客戶端請求
Socket socket = serversocket.accept();
System.err.println("請求來了");
// 設置接收超時的時間 單位毫秒
socket.setSoTimeout(Constant.reqTimeOut);
if(socket != null && !socket.isClosed()){
// 創(chuàng)建一個新線程去處理請求信息
new Thread(new ThreadOperate(socket)).start();
}
} catch (IOException e) {
System.err.println("服務線程出現(xiàn)異常:" + e.getMessage());
}
}
}
// 開啟線程主函數(shù)
public static void main(String[] args) throws IOException {
new SocketServerThread(null).start();
}
}
/**
* 處理每一個請求開的新的線程
* @author zhb
*/
public class ThreadOperate implements Runnable {
// 處理某個客戶端請求的socket
private Socket socket;
public ThreadOperate(Socket socket){
this.socket = socket;
}
public void run() {
InputStream in = null;
OutputStream out = null;
System.err.println("處理客戶請求的新線程開啟成功");
try {
in = socket.getInputStream();
out = socket.getOutputStream();
// 存放報文頭的字節(jié)數(shù)組
byte[] reqHeadByte = new byte[Constant.reqHeadLength];
//讀取報文頭內(nèi)容
in.read(reqHeadByte);
String reqHeadStr = new String(reqHeadByte, Constant.charset);
//報文頭內(nèi)容是報問體的長度
int reqBodylength = Integer.valueOf(reqHeadStr);
// 創(chuàng)建容納報文體的字節(jié)數(shù)組
byte[] bodyByte = new byte[reqBodylength];
in.read(bodyByte);
// 將報問體數(shù)組轉(zhuǎn)成報文字符串
String reqBodyStr = new String(bodyByte, Constant.charset);
System.err.print("接受報文內(nèi)容為:" + reqBodyStr);
// 這里要處理接收到的請求報文內(nèi)容reqBodyStr融撞,完后返回處理結(jié)果
//返回響應內(nèi)容體
String resBodyStr = "這里是響應內(nèi)容→ "+ reqBodyStr;
//返回響應內(nèi)容的字節(jié)數(shù)組
byte[] respBodyByte = resBodyStr.getBytes(Constant.charset);
//轉(zhuǎn)成約定好的報文頭長度盼铁,不足的前面補零
StringBuilder format = new StringBuilder("%0").append(Constant.respHeadLength).append("d");
byte[] respHeadByte = String.format(format.toString(), respBodyByte.length).getBytes();
// 輸出報文頭信息
out.write(respHeadByte);
// 輸出報文體內(nèi)容
out.write(respBodyByte);
// 發(fā)送響應信息
out.flush();
} catch (IOException e) {
System.err.println("讀取報文內(nèi)容報異常::" + e.getMessage());
}finally{
//線程結(jié)束后的處理
endHandler(in, out);
}
}
/**
* 線程結(jié)束后的處理
* @param in
* @param out
*/
private void endHandler(InputStream in, OutputStream out) {
if(in != null){
try {
in.close();
} catch (IOException e) {
System.err.println("關(guān)閉輸入流出現(xiàn)異常:" + e.getMessage());
}
}
if(out != null){
try {
out.close();
} catch (IOException e) {
System.err.println("關(guān)閉輸出流出現(xiàn)異常:" + e.getMessage());
}
}
if(socket != null){
try {
socket.close();
} catch (IOException e) {
System.err.println("關(guān)閉線程出現(xiàn)異常:" + e.getMessage());
}
}
}
}
一、 作為客戶端
ClientTest : 模擬客戶端請求開啟線程
ClientThread:具體的某個客戶端線程的請求和響應
Constant : 服務端和客戶端都用到的常量參數(shù)
/**
* 測試客戶端發(fā)送請求
* @author zhb
*/
public class ClientTest {
// 用于測試并發(fā)用
private static CountDownLatch cdl = new CountDownLatch(1);
public static void main(String[] args) throws IOException, InterruptedException {
for(int i=0; i < 10 ; i++){
Socket socket = new Socket("localhost", Constant.serverSocketPort);
new Thread(new ClientThread(socket, i, cdl)).start();
}
Thread.sleep(3000);
cdl.countDown();
}
}
/**
* 客戶端發(fā)送請求線程懦铺,并接受服務器響應信息
* @author zhb
*
*/
public class ClientThread implements Runnable {
// 客戶端的socket
private Socket socket;
// 用于標記第幾個請求
private int i;
// 用于模擬 客戶端的并發(fā)
private CountDownLatch cdl;
public ClientThread(Socket socket) {
this.socket = socket;
}
public ClientThread(Socket socket, int i) {
this.socket = socket;
this.i = i;
}
public ClientThread(Socket socket, int i, CountDownLatch cdl) {
this.socket = socket;
this.i = i;
this.cdl = cdl;
}
public void run() {
try {
//并發(fā)測試信息號
cdl.await();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
OutputStream out = null;
InputStream in = null;
try {
// 客戶端請求信息
clientReqSend(out);
// 接收服務端返回的信息
clientRespReceive(in);
} catch (IOException e) {
e.printStackTrace();
}finally{
//線程結(jié)束后的處理
endHandler(in, out);
}
}
/**
* 客戶端發(fā)送請求信息
*
* @param out
* @throws IOException
* @throws UnsupportedEncodingException
*/
private void clientReqSend(OutputStream out) throws IOException, UnsupportedEncodingException {
out = socket.getOutputStream();
String reqBodyStr = "測試請求內(nèi)容"+String.valueOf(i);
System.err.println(reqBodyStr);
// 請求報文體 字節(jié)數(shù)組
byte[] reqBodyByte = reqBodyStr.getBytes(Constant.charset);
// 請求報文頭字節(jié)數(shù)組捉貌,不夠位數(shù)前面補零
StringBuilder format = new StringBuilder("%0").append(Constant.respHeadLength).append("d");
byte[] reqHeadByte = String.format(format.toString(), reqBodyByte.length).getBytes(Constant.charset);
// 輸出報文頭內(nèi)容
out.write(reqHeadByte);
// 輸出報文體內(nèi)容
out.write(reqBodyByte);
// 發(fā)送
out.flush();
}
/**
* 客戶端接收響應信息
*
* @param in
* @throws IOException
* @throws UnsupportedEncodingException
*/
private void clientRespReceive(InputStream in) throws IOException, UnsupportedEncodingException {
in = socket.getInputStream();
// 存放報文頭的字節(jié)數(shù)組
byte[] reqHeadByte = new byte[Constant.reqHeadLength];
//讀取報文頭內(nèi)容
in.read(reqHeadByte);
String reqHeadStr = new String(reqHeadByte, Constant.charset);
//報文頭內(nèi)容是報問體的長度
int reqBodylength = Integer.valueOf(reqHeadStr);
// 創(chuàng)建容納報文體的字節(jié)數(shù)組
byte[] bodyByte = new byte[reqBodylength];
int off = 0;
int nReadLength = 0;
//循環(huán)讀取直到讀取到報文體長度
while(off < reqBodylength){
nReadLength = in.read(bodyByte, off, reqBodylength - off);
if(nReadLength > 0){
off = off + nReadLength;
}else{
break;
}
}
// 將報問體數(shù)組轉(zhuǎn)成報文字符串
String reqBodyStr = new String(bodyByte, Constant.charset);
System.err.println("接受服務端的請求信息內(nèi)容為:" +i+ reqBodyStr);
}
/**
* 線程結(jié)束后的處理
* @param in
* @param out
*/
private void endHandler(InputStream in, OutputStream out) {
if(in != null){
try {
in.close();
} catch (IOException e) {
System.err.println("關(guān)閉輸入流出現(xiàn)異常:" + e.getMessage());
}
}
if(out != null){
try {
out.close();
} catch (IOException e) {
System.err.println("關(guān)閉輸出流出現(xiàn)異常:" + e.getMessage());
}
}
if(socket != null){
try {
socket.close();
} catch (IOException e) {
System.err.println("關(guān)閉線程出現(xiàn)異常:" + e.getMessage());
}
}
}
}
/**
* 系統(tǒng)常量
*
* @author zhb
*
*/
public class Constant {
// 請求報文頭的位數(shù)
public static final int reqHeadLength = 8;
// 返回響應報文頭的長度
public static final int resHeadLength = 8;
// 字節(jié)數(shù)組和字符創(chuàng)之間的轉(zhuǎn)換時的編碼
public static final String charset = "UTF-8";
//接收請求的最長時間 單位毫秒
public static final int reqTimeOut = 5000;
//接收請求的服務端口
public static final int serverSocketPort = 8080;
}