临汾人才招聘网:手动搭建I/O‘网络通’信框架2:BIO编程模子实现群聊,手动搭建I/O‘网络通’信框架1:Socket 和[ServerSocket入门实战,实现单聊

admin 2个月前 (04-12) 科技 5 0

“第一章”:「手动搭建」I/O网络通信框架1:Socket(「和」)ServerSocket入门实战,实现单聊

  在“第一章”中运用Socket(「和」)ServerSocket 简朴的实现了网络通[信。‘这一章’,行使BIO编程模子举行升级革新,实现群聊聊天室。

  

  「如图」:当一个『客户端』请求进来 时[,吸收器会为(这个)『客户端』分配一个事情线程,((这个)事)情线程专职处置『客户端』的操作。《在》上一章中,《服务》「器吸收到『客户端』请求」后就跑去专门《服务》(这个)客<户端了>,以<<是>>当其他请求进来 时[,【<<是>>处置不到的】。

  看到(这个)图,<很容易就会想到线程>池,BIO<<是>>一个相对简朴的模子,实现它的要害之处也在‘于线程池’。

  “在上代”码之「前」,先也许说清楚每个类的『作用』,“以免弄”混淆。「更详细的说明」,都写在注释当中。

  《服务》器端:

  ChatServer:(这个)类的『作用』就像图中的Acceptor。【它有两个】对照要害的全局变量,一个就<<是>>存储在线用户{信息的}Map, 一个[就<<是>>线程池。 (这个)类会监听[端口,吸收『客户端』的请求,「然后为『客户端』分」配事情线程。还会(提供)一些常用的工具方式给每个事情线程挪用,(好比):发送“新{闻}”、添加在线用户等。{我之「前」简朴}用(过)Netty(「和」)WebSocket,(这个)类看上去就已经(「和」)这些框架有点相似了。<学习>IO编程模子也<<是>>为了接下来深入<学习>Netty〖做准备〗。

  ChatHandler:<这>个类就<<是>>事情线程的类。在(这个)项目中,“它的事情很简朴”:把吸收到的“新{闻}”转发给其他『客户端』,固然另有一些小功效,(好比)添加\移除在线用户。

  『客户端』:

  相较于《服务》器,『客户端』的改动较小,「主要」<<是>>把守候用户输入{信息(这个)功效分}到其他线程做,否则(这个)功效会一直壅闭主线程,《导》致无法吸收其他『客户端』的“新{闻}”。

  ChatClient:【『客户端』启动】类,也就<<是>>主线程,会通(过)Socket(「和」)《服务》器毗邻。也(提供)了两个工具方式:发送“新{闻}”(「和」)吸收“新{闻}”。

  UserInputHandler:专门卖力守候用户输入{信息的}线程, 一旦有信息键入[,就马上发送给《服务》器。

  首先建立两个包区分一下『客户端』(「和」)《服务》器,client(「和」)server

  《服务》器端ChatServer:

public class ChatServer {
    private int DEFAULT_PORT = 8888;
    /**
     * 【建】立一个Map【存储在线】用户的信息。(这个)map〖可以统计在线用〗户、针对这些用户可以转发其他用户发送的“新{闻}”
     * 《由于》会有多个线程操作(这个)map,以<<是>>为了平安起见用ConcurrentHashMap
     * 在这里key<就<<是>>『客户端』的>端口号,但在现实中一定不会用【端口号】区分用户,〖若<<是>><<是>>〗web的话一样平常用session。
     * value<<是>>IO的Writer,(用以存储客户)端发送的“新{闻}”
     */
    private Map<Integer, Writer> map=new ConcurrentHashMap<>();
    /**
     * 建立线程池,“线程上”限为10个,若<<是>>第11个『客户端』请求进来,《服务》器会吸收然则不会去分配线程处置它。
     * 「前」10〖个『客户端』〗的聊天纪录,它看不见。当有一个『客户端』下线 时[,这第11个『客户端』就会被分配线程,《服务》器显示在线
     * 『人人』可以把10《再设置小一点》,测试看看
     * */
    private ExecutorService executorService= Executors.newFixedThreadPool(10);
    //『客户端』毗邻 时[往map{添加客}户端
    public void addClient(Socket socket) throws IOException {
        if (socket != null) {
            BufferedWriter writer = new BufferedWriter(
                    new OutputStreamWriter(socket.getOutputStream())
            );
            map.put(socket.getPort(), writer);
            System.out.println("Client["+socket.getPort()+"]:Online");
        }
    }

    //断开毗邻 时[map「里移除『客户端』」
    public void removeClient(Socket socket) throws Exception {
        if (socket != null) {
            if (map.containsKey(socket.getPort())) {
                map.get(socket.getPort()).close();
                map.remove(socket.getPort());
            }
            System.out.println("Client[" + socket.getPort() + "]Offline");
        }
    }

    //转发『客户端』“新{闻}”,(这个)方式就<<是>>把“新{闻}”发送给在线的其他的所有『客户端』
    public void sendMessage(Socket socket, String msg) throws IOException {
        //『遍历在线客户』端
        for (Integer port : map.keySet()) {
            //发送给在线的其他『客户端』
            if (port != socket.getPort()) {
                Writer writer = map.get(port);
                writer.write(msg);
                writer.flush();
            }
        }
    }

    //吸收『客户端』请求,“并分”配Handler去处置请求
    public void start() {
        try (ServerSocket serverSocket = new ServerSocket(DEFAULT_PORT)) {
            System.out.println("Server Start,The Port is:"+DEFAULT_PORT);
            while (true){
                //守候『客户端』毗邻
                Socket socket=serverSocket.accept();
                //“为『客户端』”分配一个ChatHandler线程
                executorService.execute(new ChatHandler(this,socket));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        ChatServer server=new ChatServer();
        server.start();
    }
}

  《服务》器端ChatHandler:

public class ChatHandler implements Runnable {
    private ChatServer server;
    private Socket socket;

    //组织函数,ChatServer通(过)(这个)分配Handler线程
    public ChatHandler(ChatServer server, Socket socket) {
        this.server = server;
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            //往map里添加(这个)『客户端』
            server.addClient(socket);
            //读取(这个)『客户端』发送的“新{闻}”
            BufferedReader reader = new BufferedReader(
                    new InputStreamReader(socket.getInputStream())
            );
            String msg = null;
            while ((msg = reader.readLine()) != null) {
                //这样拼接<<是>>为了让其他『客户端』也能看清<<是>>谁发送的“新{闻}”
                String sendmsg = "Client[" + socket.getPort() + "]:" + msg;
                //《服务》器打印(这个)“新{闻}”
                System.out.println(sendmsg);
                //将收到的“新{闻}”转发给其 他在线『客户端』[
                server.sendMessage(socket, sendmsg + "\n");
                if (msg.equals("quit")) {
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //若<<是>>用户退出或者发生异常,就在map『中移除该『客户端』』
            try {
                server.removeClient(socket);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

  『客户端』ChatClient:

public class ChatClient {
    private BufferedReader reader;
    private BufferedWriter writer;
    private Socket socket;
    //发送“新{闻}”给《服务》器
    public void sendToServer(String msg) throws IOException {
        //发送之「前」,判断socket的输出流<<是>>否关闭
        if (!socket.isOutputShutdown()) {
            //若<<是>>没有关闭就把用户键入的“新{闻}”放到writer「内里」
            writer.write(msg + "\n");
            writer.flush();
        }
    }
    //从《服务》器吸收“新{闻}”
    public String receive() throws IOException {
        String msg = null;
        //判断socket的输入流<<是>>否关闭
        if (!socket.isInputShutdown()) {
            //没有关闭的话就可以通(过)reader读取《服务》器发送来的“新{闻}”。注重:若<<是>>没有读取到“新{闻}”线程会壅闭在这里
            msg = reader.readLine();
        }
        return msg;
    }

    public void start() {
        //(「和」)《服务》建立毗邻
        try {
            socket = new Socket("127.0.0.1", 8888);
            reader=new BufferedReader(
                    new InputStreamReader(socket.getInputStream())
            );
            writer=new BufferedWriter(
                    new OutputStreamWriter(socket.getOutputStream())
            );
            //“新建一个线程去监听”用户输入的“新{闻}”
            new Thread(new UserInputHandler(this)).start();
            /**
             * 一直的读取《服务》器转发的其他『客户端』的信息
             * 纪录一下之「前」踩(过)的小《坑》:
             * 这里一定要【建】立一个msg 吸收信[息,若<<是>>直接用receive()方式判断(「和」)输出receive()的话会造成有的“新{闻}”不会显示
             * 《由于》receive()获取 时[,在返回之「前」<<是>>壅闭的,一旦吸收到“新{闻}”才会返回,也就<<是>>while这里<<是>>壅闭的,一旦有“新{闻}”就会进入到while「内里」
             * 这 时[候若<<是>>输出的<<是>>receive(),那么上次获取的信息就会丢失,然后壅闭在System.out.println
             * */
            String msg=null;
            while ((msg=receive())!=null){
                System.out.println(msg);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
               if(writer!=null){
                   writer.close();
               }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        new ChatClient().start();
    }
}

  『客户端』UserInputHandler:

public class UserInputHandler implements Runnable {
    private ChatClient client;

    public UserInputHandler(ChatClient client) {
        this.client = client;
    }

    @Override
    public void run() {
        try {
            //吸收用户输入的“新{闻}”
            BufferedReader reader = new BufferedReader(
                    new InputStreamReader(System.in)
            );
            //一直的获取reader中的System.in,<实现了守候用户输入的>【效果】
            while (true) {
                String input = reader.readLine();
                //向《服务》器发送“新{闻}”
                client.sendToServer(input);
                if (input.equals("quit"))
                    break;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

  运行测试:

  通(过)打开终端,通(过)javac『编译』。若<<是>>人人<<是>>在IDEA『上编码的话可能会报编』(码错误),在javac后面加上-encoding utf-8再接java文件就好了。

  『编译』后运行,通(过)java运行 时[,又遇到了一个《坑》。『会报找不到主类的』错误,原来<<是>>《由于》加上两个包,〖要在〗class文件名「前」面加上包名。(好比)当「前」在src 目录[,下面有client(「和」)server两个包,要这么运行:java client.XXXX。可我之「前」明显在client文件夹下运行的java,也<<是>>不行,不知道为什么。

  接着测试:

  1.『首先在一』个终端里运行ChatServer,打开《服务》器

  2.在第二个终端里打开ChatClient,暂且叫A,此 时[《服务》器的终端显示:

  3.「类似的」,在第三个终端里打开ChatClient,暂且叫B,此 时[《服务》器显示:

临汾人才招聘网:手动搭建I/O‘网络通’信框架2:BIO编程模子实现群聊,手动搭建I/O‘网络通’信框架1:Socket 和[ServerSocket入门实战,实现单聊 第1张

  4.A中输入hi,除了《服务》器会打印hi‘外’,B(中也会显示),图片中的端口号(「和」)「前」面{的不一}样,<<是>>《由于》中心出了点小“问题”,「前」三张截图(「和」)后面的不<<是>>同 时[运行的。现实中同一个『客户端』会【显示一样的端】口号:

临汾人才招聘网:手动搭建I/O‘网络通’信框架2:BIO编程模子实现群聊,手动搭建I/O‘网络通’信框架1:Socket 和[ServerSocket入门实战,实现单聊 第2张

  5.当『客户端』输入quit 时[就会断开毗邻,最后,《服务》器的显示为:

临汾人才招聘网:手动搭建I/O‘网络通’信框架2:BIO编程模子实现群聊,手动搭建I/O‘网络通’信框架1:Socket 和[ServerSocket入门实战,实现单聊 第3张

,

sunbet 申博

sunbet 申博www.sunbet88.us<<是>>Sunbet(指定的)Sunbet【官网】,Sunbet(提供)Sunbet(Sunbet)、Sunbet、申博代理合作等业务。

网友评论

  • (*)

最新评论

文章归档

站点信息

  • 文章总数:453
  • 页面总数:0
  • 分类总数:8
  • 标签总数:815
  • 评论总数:116
  • 浏览总数:3896