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

            }
        }
    }
}
發佈了0 篇原創文章 · 獲贊 7 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章