java socket系列文章
java socket 單線程echo服務(wù)器
java socket 多線程echo服務(wù)器
java socket 線程池echo服務(wù)器
在java socket 多線程echo服務(wù)器這篇文章中累榜,改善了單線程echo服務(wù)器中只能處理一個(gè)客戶端請(qǐng)求的弊端,利用一個(gè)客戶端對(duì)應(yīng)一個(gè)線程的方法型酥,達(dá)到處理多個(gè)客戶端的目的秕噪。
但是,每個(gè)新線程都會(huì)消耗系統(tǒng)資源:
- 創(chuàng)建一個(gè)線程會(huì)占用 CPU 周期恋追,而且每個(gè)線程都會(huì)建立自己的數(shù)據(jù)結(jié)構(gòu)(如,棧),也要消耗系統(tǒng)內(nèi)存嘁酿。
- 當(dāng)一個(gè)線程阻塞時(shí),JVM 將保存其狀態(tài)男应,選擇另外一個(gè)線程運(yùn)行闹司,并在上下文轉(zhuǎn)換(context switch)時(shí)恢復(fù)阻塞線程的狀態(tài)。隨著線程數(shù)的增加沐飘,線程將消耗越來(lái)越多的系統(tǒng)資源游桩,這將最終導(dǎo)致系統(tǒng)花費(fèi)更多的時(shí)間來(lái)處理上下文轉(zhuǎn)換盒線程管理,更少的時(shí)間來(lái)對(duì)連接進(jìn)行服務(wù)耐朴。
在這種情況下借卧,加入一個(gè)額外的線程實(shí)際上可能增加客戶端總服務(wù)的時(shí)間。當(dāng)線程數(shù)目過(guò)多的時(shí)候筛峭,必然導(dǎo)致每個(gè)客戶端請(qǐng)求的處理時(shí)間增長(zhǎng)铐刘,使用戶體驗(yàn)明顯變差。
我們可以通過(guò)限制線程總數(shù)并重復(fù)使用線程來(lái)避免這個(gè)問(wèn)題影晓。我們讓服務(wù)器在啟動(dòng)時(shí)創(chuàng)建一個(gè)由固定線程數(shù)量組成的線程池镰吵,線程池的工作原理:
- 當(dāng)一個(gè)新的客戶端連接請(qǐng)求傳入服務(wù)器,它將交給線程池中的一個(gè)線程處理挂签,
- 該線程處理完這個(gè)客戶端之后疤祭,又返回線程池,繼續(xù)等待下一次請(qǐng)求饵婆。
- 如果連接請(qǐng)求到達(dá)服務(wù)器時(shí)画株,線程池中所有的線程都已經(jīng)被占用,它們則在一個(gè)隊(duì)列中等待啦辐,直到有空閑的線程可用谓传。
與一客戶一線程服務(wù)器一樣,線程池服務(wù)器首先創(chuàng)建一個(gè) ServerSocket 實(shí)例芹关。
然后創(chuàng)建 N 個(gè)線程续挟,每個(gè)線程反復(fù)循環(huán),從(共享的)ServerSocket 實(shí)例接收客戶端連接侥衬。當(dāng)多個(gè)線程同時(shí)調(diào)用一個(gè) ServerSocket 實(shí)例的 accept()方法時(shí)诗祸,它們都將阻塞等待跑芳,直到一個(gè)新的連接成功建立,然后系統(tǒng)選擇一個(gè)線程直颅,為建立起的連接提供服務(wù)博个,其他線程則繼續(xù)阻塞等待。
線程在完成對(duì)一個(gè)客戶端的服務(wù)后功偿,繼續(xù)等待其他的連接請(qǐng)求盆佣,而不終止。如果在一個(gè)客戶端連接被創(chuàng)建時(shí)械荷,沒(méi)有線程在 accept()方法上阻塞(即所有的線程都在為其他連接服務(wù))共耍,系統(tǒng)則將新的連接排列在一個(gè)隊(duì)列中,直到下一次調(diào)用 accept()方法吨瞎。
客戶端代碼:
public class Client {
public static void main(String[] args) throws IOException {
Socket client = new Socket("127.0.0.1", 20012);
//客戶端請(qǐng)求與本機(jī)在20011端口建立TCP連接
client.setSoTimeout(10000);
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
//獲取鍵盤輸入
PrintStream out = new PrintStream(client.getOutputStream());
//獲取Socket的輸出流痹兜,用來(lái)發(fā)送數(shù)據(jù)到服務(wù)端
BufferedReader buf = new BufferedReader(new InputStreamReader(client.getInputStream()));
//獲取Socket的輸入流,用來(lái)接收從服務(wù)端發(fā)送過(guò)來(lái)的數(shù)據(jù)
boolean flag = true;
while(flag){
System.out.print("輸入信息:");
String str = input.readLine();
out.println(str);
//發(fā)送數(shù)據(jù)到服務(wù)端
if("bye".equals(str)){
flag = false;
}else{
try{
//從服務(wù)器端接收數(shù)據(jù)有個(gè)時(shí)間限制(系統(tǒng)自設(shè)颤诀,也可以自己設(shè)置)字旭,超過(guò)了這個(gè)時(shí)間,便會(huì)拋出該異常
String echo = buf.readLine();
System.out.println(echo);
}catch(SocketTimeoutException e){
System.out.println("Time out, No response");
}
}
}
input.close();
if(client != null){
//如果構(gòu)造函數(shù)建立起了連接崖叫,則關(guān)閉套接字谐算,如果沒(méi)有建立起連接,自然不用關(guān)閉
client.close(); //只關(guān)閉socket归露,其關(guān)聯(lián)的輸入輸出流也會(huì)被關(guān)閉
}
}
}
服務(wù)器端代碼
public class ThreadPoolServer {
public static void main(String[] args) {
ServerSocket server;
ExecutorService executor=Executors.newFixedThreadPool(2);
try {
server = new ServerSocket(20012);
Socket client = null;
while(true){
System.out.println("服務(wù)器端等待客戶端發(fā)起連接請(qǐng)求");
client = server.accept();
System.out.println("客戶端向服務(wù)器端發(fā)起了連接請(qǐng)求,且連接成功");
executor.execute(new Handler(client));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
線程處理代碼
public class Handler extends Thread{
private Socket client;
PrintStream out;
BufferedReader buf;
public Handler(Socket client){
this.client = client;
try {
out = new PrintStream(client.getOutputStream());
buf = new BufferedReader(new InputStreamReader(client.getInputStream()));
} catch (IOException e) {
e.printStackTrace();
}
}
public void run(){
try{
boolean flag =true;
while(flag){
String str = buf.readLine();
if(str == null || "".equals(str)){
flag = false;
}else{
if("bye".equals(str)){
flag = false;
}else{
System.out.println("服務(wù)器從客戶端接受到的數(shù)據(jù):"+str);
out.println("echo:" + str);
}
}
}
out.close();
client.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
此項(xiàng)目的完整代碼可以到我的github,java-socket進(jìn)行下載斤儿。