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 allOut;
    //构造方法,用于初始化服务端
    public Server2() throws IOException{
        try {
            System.out.println("初始化服务端");
            /*
             * 创建ServerSocket时需要指定的服务端口
             */
            server = new ServerSocket(8088);
            //初始化线程池
            threadPool = Executors.newFixedThreadPool(50);
            /*
             * 初始化存放所有客户端输出流的集合
             * 使用ArrayList而不是linkedList的原因:
             * 增删元素不频繁,而是使用遍历频繁
             */
            allOut = new ArrayList();
            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){

            }
        }
    }
}

你可能感兴趣的:(java)