【知了堂学习笔记】——网络编程之聊天室

客户端:

package client;

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;

public class TCPClient {
    private static Socket socket;

    public TCPClient() throws UnknownHostException, IOException {
        System.out.println("设置服务器端的地址....");
        Scanner scanner = new Scanner(System.in);
        String ip = scanner.nextLine();
        socket = 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 executorService = Executors.newCachedThreadPool();
        executorService.execute(new ListServer());

        //当看到群里或者私聊消息,然后发消息给对方
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(),"UTF-8"),true);
        while(true){
            String message= new String(scanner.nextLine().getBytes("gbk"),"utf-8");
            pw.println(message);
        }

    }

    private void setName(Scanner scanner){
        /**
         * 1.输入流:读取服务器返回昵称合法性
         * 2.输出流:告诉服务器设置的昵称
         * 3.重复检验
         */

        String name;
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(),"UTF-8"));
            PrintWriter pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(),"UTF-8"),true);

            while(true) {
                System.out.println("请输入你的昵称: ");

                name= new String(scanner.nextLine().getBytes("gbk"),"utf-8");

                if(name.trim().length() == 0) {
                    System.out.println("昵称不能为空...");
                } else {
                    pw.println(name); // 把昵称发给服务器端, 服务器端getName()来接收你的昵称
                    String pass = br.readLine();   // 读取服务器端返回昵称是否可以使用, 要么OK,要么FAIL
                    if(pass != null && (!pass.equals("OK"))) {
                        System.out.println("昵称已经被占用,请重新输入:");
                    } else {
                        System.out.println("昵称 "+ name +" 已设置成功,可以开始聊天了");
                        break;
                    }
                }
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }


    }

    /**
     * 专门监听服务器端返回的信息
     * 
     */
    class ListServer implements Runnable{

        public void run() {
            try {
                BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
                String message = null;
                while((message = br.readLine()) != null) {
                    System.out.println(message);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }

    }

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

服务器端:

package chat;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
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;

import jdk.internal.util.xml.impl.Input;

/**
 * 网络聊天的服务器套接字
 * 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<String, PrintWriter>();
            executorService = Executors.newCachedThreadPool();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 启动服务器端的套接字
     */
    public void start(){

        try {
            while(true){
                System.out.println("等待客户端的连接....");
                //监听客户端的套接字
                Socket socket = serverSocket.accept();

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

                //线程来代理客户端的套接字的对象
                executorService.execute(new listenerClient(socket));//线程代理后启动线程中run方法
            }



        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 存储客户端的信息
     * @param key
     * @param printWriter
     */
    private synchronized void putIn(String key,PrintWriter value){
        storeInfo.put(key, value);
    }
    /**
     * 群聊:发送消息给所有的客户端
     * 服务器遍历所有客户端的流
     * 然后发送消息
     */
    private synchronized  void sendTOAll(String message){
        for(PrintWriter pw:storeInfo.values()){
            pw.println(message);
        }
    }
    /**
     * 私聊:发送消息给指定的客户端
     * @param key
     * @param message
     */
    private synchronized void sendSomene(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. 客户端关闭, 程序还得把客户端套接字关闭
         */

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

                            // 组合一下消息内容, 让对方看到我的名字和我发送内容,因为我是在私聊
                            info = name + ":" + info;

                            // 调用私聊的方法
                            sendSomene(theName, info);
                            continue;
                        }
                    }
                    // 群聊
                    sendTOAll(name +":"+ message);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                remove(name);
                sendTOAll("[系统通知]" + name + " 已经下线...");
                if(socket != null) {
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

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

            /**
             * 要获取客户端的昵称
             * 1. 我们要准备两个流
             * 2. PrintWriter
             * 3. BufferedReader
             */
            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("FAIL");
                    } else { // 代表昵称可以使用
                        pw.println("OK");
                        return name;
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }

    }


    public static void main(String[] args) {
        TCPServer server = new TCPServer(); 
        server.start();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值