java实现hello/hi聊天程序

1.使用java实现服务器与客户端之间的对话,客户端与服务器

服务器端代码

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.HashMap;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 

public class Chat {
    // 运行在服务端的Socket
    private ServerSocket server;
    // 线程池,用于管理客户端连接的交互线程
    private ExecutorService threadPool;
    // 保存所有客户端输出流的集合
    private HashMap<String, PrintWriter> allout;
 
    /**
     * 构造方法,用于初始化服务端
     * 
     * @throws IOException
     */
    public Chat() throws IOException {
        try {
            /*
             * 创建ServerSocket时需要指定服务端口
             */
            System.out.println("初始化服务端");
            server = new ServerSocket(8088);
 
            // 初始化线程池
            threadPool = Executors.newFixedThreadPool(50);
 
            // 初始化存放所有客户端输出流的集合
            allout = new HashMap<String, PrintWriter>();
 
            System.out.println("服务端初始化完毕");
        } catch (IOException e) {
            e.printStackTrace();
            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 (Exception e) {
            e.printStackTrace();
        }
    }
 
    /**
     * 将给定的输出流共享集合
     * 
     * @param p
     */
    public synchronized void addout(String nickName, PrintWriter p) {
        allout.put(nickName, p);
    }
 
    /**
     * 将给定的输出流从共享集合中删除
     * 
     * @param p
     */
    public synchronized void removeOut(String nickName) {
        allout.remove(nickName);
    }
 
    /**
     * 将给定的消息转发给客户端
     * 
     * @param message
     */
    public synchronized void sendMessage(String message) {
        Set<Entry<String, PrintWriter>> set = allout.entrySet();
        for (Entry<String, PrintWriter> e : set) {
            e.getValue().println(message);
        }
 
    }
 
    public static void main(String[] args) {
        Chat server;
        try {
            server = new Chat();
            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;
 
        public ClientHandler(Socket socket) {
            this.socket = socket;
            InetAddress address = socket.getInetAddress();
            // 获取远端计算机的IP地址
            ip = address.getHostAddress();
            // address.getCanonicalHostName()
            // 获取客户端的端口号
            int port = socket.getPort();
            System.out.println(ip + ":" + port + " 客户端连接了");
            // 改为了使用昵称了,所以不在这里通知了
            // //通知其他用户,该用户上线了
            // sendMessage("["+ip+"]上线了");
        }
 
        /*
         * 该线程将当前Socket中的输入流获取 用来循环读取客户端发送过来的消息 (non-Javadoc)
         * 
         * @see java.lang.Runnable#run()
         */
        public void run() {
            /*
             * 定义在try语句外的目的是,为了在finally中也可以引用到
             */
            PrintWriter pw = null;
            try {
                /*
                 * 为了让服务端与客户端发送信息 我们需要通过socket 获取输出流。
                 */
                OutputStream out = socket.getOutputStream();
                OutputStreamWriter osw = new OutputStreamWriter(out, "UTF-8");
                pw = new PrintWriter(osw, true);
 
                /*
                 * 将客户端的输出流存入共享集合 以便是的客户端也能接收服务端转发的消息
                 */
                // addout(pw);
                // Scanner sc = new Scanner(System.in) ;
                //
                // System.out.println(sc.nextLine());
                /*
                 * 通过socket获取远端的地址信息 对于服务端而言,远端就是客户端了
                 */
 
                /*
                 * 通过刚刚连上的客户端的Socket获取 输入流,来读取客户端发送过来的信息
                 */
                InputStream in = socket.getInputStream();
                /*
                 * 将字节输入流包装为字符输出流,这样 可以指定编码集来读取每一个字符
                 */
                InputStreamReader isr = new InputStreamReader(in, "UTF-8");
                /*
                 * 将字符流转换为缓冲字符输入流 这样就可以以行为单位读取字符串了
                 */
                BufferedReader br = new BufferedReader(isr);
 
                /*
                 * 当创建好当前客户端的输入流后 读取的第一个字符串,应当是昵称
                 */
                nickname = br.readLine();
                addout(nickname, pw);
                // 通知所有客户端,当前用户上线了
                sendMessage("[" + nickname + "]上线了");
 
                String message = null;
                // 读取客户端发送过来的一行字符串
                /*
                 * 读取客户端发送过来的信息这里 windows与linux存在一定的差异: linux:当客户端与服务端断开连接后
                 * 我们通过输入流会读取到null 但这是合乎逻辑的,因为缓冲流的 readLine()方法若返回null就
                 * 表示无法通过该留再读取到信息。 参考之前服务文本文件的判断。
                 * 
                 * windows:当客户端与服务端断开连接后 readLine()方法会抛出异常。
                 */
                while ((message = br.readLine()) != null) {
                    message = hexiestr(message);
                    // System.out.println(
                    // "客户端说:" + message);
                    // pw.println("服务端说:"+message) ;
                    /*
                     * 当读取客户端发送过来的一条消息后,将该消息转发给所有客户端
                     */
                    // for (PrintWriter o : allout) {
                    // o.println(message);
                    // }
                    String siliao = siliaostr(message);
                    if (allout.containsKey(siliao)) {
                        message = message.substring(message.indexOf(":") + 1);
                        allout.get(siliao).println(nickname + "对你说:" + message);
                    } else {
                        sendMessage(nickname + "说:" + message);
                    }
                }
            } catch (Exception e) {
                // 在windows中的客户顿,
                // 报错通常是因为客户端断开了连接
            } finally {
                /*
                 * 首先将该客户端的输出流从共享集合中删除
                 */
                removeOut(nickname);
                // 输出当前在线人数
                System.out.println("当前在线人数为:" + allout.size());
                // 通知其他用户,该用户下线了
                sendMessage("[" + nickname + "]下线了");
                /*
                 * 无论是Linux 用户还是windows用户,当予服务端断开连接后、、、 我们都应该在服务端也与客户端断开连接
                 */
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    System.out.println("一个客户端下线了..");
                }
            }
        }
    }
    public String siliaostr(String str) {
        int fir = str.indexOf("\\") + 1;
        int las = str.indexOf(":");
        if (fir <= 0 || las <= 0) {
            return str;
        } else {
            String sub = str.substring(fir, las);
            return sub;
        }
    }
    public String hexiestr(String str) {
        String s = str.replaceAll("(sb|cao|ca)", "**");
        return s;
    }
}

客户端代码

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.Socket;
import java.util.Scanner;
 

public class Client {
    //Socket,用于连接服务端的ServerSocket
    private Socket socket;
    private Scanner scanner;
    /*
     * 客户端构造方法,用于初始化客户端
     */
    public Client() throws IOException{
        try {
            /*
             * 创建Socket对象时,
             * 就会尝试根据给定的地址与端口连接服务端。
             * 所以,若该对象创建成功,说明与服务端连接正常。
             */
            System.out.println("正在连接服务端。。");
            socket=new Socket("127.0.0.1",8088);
            System.out.println("成功连接服务端");
        }catch (IOException 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 osw=new OutputStreamWriter(out,"UTF-8");
            /*
             * 将字符流包装为缓冲字符流,就可以按行为单位写出字符串
             */
            PrintWriter pw=new PrintWriter(osw,true);
            /*
             * 创建一个Scanner,用于接收用户输入的字符串
             */
            scanner = new Scanner(System.in);
            //输出欢迎用语
            System.out.println("欢迎来到Linus聊天室");
            while(true){
            //首先输入昵称
            System.out.println("请输入昵称");
            String nickname=scanner.nextLine();
            if(nickname.trim().length()>0){
                pw.println(nickname);
                break;
                }
                System.out.println("昵称不能为空");
            }
            while(true){
                String str=scanner.nextLine();
                pw.println(str);
            }
        } 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("客户端初始化失败");        
        }
    }
    /*
     * 该线程的作用是循环接受服务端发送过来的信息,并输出待控制台
     */
    class GetServerInfoHandler implements Runnable{
        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) {
                
            }
        }
    }
    
}

程序执行结果如下:

2.分析socket大致系统调用流程

  (1)建立一个服务器ServerSocket,并同时定义好ServerSocket的监听端口;
  (2)ServerSocket 调用accept()方法,使之处于阻塞。
  (3)创建一个客户机Socket,并设置好服务器的IP和端口。
  (4)客户机发出连接请求,建立连接。
  (5)分别取得服务器和客户端ServerSocket 和Socket的InputStream和OutputStream.
  (6)  利用Socket和ServerSocket进行数据通信。

其运行抽象图和运行流程图如下:

3.java中socket中api与linux中socket的api的对比

new SeverSocket()调用 Linux中socket()bind()listen()来实现

SeverSocket.accept()调用 Linux中accept()来实现

in.readLine()调用 Linux中recv()来实现

猜你喜欢

转载自www.cnblogs.com/sll825123774/p/12011466.html
今日推荐