需求:模拟聊天室群聊
客户端要先登录,登录成功之后才能发送和接收消息
分析:
Message 类与 Code 类的示例代码:
1 //Code 2 public class Code { 3 public static final int LOGIN = 1; 4 public static final int CHAT = 2; 5 public static final int LOGOUT = 3; 6 7 public static final int SUCCESS = 1; 8 public static final int FAIL = 2; 9 } 10 11 12 // Message 13 public class Message implements Serializable{ 14 private static final long serialVersionUID = 1L; 15 private int code; 16 private String username; 17 private String content; 18 public Message(int code, String username, String content) { 19 super(); 20 this.code = code; 21 this.username = username; 22 this.content = content; 23 } 24 25 public Message() { 26 super(); 27 } 28 29 public int getCode() { 30 return code; 31 } 32 public void setCode(int code) { 33 this.code = code; 34 } 35 public String getUsername() { 36 return username; 37 } 38 public void setUsername(String username) { 39 this.username = username; 40 } 41 public String getContent() { 42 return content; 43 } 44 public void setContent(String content) { 45 this.content = content; 46 } 47 }
服务器端用户管理类代码:
1 import java.util.HashMap; 2 3 public class UserManager { 4 public static HashMap<String,String> allUsers = new HashMap<String,String>(); 5 static{ 6 allUsers.put("gangge", "123"); 7 allUsers.put("xiaobai", "456"); 8 allUsers.put("gujie", "789"); 9 } 10 11 public static boolean login(String username, String password){ 12 if(allUsers.get(username)!=null && allUsers.get(username).equals(password)){ 13 return true; 14 }else{ 15 return false; 16 } 17 } 18 }
服务器端实例代码:
1 import java.net.ServerSocket; 2 import java.net.Socket; 3 4 public class Server { 5 public static void main(String[] args)throws Exception { 6 @SuppressWarnings("resource") 7 ServerSocket server = new ServerSocket(9999); 8 9 while(true){ 10 Socket socket = server.accept(); 11 12 ClientHandlerThread ct = new ClientHandlerThread(socket); 13 ct.start(); 14 } 15 } 16 }
服务器端处理消息的线程类代码:
1 import java.io.IOException; 2 import java.io.ObjectInputStream; 3 import java.io.ObjectOutputStream; 4 import java.net.Socket; 5 import java.util.ArrayList; 6 import java.util.Collections; 7 import java.util.HashSet; 8 import java.util.Set; 9 10 import com.tcp.chat.bean.Code; 11 import com.tcp.chat.bean.Message; 12 13 public class ClientHandlerThread extends Thread{ 14 public static Set<ObjectOutputStream> online = Collections.synchronizedSet(new HashSet<ObjectOutputStream>()); 15 16 private Socket socket; 17 private String username; 18 private ObjectInputStream ois; 19 private ObjectOutputStream oos; 20 21 public ClientHandlerThread(Socket socket) { 22 super(); 23 this.socket = socket; 24 } 25 26 public void run(){ 27 Message message = null; 28 try{ 29 ois = new ObjectInputStream(socket.getInputStream()); 30 oos = new ObjectOutputStream(socket.getOutputStream()); 31 32 //接收数据 33 while (true) { 34 message = (Message) ois.readObject(); 35 36 if(message.getCode() == Code.LOGIN){ 37 //如果是登录,则验证用户名密码 38 username = message.getUsername(); 39 String password = message.getContent(); 40 if(UserManager.login(username, password)){ 41 message.setCode(Code.SUCCESS); 42 oos.writeObject(message); 43 44 //并将该用户添加到在线人员名单中 45 online.add(oos); 46 47 message.setCode(Code.CHAT); 48 message.setContent("上线了"); 49 //通知其他人,xx上线了 50 sendToOther(message); 51 }else{ 52 message.setCode(Code.FAIL); 53 oos.writeObject(message); 54 } 55 }else if(message.getCode() == Code.CHAT){ 56 //如果是聊天信息,把消息转发给其他在线客户端 57 sendToOther(message); 58 }else if(message.getCode() == Code.LOGOUT){ 59 //通知其他人,xx下线了 60 message.setContent("下线了"); 61 sendToOther(message); 62 break; 63 } 64 } 65 }catch(Exception e){ 66 //通知其他人,xx掉线了 67 if(message!=null && username!=null){ 68 message.setCode(Code.LOGOUT); 69 message.setContent("掉线了"); 70 sendToOther(message); 71 } 72 }finally{ 73 //从在线人员中移除并断开当前客户端 74 try { 75 online.remove(oos); 76 socket.close(); 77 } catch (IOException e) { 78 e.printStackTrace(); 79 } 80 } 81 } 82 83 private void sendToOther(Message message) { 84 ArrayList<ObjectOutputStream> offline = new ArrayList<ObjectOutputStream>(); 85 for (ObjectOutputStream on : online) { 86 if(!on.equals(oos)){ 87 try { 88 on.writeObject(message); 89 } catch (IOException e) { 90 offline.add(on); 91 } 92 } 93 } 94 95 for (ObjectOutputStream off : offline) { 96 online.remove(off); 97 } 98 } 99 }
客户端示例代码:
1 import java.io.ObjectInputStream; 2 import java.io.ObjectOutputStream; 3 import java.net.Socket; 4 import java.util.Scanner; 5 6 import com.tcp.chat.bean.Code; 7 import com.tcp.chat.bean.Message; 8 9 public class Client { 10 public static void main(String[] args) throws Exception{ 11 Socket socket = new Socket("192.168.1.107", 9999); 12 13 ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream()); 14 ObjectInputStream ois = new ObjectInputStream(socket.getInputStream()); 15 16 //先登录 17 Scanner scanner = new Scanner(System.in); 18 String username; 19 while(true){ 20 //输入登录信息 21 System.out.println("用户名:"); 22 username = scanner.nextLine(); 23 System.out.println("密码:"); 24 String password = scanner.nextLine(); 25 26 Message msg = new Message(Code.LOGIN, username, password); 27 //发送登录数据 28 oos.writeObject(msg); 29 // 接收登录结果 30 msg = (Message) ois.readObject(); 31 if(msg.getCode() == Code.SUCCESS){ 32 System.out.println("登录成功!"); 33 break; 34 }else if(msg.getCode() == Code.FAIL){ 35 System.out.println("用户名或密码错误,登录失败,重新输入"); 36 } 37 } 38 39 //启动收消息和发消息线程 40 SendThread s = new SendThread(oos,username); 41 ReceiveThread r = new ReceiveThread(ois); 42 s.start(); 43 r.start(); 44 45 s.join();//不发了,就结束 46 r.setFlag(false); 47 r.join(); 48 49 scanner.close(); 50 socket.close(); 51 } 52 }
客户端发消息线程类代码:
1 import java.io.IOException; 2 import java.io.ObjectOutputStream; 3 import java.util.Scanner; 4 5 import com.tcp.chat.bean.Code; 6 import com.tcp.chat.bean.Message; 7 8 public class SendThread extends Thread{ 9 private ObjectOutputStream oos; 10 private String username; 11 12 public SendThread(ObjectOutputStream oos,String username) { 13 super(); 14 this.oos = oos; 15 this.username = username; 16 } 17 18 public void run(){ 19 try { 20 Scanner scanner = new Scanner(System.in); 21 while(true){ 22 System.out.println("请输入消息内容:"); 23 String content = scanner.nextLine(); 24 Message msg; 25 if("bye".equals(content)){ 26 msg = new Message(Code.LOGOUT, username, content); 27 oos.writeObject(msg); 28 scanner.close(); 29 break; 30 }else{ 31 msg = new Message(Code.CHAT, username, content); 32 oos.writeObject(msg); 33 } 34 } 35 } catch (IOException e) { 36 e.printStackTrace(); 37 } 38 } 39 }
客户端接受消息线程类代码:
1 import java.io.ObjectInputStream; 2 3 import com.tcp.chat.bean.Message; 4 5 public class ReceiveThread extends Thread{ 6 private ObjectInputStream ois; 7 private volatile boolean flag = true; 8 9 public ReceiveThread(ObjectInputStream ois) { 10 super(); 11 this.ois = ois; 12 } 13 public void run(){ 14 try { 15 while(flag){ 16 Message msg = (Message) ois.readObject(); 17 System.out.println(msg.getUsername() + ":" + msg.getContent()); 18 } 19 } catch (Exception e) { 20 System.out.println("请重新登录"); 21 } 22 } 23 public void setFlag(boolean flag) { 24 this.flag = flag; 25 } 26 27 }
注意:
以上案例的网络通信程序是基于阻塞式API的,所以服务器必须为每个客户端都提供一条独立线程进行处理,当服务器需要同时处理大量客户端时,这种做法会导致性能下降。如果要开发高性能网络服务器,那么需要使用Java提供的NIO API,可以让服务器使用一个或有限几个线程来同时处理连接到服务器上的所有客户端。