[知了堂学习笔记]网络编程扩展_实现简单的聊天室功能

实现客户端监控:
目的:在服务器端写线程,让服务器端线程代理客户端的Socket
技术:利用线程池来管理所有的客户端套接字,ExecutorService

package com.zhiliaotang.chat;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 网络聊天的服务器端套接字
 * 1.对客户端监控
 * 2.同时监控到客户端需要用线程代理
 * 3.信息转发
 * @author Administrator
 *
 */
public class TCPServer {

    private ServerSocket serverSocket;//创建服务器端的套接字对象
    private ExecutorService executorService;//线程池,代理监控到的客户端套接字对象
    private  Map<String, PrintWriter> storeInfo;//存储客户端的信息,存储客户端的名称+IO流

    public TCPServer() {
        try {
            serverSocket = new ServerSocket(8989);
            storeInfo = new HashMap<>();
            executorService = Executors.newCachedThreadPool();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * 启动服务器端的套接字
     */
    public void start() {
        try {
            while(true) {
                System.out.println("等待客户端连接。。。");
                Socket socket = serverSocket.accept();//监听客户端的套接字

                //获取客户端的ip地址
                InetAddress inetAddress = socket.getInetAddress();
                System.out.println("客户端:"+inetAddress.getHostAddress()+" 连接成功。。。");


                //启动一个线程来代理客户端的套接字对象
                executorService.execute(new ListenerClient(socket));//线程代理后,启动线程中run方法
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 存储客户端的聊天信息
     * @param key
     * @param value
     */
    private synchronized void putIn(String key, PrintWriter value) {
        storeInfo.put(key, value);
    }
    /**
     * 群聊,发送消息给所有客户端
     * @param message
     */
    private synchronized void sendToAll(String message) {
        /**
         * 原来Map接口还可以这样做
         */
        for(PrintWriter pw : storeInfo.values()) {
            pw.println(message);
        }
    }

    /**
     * 私聊,发送消息给指定的客户端
     * @param name
     * @param message
     */
    private synchronized void sendToSomeOne(String name, String message) {
        PrintWriter pw = storeInfo.get(name);
        if(pw != null) {
            pw.println(message);
        }
    }

    /**
     * 移出客户端的信息
     * @param name
     */
    private synchronized void remove(String name) {
        storeInfo.remove(name);
        System.out.println("当前在线人数:"+storeInfo.size());

    }


    /**
     * 用线程代理客户端的套接字对象
     * @author Administrator
     *
     */
    class ListenerClient implements Runnable{
        private Socket socket;//客户端套接字的对象
        private String name;//客户端的昵称

        public ListenerClient(Socket socket) {
            this.socket = socket;
        }
        /**
         * 1.获取输出流,发送消息
         * 2.读取客户端的信息,读取客户端的昵称
         * 3.检验是群聊还是私聊(客户端如果发送@符号,代表私聊)
         * 4.客户端关闭,程序还得把客户端的套接字关闭
         */
        @Override
        public void run() {
            try {
                //一定要注意把客户端套接字的流保存storeInfo里面
                //必须收集(存储)连接上服务器端的所有套接字对象
                //PrintWriter是一个字符流,而我们套接字只能给出字节流
                //由getName()方法来获取客户端传递过来的昵称,因为服务器端没有能力给客户端取昵称
                PrintWriter pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"),true);
                name = getName();
                putIn(name, pw);
                Thread.sleep(100);

                sendToAll("[系统通知]"+name+" 已上线。。。");

                //不管是群聊还是私聊都得获取输入流
                BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));

                //现在才去读取消息,判断是群聊还是私聊
                String message = null;

                while((message = br.readLine())!= null) {
                    //检验是否为私聊(格式、@昵称、内容等),截取字符串
                    if(message.startsWith("@")) {
                        int index = message.indexOf(":");
                        if(index > 0) {
                            //获取昵称
                            String theName = message.substring(1, index);
                            //获取说话内容
                            String info = message.substring(index+1, message.length());
                            //组合一下消息内容,让对方看到我的名字和我发送的内容
                            info = name + ":"+info;
                            //发送私聊
                            sendToSomeOne(theName, info);
                            continue;
                        }
                    }
                    //群聊
                    //遍历所有输出流,将该客户端发送的信息转发给所有客户端
                    System.out.println(name+" : "+message);
                    sendToAll(name+" : "+message);
                }



            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                remove(name);
                System.out.println("[系统消息]"+name+" 已经下线。。。");
                if(socket != null) {
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }

        }

        /**
         * 获取客户端昵称的方法
         * 1.客户端连接上服务器
         * 2.提示客户端输入一个昵称
         * 3.昵称一旦输完,则马上把昵称发送给服务器端
         * @return
         */
        private String getName() {
            /**
             * 要获取客户端的昵称
             * 1.我们要准备两个流
             * 2.PrintWriter
             * 3.BufferedReader
             * 4.服务器拿到昵称,判断是否有重复昵称,并告知客户端
             */

            try {
                //服务器发送消息告诉客户端是否你输入的昵称有重复
                PrintWriter pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(),"UTF-8"),true);
                //读取客户端输入的昵称
                BufferedReader br =new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
                //这是一个连续你来我往的通信
                //昵称有问题,请客户端重新写一个
                //然后服务器端又重新读取
                //又判断是否重复
                while(true) {
                    String name = br.readLine();
                    if(name.trim().length() == 0 || storeInfo.containsKey(name)) {//昵称不可使用或者重复或者你客户端根本没有输入
                        pw.println("FALL");
                    }else {//代表昵称可以使用
                        pw.println("OK");
                        return name;
                    }
                }

            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;

        }

    }


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


}
package com.zhiliaotang.chat;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class TCPClient {

    private static Socket clientSocket;

    public TCPClient() throws UnknownHostException, IOException {
        System.out.println("设置服务器端的地址:");
        Scanner scanner = new Scanner(System.in);
        String ip = scanner.nextLine();//输入服务器端的ip地址
        clientSocket = new Socket(ip, 8989);
        start();
    }
    /**
     * 启动客户端的套接字
     * 同时还需要有线程来代理客户端的套接字
     * @throws IOException 
     * @throws UnsupportedEncodingException 
     */
    private void start() throws UnsupportedEncodingException, IOException {
        /**
         * 1.连上服务器后需要输入昵称
         * 2.客户端设置昵称,必须根据服务器端返回昵称可不可以使用的情况来设置昵称
         * 3.发送聊天内容,聊天内容需要线程
         */

        Scanner scanner = new Scanner(System.in);
        setName(scanner);

        //接收服务器端发送过来的信息的线程启动
        ExecutorService exec = Executors.newCachedThreadPool();
        exec.execute(new ListenerServer());
        //建立输出流,给服务端发信息
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(clientSocket.getOutputStream(), "UTF-8"), true);
        while(true) {
            pw.println(scanner.nextLine());
        }
    }

    /**
     * 客户端设置昵称,必须根据服务器端返回昵称可不可以使用的情况来设置昵称
     * @param scanner
     * @throws IOException 
     * @throws UnsupportedEncodingException 
     */
    private void setName(Scanner scanner) throws UnsupportedEncodingException, IOException {
        /**
         * 1.输入流:读取服务器返回昵称的合法性
         * 2.输出流:告诉服务器我输入的昵称
         * 3.你来我往的昵称检验
         */
        String name;
        //创建输出流
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(clientSocket.getOutputStream(), "UTF-8"), true);

        //创建输入流
        BufferedReader br = new BufferedReader(new InputStreamReader(clientSocket.getInputStream(), "UTF-8"));

        while(true) {
            System.out.println("请输入你的昵称:");
            name = scanner.nextLine();
            if(name.trim().length() == 0) {
                System.out.println("昵称不能为空。。。");
            }else {
                pw.println(name);//把昵称发送给服务器端,服务器端getName()来接收你的昵称
                String pass = br.readLine();//读取服务器端的昵称是否可以使用,要么OK,要么FALL
                if(pass != null && (!pass.equals("OK"))) {
                    System.out.println("昵称已经被占用,请重新输入:");

                }else {
                    System.out.println("昵称"+name+" 已设置成功,可以开始聊天了");
                    break;
                }
            }
        }
    }
    /**
     * 专门监听服务器端返回的消息
     * 这个消息针对群聊或私聊信息
     * 线程里面只提供输入流来读取信息
     * @author Administrator
     *
     */
    class ListenerServer implements Runnable{

        @Override
        public void run() {
            BufferedReader br;
            try {
                br = new BufferedReader(new InputStreamReader(clientSocket.getInputStream(), "UTF-8"));
                String msgString;
                while((msgString = br.readLine())!=null) {
                    System.out.println(msgString);
                }

            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }

    }

    public static void main(String[] args) throws UnknownHostException, IOException {
        new TCPClient();
    }

}

请关注“知了堂学习社区”,地址:http://www.zhiliaotang.com/portal.php

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值