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. private class ServerHander implements Runnable{
  7. @Override
  8. public void run() {
  9. try {
  10. InputStream in = socket.getInputStream();
  11. InputStreamReader isr = new InputStreamReader(in, "UTF-8");
  12. BufferedReader br = new BufferedReader(isr);
  13. while(true){
  14. System.out.println(br.readLine());
  15. }
  16. } catch (Exception e) {
  17. e.printStackTrace();
  18. }
  19. }
  20. }
  21. }

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

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

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

步骤四:创建客户端工作方法 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. ServerHander handler = new ServerHander();
  14. Thread t = new Thread(handler);
  15. t.setDaemon(true);
  16. t.start();
  17. OutputStream out = socket.getOutputStream();
  18. OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
  19. PrintWriter pw = new PrintWriter(osw,true);
  20. while(true){
  21. pw.println(scanner.nextLine());
  22. }
  23. } catch (Exception e) {
  24. e.printStackTrace();
  25. } finally{
  26. if(socket != null){
  27. try {
  28. socket.close();
  29. } catch (IOException e) {
  30. e.printStackTrace();
  31. }
  32. }
  33. }
  34. }
  35. }

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

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

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

步骤六:定义 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. InputStream in = socket.getInputStream();
  33. InputStreamReader isr = new InputStreamReader(in, "UTF-8");
  34. BufferedReader br = new BufferedReader(isr);
  35. String message = null;
  36. // 循环读取客户端发送的信息
  37. while ((message = br.readLine())!=null) {
  38. //首先查看是不是私聊
  39. if(message.startsWith("\\")){
  40. /*
  41. * 私聊格式:\昵称:内容
  42. */
  43. //找到:的位置
  44. int index = message.indexOf(":");
  45. if(index>=0){
  46. //截取昵称
  47. String name = message.substring(1,index);
  48. //截取内容
  49. String info = message.substring(
  50. index+1,message.length()
  51. );
  52. //拼接内容
  53. info = nickName+"对你说:"+info;
  54. //发送私聊信息给指定用户
  55. sendMessageToOne(name, info);
  56. //发送完私聊后就不在广播了。
  57. continue;
  58. }
  59. }
  60. /*
  61. * 遍历所有输出流,将该客户端发送的信息转发给所有客户端
  62. * 需要同步
  63. */
  64. sendMessage(nickName+"说:"+message);
  65. }
  66. } catch (Exception e) {
  67. e.printStackTrace();
  68. } finally {
  69. /*
  70. * 当客户端断线,要将输出流从共享集合中删除
  71. * 需要同步
  72. */
  73. removeOut(nickName);
  74. /*
  75. * 通知所有用户该用户已下线
  76. */
  77. sendMessage(nickName+"下线了");
  78. System.out.println("当前在线人数:"+allOut.size());
  79. if (socket != null) {
  80. try {
  81. socket.close();
  82. } catch (IOException e) {
  83. e.printStackTrace();
  84. }
  85. }
  86. }
  87. }
  88. }

步骤十一:为内部类定义方法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. ClientHandler handler = new ClientHandler(socket);
  16. threadPool.execute(handler);
  17. }
  18. } catch (Exception e) {
  19. e.printStackTrace();
  20. }
  21. }
  22. }

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

本案例中,类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. ServerHander handler = new ServerHander();
  38. Thread t = new Thread(handler);
  39. t.setDaemon(true);
  40. t.start();
  41. OutputStream out = socket.getOutputStream();
  42. OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
  43. PrintWriter pw = new PrintWriter(osw,true);
  44. while(true){
  45. pw.println(scanner.nextLine());
  46. }
  47. } catch (Exception e) {
  48. e.printStackTrace();
  49. } finally{
  50. if(socket != null){
  51. try {
  52. socket.close();
  53. } catch (IOException e) {
  54. e.printStackTrace();
  55. }
  56. }
  57. }
  58. }
  59. public static void main(String[] args) {
  60. Client client = new Client();
  61. client.start();
  62. }
  63. /**
  64. * 输入昵称
  65. */
  66. private void inputNickName(Scanner scanner)throws Exception{
  67. //定义昵称
  68. String nickName = null;
  69. //创建输出流
  70. PrintWriter pw = new PrintWriter(
  71. new OutputStreamWriter(
  72. socket.getOutputStream(),"UTF-8")
  73. ,true);
  74. //创建输入流
  75. BufferedReader br = new BufferedReader(
  76. new InputStreamReader(
  77. socket.getInputStream(),"UTF-8")
  78. );
  79. /*
  80. * 循环以下操作
  81. * 输入用户名,并上传至服务器,等待服务器回应,若昵称可用就结束循环,否则通知用户后
  82. * 重新输入昵称
  83. */
  84. while(true){
  85. System.out.println("请输入昵称:");
  86. nickName = scanner.nextLine();
  87. if(nickName.trim().equals("")){
  88. System.out.println("昵称不能为空");
  89. }else{
  90. pw.println(nickName);
  91. String pass = br.readLine();
  92. if(pass!=null&&!pass.equals("OK")){
  93. System.out.println("昵称已被占用,请更换。");
  94. }else{
  95. System.out.println("你好!"+nickName+",开始聊天吧!");
  96. break;
  97. }
  98. }
  99. }
  100. }
  101. /**
  102. * 该线程用于接收服务端发送过来的信息
  103. */
  104. private class ServerHander implements Runnable{
  105. @Override
  106. public void run() {
  107. try {
  108. InputStream in = socket.getInputStream();
  109. InputStreamReader isr = new InputStreamReader(in, "UTF-8");
  110. BufferedReader br = new BufferedReader(isr);
  111. while(true){
  112. System.out.println(br.readLine());
  113. }
  114. } catch (Exception e) {
  115. e.printStackTrace();
  116. }
  117. }
  118. }
  119. }
 

