多人聊天室(Java)

第1部分 TCP和UDP

TCP:是一种可靠地传输协议,是把消息按一个个小包传递并确认消息接收成功和正确才发送下一个包,速度相对于UDP慢,但是信息准确安全;常用于一般不要求速度和需要准确发送消息的场合。

UDP:是一种不可靠的传输协议,常用于视频直播、游戏等及时性比较强的场景。

进行相关编程可以查询API手册。

第2部分 多人聊天室

下面是利用Java的TCP协议的API实现多人聊天室的案例代码:

服务端:

运行在服务端的SeverSocket主要负责:

 1 向系统申请服务端口号,客户端是通过这个端口号和IP地址与之连接的;端口号一般取四千之一万之间,因为其余有的已经绑定系统系统应用和主流应用程序,最常用的便是8088。

 2 监听申请的服务端口号,当一个客户端通过该端口号尝试建立联系连接时,SeverSocket会在服务端创建一个Socket与客户端建立连接,服务端正对不同客户端建立多个Socket,利用多线程实现。

工作模块及过程:

服务器先初始化,包括申请端口号,和创建共享消息集合(可以用map,key是用户名,value是消息,私聊返回value值,广播则遍历value),另外创建增删和发送消息的方法;

创建处理客户端交互的类,继承runnable,通过socket的getInetAdress和getHostAdress方法获取客户端计算机的信息,其start方法用来完成从一个客户端接收消息,并将其他客户端的消息返回给该客户端;

package chat;

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;


/**
 * 
 * 运行在服务端的SeverSocket主要负责;
 * 1 向系统申请服务端口号客户端是通过这个端口号与之连接的
 * 2 监听申请的服务端口号,当一个客户端通过该端口号尝试建立联系连接时,
 *      SeverSocket会在服务端创建一个Socket与客户端建立连接
 *       服务端正对不同客户端建立多个Socket
 * 
 * @author KwinWei QQ:885251358
 *
 */
public class Server {
    
    public ServerSocket server;
    /*
     * 用来保存客户端输出流的集合,
     *因为线程安全的也不和遍历互斥,要自己维护也可以保证安全
     */
    private List<PrintWriter> allOut;     //私聊可以用map,key为昵称,value是对应消息,广播则遍历value
    
    /*
     *初始化 服务端
     */
    public Server()throws Exception  {
        /*
         * 初始化的同时申请端口号
         */
        server = new ServerSocket(8788);
        allOut = new ArrayList<PrintWriter>();
    }
    
    /**
     * 将给定的输出流存入共享集合
     * @param out
     */
    private synchronized void addOut(PrintWriter out) {
        allOut.add(out);
    }
    /**
     * 将给定的输出流从共享集合中删除
     * @param out
     */
    private synchronized void removeOut(PrintWriter out) {
        allOut.remove(out);
    }
    /**
     * 将给定的消息发送给所有客户端
     * @param out
     */
    private synchronized void  sendMessage(String message) {
        for(PrintWriter out : allOut) {
            out.println(message);
        }
    }
    
