1. 程式人生 > >socket,執行緒池(TCP通訊)

socket,執行緒池(TCP通訊)

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){

            }
        }
    }
}