转载于:https://www.cnblogs.com/xyk1987/p/8330970.html

完成聊天室的私聊功能相关推荐

  1. 聊天室私聊php代码,window_聊天室实现私聊(三),聊天室程序是一个application和se - phpStudy...

    聊天室实现私聊(三) 聊天室程序是一个application和session对象结合性很强的asp程序.首先,它比较具有实时性,聊天速度太慢,那么没有人会喜欢的,而且在多人同时发言的时侯,如果程序处理 ...

  2. SpringBoot与webSocket实现在线聊天室——实现私聊+群聊+聊天记录保存

    SpringBoot与webSocket实现在线聊天室--实现私聊+群聊+聊天记录保存 引用参考:原文章地址:https://blog.csdn.net/qq_41463655/article/det ...

  3. 使用html5制作聊天室,快速实现H5聊天室和管理功能

    对于FastHttpApi来说搭建一个基于Websocket的页面聊天室是一个非常简单的事件:毕竟基于FastHttpApi编写的接口默认就提供了WebSocket支持,因此在做基于Websocket ...

  4. 出售视频聊天室源码 功能类似 YY 齐秀

    出售视频聊天室源码 功能类似 YY  齐秀 转载于:https://www.cnblogs.com/sxsoft/archive/2012/05/16/2504398.html

  5. java网络程序设计 聊天室之私聊、群聊和清屏功能的实现

    TCP聊天室实现了私聊.群聊和清屏的功能,简陋的UI界面,一个服务器端,支持多个客户端之间的通信. 项目代码:https://pan.baidu.com/s/17iegRam4KnWvcWHw3mvp ...

  6. 基于Vue+springboot+websocket实现的简短仿微信web聊天室(私聊和群聊功能)(可在线预览)

    写目录 一.界面展示 二.介绍 一.界面展示 之前闲着有空就给自己的个人博客搭了一些附加功能,聊天室也是其中之一,简单的实现了私聊.群聊功能,可以发送emoji表情和图片等,项目已经部署在www.tc ...

  7. javaWeb实现聊天室(私聊+群聊)

    写在前面 近几天,迎来了第一个小项目,不做不知道,一做吓一跳.好多知识都掌握的不够扎实,看似会了,但其实似懂非懂,不能真正掌握原理,导致使用起来错误百出.而且深深体会到,知识只有到用时方恨少,一个简单 ...

  8. Netty中实现多客户端连接与通信-以实现聊天室群聊功能为例(附代码下载)

    场景 Netty的Socket编程详解-搭建服务端与客户端并进行数据传输: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/1086 ...

  9. Java用TCP手写聊天室 可以 私聊版加群聊版

    一:引言 想要私聊必须有规定的格式:@名字:要说的话 二:上码 1.服务端 package com.wyj.talkhome; /** * 实现一个用户可以接发多条消息 * * */ import j ...

最新文章

  1. PS调出通透唯美阳光外景女生照片
  2. [Hbase]Hbase章2 Hbase读写过程解析
  3. Kafka剖析(一):Kafka背景及架构介绍--转
  4. php中文网企业网站,闻名 PHP企业网站系统 weenCompany v5.3.0 简体中文 UTF8
  5. real210移植记录-支持eMMC,增加菜单操作
  6. 信息学奥赛一本通(C++)在线评测系统——基础(一)C++语言—— 1044:判断是否为两位数
  7. ftp之高级配置——虚拟用户
  8. 基于ZooKeeper的分布式Session实现
  9. Hadoop学习笔记
  10. python中的正则表达式是干嘛的_python中正则表达式总结
  11. Win10+TeXLive2021无法识别新安装字体解决方法
  12. Pro ASP.NET MVC - [3]Prerequisites(前提) - [2]Domain Modeling
  13. 获取邮箱的DNS和MX 工具类
  14. selenium打开网址
  15. python用函数判断一个数是否为素数,python分享是否为素数 python输入并判断一个数是否为素数...
  16. python求最大素数_python-最大素数
  17. JAVA身份证阅读器数据返回图片
  18. 从致敬KAWS系列盲盒大火,看“NFT+盲盒”玩法的想象空间
  19. 5.11 Go语言文本大数据处理(2):文件分割与入库
  20. 骞云再获阿里云产品生态集成认证,携手共建云原生管理新生态

热门文章

  1. [react] 什么是React.forwardRef?它有什么作用?
  2. 前端学习(3060):vue+element今日头条管理-处理展示文章封面
  3. [html] 在两个iframe之间传递参数的方法有哪些?
  4. [html] 说说如果a链接href=““(空)时点击时会有什么表现?
  5. [html] title与h1的区别、b与strong的区别、i与em的区别?
  6. [html] js放在html的<body>和<head>有什么区别?
  7. [css] css中的选择器、属性、属性值区分大小写吗?
  8. 前端学习(2833):样式rpx
  9. 工作总结3:axios里面的主要参数
  10. 前端学习(2341):jsx的本质