码迷,mamicode.com
首页 > 编程语言 > 详细

JAVA简单聊天室(服务端+客户端)

时间:2015-05-12 15:05:56      阅读:222      评论:0      收藏:0      [点我收藏+]

标签:

服务端的代码如下:

package com;

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.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


@SuppressWarnings("unused")
public class Server {
    
    private ExecutorService threadPool;  //线程池,用来管理与客户端交互的线程(重用线程)
    
    private List<PrintWriter> allOut; //定义一个集合List,存放所有客户端输出流的共享集合
    
    /*
     * java.net.ServerSocket运行在服务端的Socket。主要工作是申请服务端的端口,
     * 并且监听该端口, 等待客户端的连接。一旦一个客户端与服务端建立连接,
     * ServerSocket会返回与该客户端沟通的Socket。
     */
    private ServerSocket server;
    
    public Server()  //服务端构造方法,用来初始化服务端的相关属性
    {
        try {
            
            //初始化
            server = new ServerSocket(8088);
            
            allOut = new ArrayList<PrintWriter>();
            
            threadPool = Executors.newFixedThreadPool(50);
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    private synchronized void addOut(PrintWriter out)  //向共享集合中添加给定的输出流
    {
        allOut.add(out);
    }
    
    private synchronized void removeOut(PrintWriter out)  //删除输出流
    {
        allOut.remove(out);
    }
    
    private synchronized void sendMessageToAllClient(String message)  //给所有的客户端发送消息
    {
        for(PrintWriter out : allOut)
        {
            out.println(message);  //把要发送的消息发送给这个客户端
        }
    }
    
    
    //服务端开始工作的方法
    public void start() {
        try {
            //ServerSocket的accept方法:该方法用来监听服务端打开的8088端口
            //,等待客户端的连接
            //一旦一个客户端连接了就会返回与该客户端进行通信的Socket。
             
            while(true)
            {
                System.out.println("等待客户端连接……");
                Socket socket = server.accept();
                //accept是一个阻塞方法,直到一个客户端连接,否则会一直卡在这里
                System.out.println("一个客户端连接了!");
                
                //启动一个线程,来完成与刚刚连接的客户端的交互工作
                ClientHandler handler = new ClientHandler(socket);
                //Thread t = new Thread(handler);
                //t.start();                
                threadPool.execute(handler); //把上面单独新建的一个线程,改成可以管理多个线程的线程池来使用
            }
        
        } catch (Exception e) {
            e.printStackTrace();
        }
    }    
    
    public static void main(String[] args) {                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        
        
        Server server = new Server();
        server.start();
        
        
    }
    
    //定义一个内部类,新建runnable新的线程
    class ClientHandler implements Runnable
    {
        //当前线程用来交互的客户端的Socket
        
        private Socket socket; //创建线程任务的同时指定要交互的客户端的Socket
        
        private String host; //该客户端地址
        
        public ClientHandler(Socket socket) 
        {
            //通过Socket我们是可以得知远端计算机的信息        
            
            InetAddress address = socket.getInetAddress();  //IP
            host = address.getHostAddress();
            int port = socket.getPort();//端口
            
            System.out.println(host + "上线");
            
            this.socket = socket;
        }
        public void run()
        {
            PrintWriter pw = null;
            
            try {
                OutputStream out = socket.getOutputStream();//通过socket获取输出流,来将消息发送回给客户端。
                OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
                pw = new PrintWriter(osw, true);
                
                //(将该输出流存入共享集合中,以便可以广播消息)
                addOut(pw); //在内部类用外部类的方法
                
        
                //通过Socket获取输入流,用方法:getInputStream()
                //InputStream getInputStream(),该方法用来获取一个输入流,
                //可以读取来自远端计算机发送过来的数据。
            
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in,"UTF-8");
                BufferedReader br = new BufferedReader(isr);
                
                //转换为缓冲字符输入流的目的在于,可以以“行”为单位,读取字符串
                
                String message = null; 
                
                //由于br.readLine()方法在操作系统的不同,导致客户端和幅度按断开连接后,这个方法的效果是不同的。
                //linux:当linux的客户端与服务端断开连接后,br.readLine()方法会返回null
                //windows:当windows的客户端与服务端断开连接后,br.readLine()方法会抛出异常
                while((message = br.readLine()) != null)  //读取客户端发送过来的一行字符串
                {
                    //System.out.println(host + "说:" + message);
                    //pw.println(host + "说:" + message);
                    
                    //将该客户端发送过来的消息,转发给所有客户端,达到广播的目的。
                    sendMessageToAllClient(host+"说"+message);
                }
                    
            } catch (Exception e) {
                e.printStackTrace();
            }finally{
                //无论是linux的客户端还是windows的客户端,当与我们断开连接后,都应当将Socket关闭,来释放资源
                System.out.println(host + "下线了!");
                
                //(将该客户端的输出流从共享集合中删除)
                removeOut(pw);
                
                    try {
                        
                        socket.close();
                        
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
            }
        }
    }

}

客户端代码如下:

package com;

import java.io.BufferedReader;
import java.io.FileInputStream;
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.Properties;
import java.util.Scanner;

public class Client {

    //java.net.Socket封装了TCP协议通讯的Socket
    //使用它我们就可以与服务端的ServerSocket进行连接,
    //然后通过输入流、输出流与服务端进行数据交换 ,达到网络通信的目的
    
    private Socket socket;
    
    public Client()
    {
        try {
            //初始化时通常传入两个参数:
            //1:远程计算机的地址(服务端IP地址)
            //2:服务端打开的端口号
            //端口号0-65535
            //前1000不要用,10000以后也很少用。
            //端口是系统用来将外界数据转发给某个应用程序的,每个程序都如果想访问网络,
            //都需要申请一个对外的端口,这样当外界通过IP地址找到我们的计算机后,
            //通过该端口就可以将数据交给我们的程序来处理。
            
            //读取配置文件,将Socket需要的两个参数读取回来,然后再创建Socket。
            //java.util.Properties借助该类来读取配置文件config.properties
            
            Properties prop = new Properties();
            prop.load(new FileInputStream("config.properties"));
            
            // void load(InputStream in) 通过给定的流来读取配置内容
            //我们的配置内容写在了文件config.properties中,所以我们可以创建一个读取该文件的流。
            //这样,load方法就可以通过这个流读取该文件上的配置内容了。
            //当读取完毕后,Properties对象就可以表示配置文件中所有内容,我们可以类似Map的形式获取对应的配置内容,
            //例如:配置文件中的一行 serverhost=localhost
            //可以根据“=”左边的内容获取右边对应的值
            //需要注意的是:key与value都是字符串
            
            String serverHost = prop.getProperty("serverhost");
            int serverPort = Integer.parseInt(prop.getProperty("serverport"));
            
            socket = new Socket(serverHost, serverPort);
            //创建连接,成功返回Socket实例,否则抛异常
            //8088是服务端那边的程序申请的对外端口,可以通过8088连接服务端的程序
            
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    } 
    
    //客户端开始工作的方法
    public void start()
    {
        //让客户端向服务器做一次会话,输出流
        try {
            
            //启动线程,来接收服务端发送过来的消息
            GetServerMessageHandler handler = new GetServerMessageHandler();
            Thread t = new Thread(handler);
            t.start();
            
            //Socket的getOutputStream方法,可以获取一个直接输出流,
            //通过该流写出去的数据将会发送到远端(发送给服务器)
            
            //可以通过这个流发送文本、图片等,输出流
            OutputStream out = socket.getOutputStream();
            
            //把字节流转成字符流,要考虑字符集的编码问题,要用到转换流
            OutputStreamWriter osw = new OutputStreamWriter(out,"utf-8");
            
            //通过PrintWriter写出字符流
            PrintWriter pw = new PrintWriter(osw, true); //true自动行刷新       

            
            Scanner sc = new Scanner(System.in);
            while(true)
            {
                String message = sc.nextLine();
                pw.println(message);
            }

        } catch (Exception e) {
            
            e.printStackTrace();
            
        } 
    }
    
    public static void main(String[] args)
    {
        Client client = new Client();
        client.start();
    }
    
    //新建一个内部类,作为新的runnable新线程,该线程的作用是接受服务端发送过来的每一行消息,并输出到客户端的控制台
    class GetServerMessageHandler implements Runnable
    {
        public void run()
        {
            try {
                //1.通过Socket获取输入流
                //2.转换为缓冲字符输入流
                //3.循环读取服务端发送的每行消息
                //4.将消息输出到控制台
                
                //1
                InputStream in = socket.getInputStream();
                
                //2
                InputStreamReader isr = new InputStreamReader(in,"UTF-8");
                
                //3
                BufferedReader br = new BufferedReader(isr);
                String message = null;
                while((message = br.readLine()) != null)
                {
                    System.out.println(message);
                }
                
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

}

 

JAVA简单聊天室(服务端+客户端)

标签:

原文地址:http://www.cnblogs.com/cyjs1988/p/4497242.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!