• 完成聊天室的私聊功能


    1 完成聊天室的私聊功能

    完成聊天室私聊功能。私聊功能是指,客户端之间可以实现一对一的聊天。

    服务器端程序启动后,将等待客户端连接,界面效果如图-1所示:

    图-1

    客户端程序运行时,需要用户先输入昵称。用户输入昵称之后,提示用户可以开始聊天。界面效果如图-2所示:

    图-2

    另一个客户端运行起来后,也需要输入昵称,界面效果如图-3所示:

    图-3

    此时,其他运行中的客户端会收到昵称为“jerry”的客户端上线的消息。比如,之前运行起来的客户端“mary”的界面效果如图-4所示:

    图-4

    其他客户端可以通过输入类似“jerry:你好”这样的字样和昵称为“jerry”的客户端私聊。比如,昵称为“mary”的客户端可以输入如图-5所示的信息:

    图-5

    注意:如果需要进行私聊,必需使用“昵称:信息”的格式发送消息。其中,“昵称:”为固定格式,“昵称”表示要私聊的客户端的昵称;“信息”表示需要发送的消息。例如:"jerry:你好",表示发送消息“你好”给昵称为“jerry”的客户端。

    昵称为“jerry”的客户端将接收到客户端“mary”发来的信息,界面效果如图-6所示:

    图-6

    如果某客户端程序停止运行,其他客户端程序可以接收到消息并显示。例如,昵称为“jerry”的客户端停止运行,昵称为“mary”的客户端的界面效果如图-7所示:

    图-7

    对于服务器端而言,只要有客户端连接,就会在界面输出提示信息。界面效果如图-8所示:

    图-8

    参考答案

    实现此案例需要按照如下步骤进行。

    步骤一:创建客户端类

    新建名为com.tarena.homework的包,并在包下新建名为Client的类,用于表示客户端。

    在Client 类中声明全局变量 socket 表示一个客户端Socket对象,并在实例化 Client 类时使用构造方法“Socket(String ip,int port)”来创建Socket类的对象。此时,需要进行异常处理。代码如下所示:

     
    1. package com.tarena.homework;
    2. import java.io.BufferedReader;
    3. import java.io.IOException;
    4. import java.io.InputStream;
    5. import java.io.InputStreamReader;
    6. import java.io.OutputStream;
    7. import java.io.OutputStreamWriter;
    8. import java.io.PrintWriter;
    9. import java.net.Socket;
    10. import java.util.Scanner;
    11. /**
    12. * 客户端应用程序
    13. */
    14. public class Client {
    15.     //客户端Socket
    16.     private Socket socket;
    17.     /**
    18.      * 构造方法,用于初始化
    19.      */
    20.     public Client(){
    21.         try {
    22.             socket = new Socket("localhost",8088);
    23.         } catch (Exception e) {
    24.             e.printStackTrace();
    25.         }
    26.     }
    27. }

    步骤二:定义客户端线程要执行的任务

    在Client类中定义成员内部类ServerHander。该内部类需要实现Runnable接口并实现该接口的run() 方法。在该方法中实现线程要执行的任务,在此,线程要执行的任务为循环接收服务端的消息并打印到控制台。代码如下所示:

     
    1. public class Client {
    2.     //其他代码,略
    3.     
    4.     /**
    5.      * 该线程用于接收服务端发送过来的信息
    6.      */
    7.     private class ServerHander implements Runnable{
    8.         @Override
    9.         public void run() {
    10.             try {
    11.                 InputStream in = socket.getInputStream();
    12.                 InputStreamReader isr = new InputStreamReader(in, "UTF-8");
    13.                 BufferedReader br = new BufferedReader(isr);
    14.                 while(true){
    15.                     System.out.println(br.readLine());
    16.                 }
    17.             } catch (Exception e) {
    18.                 e.printStackTrace();
    19.             }
    20.         }
    21.     }
    22. }

    步骤三:定义方法inputNickName(),用于输入昵称

    为Client类定义方法inputNickName(),用于输入昵称。代码如下所示:

     
    1. public class Client {
    2.     //其他代码,略
    3.     
    4.     /**
    5.      * 输入昵称
    6.      */
    7.     private void inputNickName(Scanner scanner)throws Exception{
    8.         //定义昵称
    9.         String nickName = null;
    10.         //创建输出流
    11.         PrintWriter pw = new PrintWriter(
    12.                             new OutputStreamWriter(
    13.                                 socket.getOutputStream(),"UTF-8")
    14.                          ,true);
    15.         //创建输入流
    16.         BufferedReader br = new BufferedReader(
    17.                                 new InputStreamReader(
    18.                                         socket.getInputStream(),"UTF-8")
    19.                          );
    20.         /*
    21.          * 循环以下操作
    22.          * 输入用户名,并上传至服务器,等待服务器回应,若昵称可用就结束循环,否则通知用户后
    23.          * 重新输入昵称
    24.          */
    25.         while(true){
    26.             System.out.println("请输入昵称:");
    27.             nickName = scanner.nextLine();
    28.             if(nickName.trim().equals("")){
    29.                 System.out.println("昵称不能为空");
    30.             }else{
    31.                 pw.println(nickName);
    32.                 String pass = br.readLine();
    33.                 if(pass!=null&&!pass.equals("OK")){
    34.                     System.out.println("昵称已被占用,请更换。");
    35.                 }else{
    36.                     System.out.println("你好!"+nickName+",开始聊天吧!");
    37.                     break;
    38.                 }
    39.             }
    40.         }
    41.     }
    42. }

    步骤四:创建客户端工作方法 start()

    为 Client 类创建客户端工作方法 start()。在该方法中,首先调用方法inputNickName()得到用户昵称,然后启动接收服务端信息的线程,接收数据后打印显示。

    代码如下所示:

     
    1. public class Client {
    2.     //其他代码,略
    3.     /**
    4.      * 客户端工作方法
    5.      */
    6.     public void start(){
    7.         try {
    8.             //创建Scanner读取用户输入内容
    9.             Scanner scanner = new Scanner(System.in);
    10.             //首先输入昵称
    11.             inputNickName(scanner);
    12.             
    13.             //将接收服务端信息的线程启动
    14.             ServerHander handler = new ServerHander();
    15.             Thread t = new Thread(handler);
    16.             t.setDaemon(true);
    17.             t.start();
    18.             
    19.             OutputStream out = socket.getOutputStream();            
    20.             OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");            
    21.             PrintWriter pw = new PrintWriter(osw,true);
    22.             while(true){
    23.                 pw.println(scanner.nextLine());
    24.             }
    25.         } catch (Exception e) {
    26.             e.printStackTrace();
    27.         } finally{
    28.             if(socket != null){
    29.                 try {
    30.                     socket.close();
    31.                 } catch (IOException e) {
    32.                     e.printStackTrace();
    33.                 }
    34.             }
    35.         }
    36.     }
    37. }

    步骤五:为客户端类定义 main() 方法

    为类 Client 定义 main() 方法,并在该方法中,创建 Client 对象,调用上一步中所创建的 start() 方法。代码如下所示:

     
    1. /**
    2. * 客户端应用程序
    3. */
    4. public class Client {
    5.     //其他代码,略
    6.     
    7.     public static void main(String[] args) {
    8.         Client client = new Client();
    9.         client.start();
    10.     }
    11. }

    步骤六:定义 Server类

    定义Server类,并在Server类中添加ExecutorService类型的属性threadPool,并在构造方法中将其初始化。初始化时,使用固定大小的线程池,线程数量为40。这里使用Executors类的newFixedThreadPool(int threads)方法来创建固定大小的线程池。定义属性serverSocket,其类型为ServerSocket,并在构造方法中将其初始化,申请的服务端口为8088。再定义属性allOut,其类型为HashMap,其中key用于保存用户昵称,value用于保存该客户端的输出流,并在构造方法中初始化以便服务端可以转发信息。

    代码如下所示:

     
    1. package com.tarena.homework;
    2. import java.io.BufferedReader;
    3. import java.io.IOException;
    4. import java.io.InputStream;
    5. import java.io.InputStreamReader;
    6. import java.io.OutputStream;
    7. import java.io.OutputStreamWriter;
    8. import java.io.PrintWriter;
    9. import java.net.ServerSocket;
    10. import java.net.Socket;
    11. import java.util.HashMap;
    12. import java.util.Map;
    13. import java.util.concurrent.ExecutorService;
    14. import java.util.concurrent.Executors;
    15. /**
    16. * 服务端应用程序
    17. */
    18. public class Server {
    19.     // 服务端Socket
    20.     private ServerSocket serverSocket;
    21.     // 所有客户端输出流,key为用户的昵称,value为该用户的输出流
    22.     private Map<String,PrintWriter> allOut;
    23.     // 线程池
    24.     private ExecutorService threadPool;
    25.     /**
    26.      * 构造方法,用于初始化
    27.      */
    28.     public Server() {
    29.         try {
    30.             serverSocket = new ServerSocket(8088);            
    31.             allOut = new HashMap<String,PrintWriter>();            
    32.             threadPool = Executors.newFixedThreadPool(40);
    33.         } catch (Exception e) {
    34.             e.printStackTrace();
    35.         }
    36.     }
    37. }

    步骤七:为 Server 类定义 addOut()和removeOut()方法

    定义 addOut()方法,该方法向Server的属性allOut集合中添加输出流,并使用synchronized关键字修饰,使该方法变为同步方法。

    再定义removeOut()方法,该方法从Server的属性allOut集合中删除输出流,并使用synchronized关键字修饰,使该方法变为同步方法。

    代码如下所示:

    1. public class Server {
    2.     //其他代码,略
    3.     /**
    4.      * 将输出流存入共享集合,与下面两个方法互斥,保证同步安全
    5.      * @param out
    6.      */
    7.     private synchronized void addOut(String nickName,PrintWriter out){
    8.         allOut.put(nickName,out);
    9.     }
    10.     /**
    11.      * 将给定输出流从共享集合删除
    12.      * @param out
    13.      */
    14.     private synchronized void removeOut(String nickName){
    15.         allOut.remove(nickName);
    16.     }
    17. }

    步骤八:为 Server 类定义sendMessage()方法

    定义sendMessage()方法,该方法用于遍历Server的属性allOut集合元素,将信息写入每一个输出流来完成广播消息的功能,并使用synchronized关键字修饰,使该方法变为同步方法。代码如下所示:

     
    1. public class Server {
    2.     //其他代码,略
    3.     /**
    4.      * 将消息转发给所有客户端
    5.      * @param message
    6.      */
    7.     private synchronized void sendMessage(String message){
    8.         for(PrintWriter o : allOut.values()){
    9.             o.println(message);
    10.         }
    11.     }
    12. }

    步骤九:为 Server 类定义sendMessageToOne() 方法

    定义sendMessageToOne()方法,该方法用于将消息发送给指定昵称的客户端来实现私聊功能。代码如下所示:

     
    1. public class Server {
    2.     //其他代码,略
    3.     /**
    4.      * 将消息发送给指定昵称的客户端
    5.      * @param nickName
    6.      * @param message
    7.      */
    8.     private synchronized void sendMessageToOne(String nickName,String message){
    9.         PrintWriter out = allOut.get(nickName);
    10.         if(out!=null){
    11.             out.println(message);
    12.         }
    13.     }
    14. }

    步骤十:创建内部类

    创建 Server的内部类ClientHandler,在内部类中定义run()方法。在run()方法中,读取用户昵称以发送用户上线信息,并进行消息转发,其中先判断是否为私聊信息,若是则调用发送私聊信息的方法,否则向所有客户端广播消息 。代码如下所示:

     
    1.     /**
    2.      * 线程体,用于并发处理不同客户端的交互
    3.      */
    4.     private class ClientHandler implements Runnable {
    5.         // 该线程用于处理的客户端
    6.         private Socket socket;
    7.         // 开客户端的昵称
    8.         private String nickName;
    9.         public ClientHandler(Socket socket) {
    10.             this.socket = socket;
    11.         }
    12.         @Override
    13.         public void run() {
    14.             PrintWriter pw = null;
    15.             try {
    16.                 //将客户端的输出流存入共享集合,以便广播消息
    17.                 OutputStream out = socket.getOutputStream();
    18.                 OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
    19.                 pw = new PrintWriter(osw,true);
    20.                 /*
    21.                  * 将用户信息存入共享集合
    22.                  * 需要同步
    23.                  */
    24.                 //先获取该用户昵称
    25.                 nickName = getNickName();
    26.                 addOut(nickName,pw);
    27.                 Thread.sleep(100);
    28.                 /*
    29.                  * 通知所有用户该用户已上线
    30.                  */
    31.                 sendMessage(nickName+"上线了");
    32.                 
    33.                 InputStream in = socket.getInputStream();
    34.                 InputStreamReader isr = new InputStreamReader(in, "UTF-8");
    35.                 BufferedReader br = new BufferedReader(isr);
    36.                 
    37.                 String message = null;
    38.                 // 循环读取客户端发送的信息
    39.                 while ((message = br.readLine())!=null) {
    40.                     //首先查看是不是私聊
    41.                     if(message.startsWith("\")){
    42.                         /*
    43.                          * 私聊格式:昵称:内容
    44.                          */
    45.                         //找到:的位置
    46.                         int index = message.indexOf(":");
    47.                         if(index>=0){
    48.                             //截取昵称
    49.                             String name = message.substring(1,index);
    50.                             //截取内容
    51.                             String info = message.substring(
    52.                                                  index+1,message.length()
    53.                                                  );
    54.                             //拼接内容
    55.                             info = nickName+"对你说:"+info;
    56.                             //发送私聊信息给指定用户
    57.                             sendMessageToOne(name, info);
    58.                             //发送完私聊后就不在广播了。
    59.                             continue;
    60.                         }
    61.                     }
    62.                     /*
    63.                      * 遍历所有输出流,将该客户端发送的信息转发给所有客户端
    64.                      * 需要同步
    65.                      */
    66.                     sendMessage(nickName+"说:"+message);
    67.                 }
    68.             } catch (Exception e) {
    69.                 e.printStackTrace();
    70.             } finally {
    71.                 /*
    72.                  * 当客户端断线,要将输出流从共享集合中删除
    73.                  * 需要同步
    74.                  */
    75.                 removeOut(nickName);
    76.                 /*
    77.                  * 通知所有用户该用户已下线
    78.                  */
    79.                 sendMessage(nickName+"下线了");
    80.                 System.out.println("当前在线人数:"+allOut.size());
    81.                 if (socket != null) {
    82.                     try {
    83.                         socket.close();
    84.                     } catch (IOException e) {
    85.                         e.printStackTrace();
    86.                     }
    87.                 }
    88.             }
    89.         }
    90. }

    步骤十一:为内部类定义方法getNickName()

    为 Server的内部类ClientHandler定义方法getNickName(),用于获取用户的昵称。代码如下所示:

     
    1.     private class ClientHandler implements Runnable {
    2.         //其他代码,略
    3.         /**
    4.          * 获取该用户的昵称
    5.          * @return
    6.          */
    7.         private String getNickName()throws Exception{
    8.             try {
    9.                 //获取该用户的输出流
    10.                 OutputStream out = socket.getOutputStream();
    11.                 OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
    12.                 PrintWriter pw = new PrintWriter(osw,true);
    13.                 //获取该用户的输入流
    14.                 InputStream in = socket.getInputStream();
    15.                 InputStreamReader isr = new InputStreamReader(in, "UTF-8");
    16.                 BufferedReader br = new BufferedReader(isr);
    17.                 //读取客户端发送过来的昵称
    18.                 String nickName = br.readLine();
    19.                 while(true){
    20.                     //若昵称为空发送失败代码
    21.                     if(nickName.trim().equals("")){
    22.                         pw.println("FAIL");
    23.                     }
    24.                     //若昵称已经存在发送失败代码
    25.                     if(allOut.containsKey(nickName)){
    26.                         pw.println("FAIL");
    27.                     //若成功,发送成功代码,并返回昵称
    28.                     }else{
    29.                         pw.println("OK");
    30.                         return nickName;
    31.                     }
    32.                     //若改昵称被占用,等待用户再次输入昵称
    33.                     nickName = br.readLine();
    34.                 }
    35.             } catch (Exception e) {
    36.                 throw e;
    37.             }            
    38.         }
    39.     }

    步骤十二:为 Server 类创建 start()方法

    为 Server 类创建 start()方法。在该方法中,循环监听8088端口,等待客户端的连接,一旦一个客户端连接后,向线程池申请一个线程来完成针对该客户端的交互。代码如下所示:

     
    1. public class Server {
    2.     //其他代码,略
    3.     /**
    4.      * 服务端开启方法
    5.      */
    6.     public void start() {
    7.         try {
    8.             //循环监听客户端的连接
    9.             while(true){
    10.                 System.out.println("等待客户端连接...");
    11.                 // 监听客户端的连接
    12.                 Socket socket = serverSocket.accept();
    13.                 System.out.println("客户端已连接!");
    14.                 
    15.                 //启动一个线程来完成针对该客户端的交互
    16.                 ClientHandler handler = new ClientHandler(socket);
    17.                 threadPool.execute(handler);
    18.             }            
    19.         } catch (Exception e) {
    20.             e.printStackTrace();
    21.         }
    22.     }
    23. }

    步骤十三:为 Server类定义 main() 方法

    为 Server 类定义 main() 方法,并在 main() 方法中,创建 Server 对象,调用上一步中所创建的 start() 方法。代码如下所示:

     
    1. public class Server {
    2.     //其他代码,略
    3.     public static void main(String[] args) {
    4.         Server server = new Server();
    5.         server.start();
    6.     }
    7. }

    本案例中,类Server的完整代码如下所示:

     
    1. package com.tarena.homework;
    2. import java.io.BufferedReader;
    3. import java.io.IOException;
    4. import java.io.InputStream;
    5. import java.io.InputStreamReader;
    6. import java.io.OutputStream;
    7. import java.io.OutputStreamWriter;
    8. import java.io.PrintWriter;
    9. import java.net.ServerSocket;
    10. import java.net.Socket;
    11. import java.util.HashMap;
    12. import java.util.Map;
    13. import java.util.concurrent.ExecutorService;
    14. import java.util.concurrent.Executors;
    15. /**
    16. * 服务端应用程序
    17. */
    18. public class Server {
    19.     // 服务端Socket
    20.     private ServerSocket serverSocket;
    21.     // 所有客户端输出流,key为用户的昵称,value为该用户的输出流
    22.     private Map<String,PrintWriter> allOut;
    23.     // 线程池
    24.     private ExecutorService threadPool;
    25.     /**
    26.      * 构造方法,用于初始化
    27.      */
    28.     public Server() {
    29.         try {
    30.             serverSocket = new ServerSocket(8088);            
    31.             allOut = new HashMap<String,PrintWriter>();            
    32.             threadPool = Executors.newFixedThreadPool(40);
    33.         } catch (Exception e) {
    34.             e.printStackTrace();
    35.         }
    36.     }
    37.     /**
    38.      * 服务端开启方法
    39.      */
    40.     public void start() {
    41.         try {
    42.             //循环监听客户端的连接
    43.             while(true){
    44.                 System.out.println("等待客户端连接...");
    45.                 // 监听客户端的连接
    46.                 Socket socket = serverSocket.accept();
    47.                 System.out.println("客户端已连接!");
    48.                 
    49.                 //启动一个线程来完成针对该客户端的交互
    50.                 ClientHandler handler = new ClientHandler(socket);
    51.                 threadPool.execute(handler);
    52.             }            
    53.         } catch (Exception e) {
    54.             e.printStackTrace();
    55.         }
    56.     }
    57.     /**
    58.      * 将输出流存入共享集合,与下面两个方法互斥,保证同步安全
    59.      * @param out
    60.      */
    61.     private synchronized void addOut(String nickName,PrintWriter out){
    62.         allOut.put(nickName,out);
    63.     }
    64.     /**
    65.      * 将给定输出流从共享集合删除
    66.      * @param out
    67.      */
    68.     private synchronized void removeOut(String nickName){
    69.         allOut.remove(nickName);
    70.     }
    71.     /**
    72.      * 将消息转发给所有客户端
    73.      * @param message
    74.      */
    75.     private synchronized void sendMessage(String message){
    76.         for(PrintWriter o : allOut.values()){
    77.             o.println(message);
    78.         }
    79.     }
    80.     /**
    81.      * 将消息发送给指定昵称的客户端
    82.      * @param nickName
    83.      * @param message
    84.      */
    85.     private synchronized void sendMessageToOne(String nickName,String message){
    86.         PrintWriter out = allOut.get(nickName);
    87.         if(out!=null){
    88.             out.println(message);
    89.         }
    90.     }
    91.     
    92.     public static void main(String[] args) {
    93.         Server server = new Server();
    94.         server.start();
    95.     }
    96.     /**
    97.      * 线程体,用于并发处理不同客户端的交互
    98.      */
    99.     private class ClientHandler implements Runnable {
    100.         // 该线程用于处理的客户端
    101.         private Socket socket;
    102.         // 开客户端的昵称
    103.         private String nickName;
    104.         public ClientHandler(Socket socket) {
    105.             this.socket = socket;
    106.         }
    107.         @Override
    108.         public void run() {
    109.             PrintWriter pw = null;
    110.             try {
    111.                 //将客户端的输出流存入共享集合,以便广播消息
    112.                 OutputStream out = socket.getOutputStream();
    113.                 OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
    114.                 pw = new PrintWriter(osw,true);
    115.                 /*
    116.                  * 将用户信息存入共享集合
    117.                  * 需要同步
    118.                  */
    119.                 //先获取该用户昵称
    120.                 nickName = getNickName();
    121.                 addOut(nickName,pw);
    122.                 Thread.sleep(100);
    123.                 /*
    124.                  * 通知所有用户该用户已上线
    125.                  */
    126.                 sendMessage(nickName+"上线了");
    127.                 
    128.                 InputStream in = socket.getInputStream();
    129.                 InputStreamReader isr = new InputStreamReader(in, "UTF-8");
    130.                 BufferedReader br = new BufferedReader(isr);
    131.                 
    132.                 String message = null;
    133.                 // 循环读取客户端发送的信息
    134.                 while ((message = br.readLine())!=null) {
    135.                     //首先查看是不是私聊
    136.                     if(message.startsWith("\")){
    137.                         /*
    138.                          * 私聊格式:昵称:内容
    139.                          */
    140.                         //找到:的位置
    141.                         int index = message.indexOf(":");
    142.                         if(index>=0){
    143.                             //截取昵称
    144.                             String name = message.substring(1,index);
    145.                             //截取内容
    146.                             String info = message.substring(index+1,message.length());
    147.                             //拼接内容
    148.                             info = nickName+"对你说:"+info;
    149.                             //发送私聊信息给指定用户
    150.                             sendMessageToOne(name, info);
    151.                             //发送完私聊后就不在广播了。
    152.                             continue;
    153.                         }
    154.                     }
    155.                     /*
    156.                      * 遍历所有输出流,将该客户端发送的信息转发给所有客户端
    157.                      * 需要同步
    158.                      */
    159.                     sendMessage(nickName+"说:"+message);
    160.                 }
    161.             } catch (Exception e) {
    162.                 e.printStackTrace();
    163.             } finally {
    164.                 /*
    165.                  * 当客户端断线,要将输出流从共享集合中删除
    166.                  * 需要同步
    167.                  */
    168.                 removeOut(nickName);
    169.                 /*
    170.                  * 通知所有用户该用户已下线
    171.                  */
    172.                 sendMessage(nickName+"下线了");
    173.                 System.out.println("当前在线人数:"+allOut.size());
    174.                 if (socket != null) {
    175.                     try {
    176.                         socket.close();
    177.                     } catch (IOException e) {
    178.                         e.printStackTrace();
    179.                     }
    180.                 }
    181.             }
    182.         }
    183.         /**
    184.          * 获取该用户的昵称
    185.          * @return
    186.          */
    187.         private String getNickName()throws Exception{
    188.             try {
    189.                 //获取该用户的输出流
    190.                 OutputStream out = socket.getOutputStream();
    191.                 OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
    192.                 PrintWriter pw = new PrintWriter(osw,true);
    193.                 //获取该用户的输入流
    194.                 InputStream in = socket.getInputStream();
    195.                 InputStreamReader isr = new InputStreamReader(in, "UTF-8");
    196.                 BufferedReader br = new BufferedReader(isr);
    197.                 //读取客户端发送过来的昵称
    198.                 String nickName = br.readLine();
    199.                 while(true){
    200.                     //若昵称为空发送失败代码
    201.                     if(nickName.trim().equals("")){
    202.                         pw.println("FAIL");
    203.                     }
    204.                     //若昵称已经存在发送失败代码
    205.                     if(allOut.containsKey(nickName)){
    206.                         pw.println("FAIL");
    207.                     //若成功,发送成功代码,并返回昵称
    208.                     }else{
    209.                         pw.println("OK");
    210.                         return nickName;
    211.                     }
    212.                     //若改昵称被占用,等待用户再次输入昵称
    213.                     nickName = br.readLine();
    214.                 }
    215.             } catch (Exception e) {
    216.                 throw e;
    217.             }            
    218.         }
    219.     }
    220. }
     

    本案例中,类Client的完整代码如下所示:

     
    1. package com.tarena.homework;
    2. import java.io.BufferedReader;
    3. import java.io.IOException;
    4. import java.io.InputStream;
    5. import java.io.InputStreamReader;
    6. import java.io.OutputStream;
    7. import java.io.OutputStreamWriter;
    8. import java.io.PrintWriter;
    9. import java.net.Socket;
    10. import java.util.Scanner;
    11. /**
    12. * 客户端应用程序
    13. */
    14. public class Client {
    15.     //客户端Socket
    16.     private Socket socket;
    17.     /**
    18.      * 构造方法,用于初始化
    19.      */
    20.     public Client(){
    21.         try {
    22.             socket = new Socket("localhost",8088);
    23.         } catch (Exception e) {
    24.             e.printStackTrace();
    25.         }
    26.     }
    27.     /**
    28.      * 客户端工作方法
    29.      */
    30.     public void start(){
    31.         try {
    32.             //创建Scanner读取用户输入内容
    33.             Scanner scanner = new Scanner(System.in);
    34.             //首先输入昵称
    35.             inputNickName(scanner);
    36.             
    37.             //将接收服务端信息的线程启动
    38.             ServerHander handler = new ServerHander();
    39.             Thread t = new Thread(handler);
    40.             t.setDaemon(true);
    41.             t.start();
    42.             
    43.             OutputStream out = socket.getOutputStream();            
    44.             OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");            
    45.             PrintWriter pw = new PrintWriter(osw,true);
    46.             while(true){
    47.                 pw.println(scanner.nextLine());
    48.             }
    49.         } catch (Exception e) {
    50.             e.printStackTrace();
    51.         } finally{
    52.             if(socket != null){
    53.                 try {
    54.                     socket.close();
    55.                 } catch (IOException e) {
    56.                     e.printStackTrace();
    57.                 }
    58.             }
    59.         }
    60.     }
    61.     
    62.     public static void main(String[] args) {
    63.         Client client = new Client();
    64.         client.start();
    65.     }
    66.     
    67.     /**
    68.      * 输入昵称
    69.      */
    70.     private void inputNickName(Scanner scanner)throws Exception{
    71.         //定义昵称
    72.         String nickName = null;
    73.         //创建输出流
    74.         PrintWriter pw = new PrintWriter(
    75.                             new OutputStreamWriter(
    76.                                 socket.getOutputStream(),"UTF-8")
    77.                          ,true);
    78.         //创建输入流
    79.         BufferedReader br = new BufferedReader(
    80.                                 new InputStreamReader(
    81.                                         socket.getInputStream(),"UTF-8")
    82.                          );
    83.         /*
    84.          * 循环以下操作
    85.          * 输入用户名,并上传至服务器,等待服务器回应,若昵称可用就结束循环,否则通知用户后
    86.          * 重新输入昵称
    87.          */
    88.         while(true){
    89.             System.out.println("请输入昵称:");
    90.             nickName = scanner.nextLine();
    91.             if(nickName.trim().equals("")){
    92.                 System.out.println("昵称不能为空");
    93.             }else{
    94.                 pw.println(nickName);
    95.                 String pass = br.readLine();
    96.                 if(pass!=null&&!pass.equals("OK")){
    97.                     System.out.println("昵称已被占用,请更换。");
    98.                 }else{
    99.                     System.out.println("你好!"+nickName+",开始聊天吧!");
    100.                     break;
    101.                 }
    102.             }
    103.         }
    104.     }
    105.     
    106.     /**
    107.      * 该线程用于接收服务端发送过来的信息
    108.      */
    109.     private class ServerHander implements Runnable{
    110.         @Override
    111.         public void run() {
    112.             try {
    113.                 InputStream in = socket.getInputStream();
    114.                 InputStreamReader isr = new InputStreamReader(in, "UTF-8");
    115.                 BufferedReader br = new BufferedReader(isr);
    116.                 while(true){
    117.                     System.out.println(br.readLine());
    118.                 }
    119.             } catch (Exception e) {
    120.                 e.printStackTrace();
    121.             }
    122.         }
    123.     }
    124. }
     
  • 相关阅读:
    mybatis的注意事项一
    java代码操作word模板生成PDF文件
    使用mybatis框架实现带条件查询多条件(传入实体类)
    MyBatis框架ResultMap节点
    优化mybatis框架中的查询用户记录数的案例
    Mybatis框架联表查询显示问题解决
    使用mybatis框架实现带条件查询单条件
    [DB] 如何彻底卸载删除MySQL 【MYSQL】
    [DB] MySQL窗口输入密码后消失问题 【MYSQL】
    [acm] 曾经 刷题记录 [只有正确的坚持才是胜利]
  • 原文地址:https://www.cnblogs.com/xyk1987/p/8330970.html
Copyright © 2020-2023  润新知