完成聊天室的私聊功能

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. }
 
原文地址:https://www.cnblogs.com/xyk1987/p/8330970.html