TCP通讯小案例聊天室(Server和Client)

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;

/**
 * TCP通讯聊天室服务端
 * @author  Administrator
 *
 */
public class Server {
    public static void main(String[] args){
        try{
            Server server = new Server();
            server.start();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
    /*
     * java.net.ServerSocket
     * 运行在服务端的ServerSocket有两个作用
     * 1:申请服务端(客户端通过该端口与服务端建立连接)
     * 2:监听服务端口,等待客户端连接,一旦客户端连接则创建一个Socket实例用于与客户端交互。
     */
    private ServerSocket server;
    /*
     * 该集合用于保存所有客户端的Socket
     */
    private List<PrintWriter> allOut;
    
    public Server()throws Exception{
        try{
            allOut = new ArrayList<PrintWriter>();
            /*
             * 实例化ServerSocket需要指定服务端口,该端口不能与当前操作系统其他程序申请的
             * 端口冲突,否则会抛出端口被占用异常。
             */
            server = new ServerSocket(8088);
        }catch(Exception e){
            throw e;
        }
    }
    /**
     * 将给定的输出流存入共享集合
     * @param  out
     */
    private synchronized void addOut(PrintWriter out){
        allOut.add(out);
    }
    /**
     * 将给定的输出流从共享集合中删除
     * @param  out
     */
    private synchronized void removeOut(PrintWriter out){
        allOut.remove(out);
    }
    /**
     * 将给定的消息发给所有客户
     * @param  message
     */
    private synchronized void sendMessage(String message){
        for(PrintWriter out:allOut){
            out.println(message);
        }
    }
    public void start(){
        try{
            /*
             * ServerSocket提供了方法:
             * Socket accept()
             * 该方法是一个阻塞方法,作用是监听ServerSocket开启的服务端口,
             * 直到一个客户端通过该端口连接,该方法才会解除阻塞,并返回一个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();
        }
    }
    /**
     * 该线程负责与指定的客户端进行交互。
     * @author  Administrator
     *
     */
    class ClientHandler implements Runnable{
        /*
         * 负责当前线程与指定客户端交互的socket
         */
        private Socket socket;
        //客户端地址信息
        private String host;
        public ClientHandler(Socket socket){
            this.socket = socket;
            /*
             * 通过socket获取远程计算机地址信息
             * 对于服务端而言,远程计算机就是客户端。
             */
            InetAddress address = socket.getInetAddress();
            //获取远程计算机IP
            host = address.getHostAddress();
        }
        public void run(){
            PrintWriter pw = null;
            try{
                System.out.println(host+"上线了!");
                /*
                 * 通过Socket创建输出流,用于将消息发给客户端。
                 */
                OutputStream out = socket.getOutputStream();
                OutputStreamWriter osw = new OutputStreamWriter(out,"utf-8");
                pw = new PrintWriter(osw,true);
                
                //将该客户端输出流存入共享集合
                addOut(pw);
                
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in,"utf-8");
                BufferedReader br = new BufferedReader(isr);
                String message=null;
                /*
                 * br.readLine读取客户端发送过来的一行字符串时,客户端断开连接时,
                 * 由于客户端所在系统不同,这里readLine方法的执行结果也不同:
                 * 当windons客户端断开连接时,readLine方法会直接抛出异常。
                 * 当linux的客户端断开连接时,readLine方法会返回null。
                 */
                while((message = br.readLine())!=null){           
                    //转发给所有客户端
                    sendMessage(host+"说:"+message);
                }
            }catch(Exception e){
                
            }finally{
                //客户端与服务端断开连接
                //客户端下线后从共享集合删除输出流。
                removeOut(pw);
                sendMessage(host+"客户端下线了!");                
                try {                    
                    socket.close();
                } catch (IOException e){
                    e.printStackTrace();
                }
            }
        }        
    }    
}

package chat;

import java.io.BufferedReader;
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 Administrator
 *
 */
public class Client {
    public static void main(String[]args){
        try{
            Client client = new Client();
            client.start();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
    /*
     *  /sbin/ifconfig
     * java.net.Socket 套接字
     * 封装了TCP通讯,使用该类完成与服务端的链接,并进行相应的通讯。
     */
    private Socket socket;
    /**
     * 构造方法用来初始化客户端
     */
    public Client() throws Exception{
        try{
            /*
             * 实例化Socket时,需要传入两个参数
             * 1:服务端的地址
             * 2:服务端的端口
             *
             * 获取本地ip  localhost
             * 通过地址找到服务端的计算机,端口则找到该计算机上的服务端应用程序。
             * 实例化Socket的过程就是连接服务端的过程,连接不成功该构造方法会抛出异常。
             */
            System.out.println("正在连接服务端。。。。");
            socket = new Socket("10.0.2.68",8088);
            System.out.println("连接服务端成功!");
        }catch(Exception e){
            throw e;
        }
    }
    /*
     * 客户端的启动方法
     */
    public void start(){
        try{
            /*
             * 先启动用于接收服务端发送过来的消息的线程
             */
            ServerHandler handler = new ServerHandler();
            Thread t = new Thread(handler);
            //可以将此线程设为守护线程
            t.setDaemon(true);
            t.start();
            
            /*
             * Socket提供了方法:
             * OutputStream getOutputStream();
             * 该方法可以捕获一个字节输出流,通过该字节输出流写出的数据会发送至远端计算机,
             * 对于客户端这边而言,远端是服务端。
             */
            OutputStream out = socket.getOutputStream();
            OutputStreamWriter osw = new OutputStreamWriter(out,"utf-8");
            PrintWriter pw = new PrintWriter(osw,true);
            Scanner scan = new Scanner(System.in);
            String message = null;
            while(true){
                message = scan.next();
                pw.println(message);
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }
    /**
     * 该线程负责循环接收服务端发送过来的消息,并输出到本控制台。
     * @author Administrator
     *
     */
    class ServerHandler 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){
                
            }
        }
    }
}

转载于:https://my.oschina.net/langgege/blog/657046

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值