socket,執行緒池(TCP通訊)
阿新 • • 發佈:2019-02-13
Server 1
package day20150914socket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 服務端應用程式
* (MINA的本質也是使用server)
*/
public class Server {
//服務端的Socket
private ServerSocket server;
//構造方法,用於初始化服務端
public Server() throws IOException{
try {
System.out.println("初始化服務端");
/*
* 建立ServerSocket時需要指定的服務埠
*/
server = new ServerSocket(8088);
System.out.println("服務端初始化完畢" );
} catch (IOException e) {
throw e;
}
}
public void start(){
try {
System.out.println("等待客戶端連線。。。");
/*
* ServerSocket的accept()方法:
* 用於監聽8088埠,等待客戶連線, 否則該方法阻塞。
* 若一個客戶端連線了,會返回給客戶端的Socket
*/
Socket socket = server.accept();
//獲取遠端(客戶端)地址
InetAddress address = socket.getInetAddress();
//獲取遠端IP地址
String ip = address.getHostAddress();
//獲取遠端埠號
int port = socket.getPort();
System.out.println(ip+":"+port+"客戶端連線上了");
/*
* 通過剛剛連線上來的客戶端的Socket獲取輸入流
* 來讀取客戶端發過來的資訊
*/
InputStream in = socket.getInputStream();
//將位元組輸入流包裝為字元輸入流,這樣就可指定編碼集
InputStreamReader isr = new InputStreamReader(in,"utf-8");
//將字元流轉為緩衝字元輸入流,這樣就可以以行為單位來讀取字串了
BufferedReader br = new BufferedReader(isr);
String message = null;
while((message=br.readLine())!=null){
System.out.println("客戶端說:"+message);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
Server server = new Server();
server.start();
} catch (IOException e) {
e.printStackTrace();
System.out.println("伺服器初始化失敗");
}
}
}
Client 1
package day20150914socket;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class Client {
//用於連線伺服器端的Socket
private Socket socket;
public Client() throws Exception{
try {
System.out.println("正在連線服務端。。。");
/*
* 建立Socket物件,
* 就會嘗試根據給定的地址與埠連線伺服器
* 所以,若該物件建立成功,說明與伺服器端連線正常
*/
//localhost:本機。連線其他計算機可寫IP
socket = new Socket("localhost",8088);
System.out.println("成功連線服務端。");
} catch (Exception e) {
throw e;
}
}
public void start(){
try{
/*
* 可以通過Socket的getOutputStream()方法獲取一條輸出流
* 用於將資訊傳送至伺服器
*/
OutputStream out = socket.getOutputStream();
/*
*使用字元流指定編碼集將字串轉為位元組後,
*再通過out傳送給伺服器
*/
OutputStreamWriter osr = new OutputStreamWriter(out,"utf-8");
/*
* 將字元流包裝為緩衝字元流
* 就可以以行為單位寫出字串了。
*/
PrintWriter pw = new PrintWriter(osr);
Scanner sc = new Scanner(System.in);
while(true){
String str = sc.nextLine();
pw.println(str);
pw.flush();
}
}catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
Client client = new Client();
client.start();
} catch (Exception e) {
e.printStackTrace();
System.out.println("客戶端初始化失敗");
}
}
}
Server(2)
package day20150914socket2;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 服務端應用程式
* (MINA的本質也是使用server)
*/
public class Server2 {
//服務端的Socket
private ServerSocket server;
//執行緒池,用於管理客戶端連線的互動執行緒
private ExecutorService threadPool;
//儲存所有客戶端輸出流的集合
private List<PrintWriter> allOut;
//構造方法,用於初始化服務端
public Server2() throws IOException{
try {
System.out.println("初始化服務端");
/*
* 建立ServerSocket時需要指定的服務埠
*/
server = new ServerSocket(8088);
//初始化執行緒池
threadPool = Executors.newFixedThreadPool(50);
/*
* 初始化存放所有客戶端輸出流的集合
* 使用ArrayList而不是linkedList的原因:
* 增刪元素不頻繁,而是使用遍歷頻繁
*/
allOut = new ArrayList<PrintWriter>();
System.out.println("服務端初始化完畢");
} catch (IOException e) {
throw e;
}
}
public void start(){
try {
/*
* ServerSocket的accept()方法:
* 用於監聽8088埠,等待客戶連線, 否則該方法阻塞。
* 若一個客戶端連線了,會返回給客戶端的Socket
*/
while(true){
System.out.println("等待客戶端連線。。。");
Socket socket = server.accept();
/*
* 當一個客戶端連線後,啟動一個執行緒ClientHandler
* 將客戶端的socket傳入,使得該執行緒處理與該客戶端的互動
* 這樣,可再次進入迴圈,接收下一個客戶端的連線
*/
Runnable handler = new ClientHandler(socket);
//Thread t = new Thread(handler);
//t.start();
/*
* 使用執行緒池分配空閒執行緒來處理當前連線的客戶端
*/
threadPool.execute(handler);
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 將給定的輸出流存入共享集合
*
* 加synchronized關鍵字,鎖定Server物件,
* 下面3個方法鎖定後,互斥(3個方法鎖定同一個物件)
* 即一個執行緒訪問其中一個方法後,
* 其他執行緒不可訪問這3個方法中的任何一個
*/
public synchronized void addOut(PrintWriter pw){
allOut.add(pw);
}
/**
* 將給定的輸出流從共享集合中刪除
*/
public synchronized void removeOut(PrintWriter pw){
allOut.remove(pw);
}
/**
* 將給定的訊息轉發給所有客戶端
*/
public synchronized void sendMessage(String message){
for(PrintWriter pw : allOut){
pw.println(message);
}
}
public static void main(String[] args) {
try {
Server2 server = new Server2();
server.start();
} catch (IOException e) {
e.printStackTrace();
System.out.println("伺服器初始化失敗");
}
}
/**
* 伺服器端的一個執行緒,用於與某個客戶端互動,
* 使用執行緒的目的是使得伺服器可以處理多個客戶端了
*/
class ClientHandler implements Runnable{
//當前執行緒處理的客戶端的socket
private Socket socket;
//當前客戶端的IP
private String ip;
//當前客戶端的暱稱
private String nickname;
/**
* 根據給定的客戶端的Socket,建立執行緒體
*/
public ClientHandler(Socket socket){
this.socket = socket;
//獲取遠端(客戶端)地址
InetAddress address = socket.getInetAddress();
//獲取遠端IP地址
ip = address.getHostAddress();
//獲取遠端埠號
int port = socket.getPort();
//改為使用暱稱,所以不在這裡通知了
//System.out.println(ip+":"+port+"客戶端連線上了");
}
/**
* 該執行緒會將當前socket中的輸入流獲取
* 用來迴圈讀取客戶端傳送過來的訊息
*/
@Override
public void run() {
PrintWriter pw = null;
try{
/*
* 為了讓服務端向客戶端傳送資訊
* 通過socket獲取輸出流
*/
OutputStream out = socket.getOutputStream();
//轉為字元流,用於指定編碼集
OutputStreamWriter osr = new OutputStreamWriter(out,"utf-8");
//建立緩衝字元輸出流,true自動行重新整理
pw = new PrintWriter(osr,true);
/*
* 將該客戶端的輸出流存入共享集合
* 以便使得該客戶端也能接收伺服器轉發的訊息
*/
//allOut.add(pw);
addOut(pw);
System.out.println("當前線上人數:"+allOut.size());
/*
* 通過剛剛連線上來的客戶端的Socket獲取輸入流
* 來讀取客戶端發過來的資訊
*/
InputStream in = socket.getInputStream();
//將位元組輸入流包裝為字元輸入流,這樣就可指定編碼集
InputStreamReader isr = new InputStreamReader(in,"utf-8");
//將字元流轉為緩衝字元輸入流,這樣就可以以行為單位來讀取字串了
BufferedReader br = new BufferedReader(isr);
//當建立好當前客戶端的輸入流後,讀取的第一個字串應當是暱稱
nickname = br.readLine();
//通知所有客戶端,當前使用者上線了
sendMessage("["+nickname+"]上線了");
sendMessage("當前線上人數為:"+allOut.size());//告知所有客戶端線上人數
String message = null;
/*
* 讀取客戶端發來的一行字串
* windows與linux的差異:
* linux:當客戶端斷開連線後,通過輸入流會讀取到null,
* 這是合乎邏輯的,
* 因為緩衝流的readLine方法若返回null就無法通過該流讀取到資訊
*
* windows:當客戶端與伺服器端斷開接連後
* readLine()方法會丟擲異常
*/
while((message=br.readLine())!=null){
//System.out.println("客戶端說:"+message);
//pw.println(message);//把客戶端發來的訊息回給客戶端
//當前客戶端說話內容告訴給所有客戶端
sendMessage(nickname+"說:"+message);
}
}catch(Exception e){
/*
* 在windows中的客戶端
* 報錯通常是因為客戶端斷開了連線
*
* 不用關流,可直接關Socket
*/
}finally{
/*
* 首先將該客戶端的輸出流從共享集合中刪除
*/
//allOut.remove(pw);
removeOut(pw);
//控制檯顯示該使用者下線了
System.out.println("["+nickname+"]下線了");
//通知其他使用者該使用者下線了
sendMessage("["+nickname+"]下線了");
//輸出當前線上人數(輸出流的個數)
System.out.println("當前線上人數為:"+allOut.size());
sendMessage("當前線上人數為:"+allOut.size());//告知所有客戶端線上人數
/*
* 無論是linux使用者還是windows使用者,
* 當客戶與服務端斷開連線後,
* 我們都應當在伺服器端與客戶端斷開連線
*/
try {
socket.close();
//關閉之後,catch處理意義也不大,故catch塊裡的內容可為空
} catch (IOException e) {
}
//System.out.println("一個客戶端下線了");
}
}
}
}
Client(2)
package day20150914socket2;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class Client2 {
//用於連線伺服器端的Socket
private Socket socket;
public Client2() throws Exception{
try {
System.out.println("正在連線服務端。。。");
/*
* 建立Socket物件,
* 就會嘗試根據給定的地址與埠連線伺服器
* 所以,若該物件建立成功,說明與伺服器端連線正常
*/
//localhost:本機。連線其他計算機可寫IP
socket = new Socket("localhost",8088);
System.out.println("成功連線服務端。");
} catch (Exception e) {
throw e;
}
}
/**
* 客戶端啟動方法
*/
public void start(){
try{
//建立並啟動執行緒,來接收伺服器端傳送過來的訊息
Runnable runn = new GetServerInfoHandler();
Thread t = new Thread(runn);
t.start();
/*
* 可以通過Socket的getOutputStream()方法獲取一條輸出流
* 用於將資訊傳送至伺服器
*/
OutputStream out = socket.getOutputStream();
/*
*使用字元流指定編碼集將字串轉為位元組後,
*再通過out傳送給伺服器
*/
OutputStreamWriter osr = new OutputStreamWriter(out,"utf-8");
/*
* 將字元流包裝為緩衝字元流
* 就可以以行為單位寫出字串了。
*/
PrintWriter pw = new PrintWriter(osr,true);//true,自動行重新整理
//建立一個Scanner,用於接收使用者輸入的字串
Scanner sc = new Scanner(System.in);
//輸出歡迎語
System.out.println("歡迎來到傳奇的聊天室");
while(true){
System.out.println("請輸入暱稱");
String nickname = sc.nextLine();
if(nickname.trim().length()>0){
pw.println(nickname);
break;
}
System.out.println("暱稱不能為空");
}
while(true){
String str = sc.nextLine();
pw.println(str);
//pw.flush();//PrintWriter自動行重新整理就不要此句了
}
}catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
Client2 client = new Client2();
client.start();
} catch (Exception e) {
e.printStackTrace();
System.out.println("客戶端初始化失敗");
}
}
/**
* 該執行緒的作用是迴圈接收伺服器端傳送過來的資訊,
* 並輸出到控制檯
*/
class GetServerInfoHandler implements Runnable{
@Override
public void run() {
try{
//通過socket獲取輸入流
InputStream in = socket.getInputStream();
//將位元組輸入流轉為字元輸入流,指定編碼集
InputStreamReader isr = new InputStreamReader(in,"utf-8");
//將字元流轉為緩衝字元輸入流,這樣就可以以行為單位來讀取字串了
BufferedReader br = new BufferedReader(isr);
String message = null;
//迴圈讀取服務端傳送過來的每個字串
while((message=br.readLine())!=null){
//將服務端傳送的字串輸出到控制檯
System.out.println(message);
}
}catch(Exception e){
}
}
}
}