    /*服务端开始工作的方法
     * 
     */
    public void start() {
        try {
            /*
             * ServerSocket的accept的方法
             * 是一个阻塞的方法,作用是监听服务端口号,知道一个客户端;连接并创建一个Socket,使用该Socket
             *     即可与刚才链接的客户端进行交互
             */
            
            while(true) {
                System.out.println("等待客户端连接...");
                Socket socket = server.accept();
                System.out.println("一个客户端连接了!");
                
                /*
                 * 启动一个线程,来完成与该客户端的交互
                 */
                ClientHandler handler= new ClientHandler(socket);
                Thread t = new Thread(handler);
                t.start();    
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        
    }
    public static void main(String[] args) {
        try {
            Server server = new Server();
            server.start();
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("服务端建立联系失败!");
        }
    }
    
    /**
     *该线程负责处理一个客户端的交互
     * 
     */
    class ClientHandler implements Runnable{
        /*
         * 该线程处理的客户端的Socket
         */
        private Socket socket ;
        
        
         // 客户端的地址信息,区分不同客户端
        private String host;
        
        //用户的昵称
        private String nickName;
        
    
        public ClientHandler(Socket socket) {
            this.socket = socket;
            /*
             * 通过Socket可以获取远端计算机的地址信息
             */
            InetAddress address = socket.getInetAddress();
            //获取IP地址
            host = address.getHostAddress();
        }
        public void run() {
            PrintWriter pw = null;
            try {
                
                /*
                 * Socket 提供的方法
                 * InputStream getInputStream()
                 * 该方法可以获取一个输入流,从该方法读取的数据就是从远端计算机发送来的 
                 */
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in,"UTF-8");
                BufferedReader br = new BufferedReader(isr);
                
                //首先读取一行字符串为昵称
                nickName =  br.readLine();
                //System.out.println(host+"上线了");
                sendMessage(nickName+"上线了");
                
                /*
                 * 通过Socket创建输出流用于将消息发送给客户端
                 */
                OutputStream out = socket.getOutputStream();
                OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
                 pw = new PrintWriter(osw,true);
                
                /*
                 * 将该客户端对的输出流存入到共享集合中
                 */
                addOut(pw);
                
                
                
                String message = null;
                /*
                 * br.readLine()在读取客户户端发送过来的消息时,由于客户端断线,
                 * 而操作系统的不同,这里读取后的结果不同:
                 * 当windows的客户端断开时:br.readLine会抛出异常
                 * 当linux的客户端断开时:br.readLine 会返回null
                 */
                
                while((message = br.readLine())!=null) {
                    //System.out.println(host+"说:"+ message);
                    //pw.println(host+"说:"+message);
                    //广播消息
                    sendMessage(nickName+"说:"+message);
                } 
                
            } catch (Exception e) {
                
                
            }finally {
                /*
                 * 处理当前客户端断开后的逻辑
                 */
                //将该客户端的输出流从共享集合中删除
                removeOut(pw);
                //System.out.println(host+"下线了");
                sendMessage(nickName+"下线了");
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }    
        }
        
    }
    
}
View Code

客户端:

package chat;

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;

/**
 * 
 * @author KwinWei QQ:885251358
 *
 */
public class Client {
    /*
     * 套接字
     * 
     * 理解为电话,拨号,建立联系,麦克风说话,
     * socket输入和输出流,
     * 你输入流对应他输出流,你说话他听
     * 你输出流对应他输入流
     * 
     * java,net.Socket;
     * 封装了TCP协议,使用它就可以基于某种TCP协议进行网络通信
     * Socket试运行在客户端的
     */
    private Socket socket;
    
    public Client() throws Exception {
        /*
         *实例化Socket的时候需要传入两个参数
         * 1 IP:127.0.0.1或localhost本机;
         * 2 端口:0-65535,听服务端的,区用网程序,
         *    一般使用四千以上一万以下,前四千被占用绑定主流应用程序等    
         * 通过Ip可以找到服务的那台计算机,通过端口号可以找到服务端计算机上服务端用程序
         * 
         * 实例化Socket的过程就是连接的过程,若远端计算机没有响应会抛出异常
         *  
         */
        System.out.println("正在连接服务端...");
        socket = new Socket("localhost",8788);
        System.out.println("已经和服务端建立联系");
    }
    
    /*
     * 启动客户端的方法
     */
    public void start() {
        try {
            Scanner scanner = new Scanner(System.in);
            /*
             * 先要求用户创建一个昵称
             */
            //System.out.println("");
            String nickName = null;
            while(true) {
                System.out.println("请用户输入用户名");
                nickName = scanner.nextLine();
                if(nickName.length()>0) {
                    break;
                }
                System.out.println("输入有误");
            }
            System.out.println("欢迎你"+nickName+"!开始聊天吧!");
            /*socket 提供的方法:
             * OutputStream getOutStream
             * 获取一个字节输出流,通过该流写出的数据会被发送至远端计算机。
             */
            OutputStream out = socket.getOutputStream();
            OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
            PrintWriter pw =  new  PrintWriter(osw,true);  //不能指明字节流,所以要加一个转换流,第二参数为true会行刷新
            
            //先将昵称发给服务器
            
            pw.println(nickName);
            
            /*
             * 启动读取服务端发送过来消息的线程
             */
            ServerHandler  handler = new ServerHandler();
            Thread t = new Thread(handler);
            t.start();
            
            /*
             * 将字符串发送至服务端,加上行刷新,说一句发一句
             * 否则需要用  flush()强制写出
             */
            while(true) {
                pw.println(scanner.nextLine());
                //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("客户端启动失败!");
        }
    }

    /*
     * 该线程用来读取服务器端发送来的消息,并输出到客户端控制台显示
     */
    class ServerHandler implements Runnable {
        public void run() {
            try {
                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) {
                
                
            }
        }
    }
    
    
}
View Code
原文地址:https://www.cnblogs.com/kwinwei/p/10575144.html