创建NetSocketIoChat聊天系统
使用技术关键字有 Socket,Io流,Thread
step1:首先创建客户端与服务端以及实体类
Message实体类的创建
public class Message implements Serializable { //序列化 private static final long serialVersionUID = 4497214422911483808L; //操作类型 private Integer type; //消息 private String content; //用来登录 private String username; private String password; //给朋友发消息时,用于指定用户 private String friendUsername; public void setType(Integer type) { this.type = type; } public void setContent(String content) { this.content = content; } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } public void setFriendUsername(String friendUsername) { this.friendUsername = friendUsername; } public Integer getType() { return type; } public String getContent() { return content; } public String getUsername() { return username; } public String getPassword() { return password; } public String getFriendUsername() { return friendUsername; } //登录构造器 public Message(String username, String password,Integer type) { this.username = username; this.password = password; this.type = type; } //登录,向服务器发消息 public Message(Integer type, String content, String username) { this.type = type; this.content = content; this.username = username; } //向在线用户发消息 public Message(Integer type, String content, String username, String friendUsername) { this.type = type; this.content = content; this.username = username; this.friendUsername = friendUsername; } public Message() { } @Override public String toString() { return "Message{" + "type=" + type + ", content='" + content + '\'' + ", username='" + username + '\'' + ", password='" + password + '\'' + ", friendUsername='" + friendUsername + '\'' + '}'; } }
常量进行打包存放
public class Constant { public static final String SUCCESS = "SUCCESS"; public static final String FAIL = "FAIL"; public static final String SERVER_NAME = "server"; public static final String OK= "ok"; public static final String DEFAULT_PASSWORD = "123"; //保存在线的用户 public static final Map<String, Socket> ONLINE_USERS = new ConcurrentHashMap<>(8); } //操作类型 public class MessageType { public static final int TO_SERVER = 2; public static final int TO_FRIEND = 3; public static final int TO_ALL = 4; public static final int LOGIN = 1; public static final int FORM_SERVER = 6; public static final int RECEIVER = 5; }
读写工具类打包
public class MsgUtils { //读操作 public static Optional<Message> readMsg(InputStream inputStream) { ObjectInputStream ois = null; try { ois = new ObjectInputStream(inputStream); //封装成一个optional,将来使用的时候就可以避免空指针 return Optional.ofNullable((Message) ois.readObject()); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } return Optional.empty(); } //向输出流写数据 public static void writeMsg(OutputStream outputStream,Message message) { ObjectOutputStream oos = null; try { oos = new ObjectOutputStream(outputStream); //封装成一个optional,将来使用的时候就可以避免空指针 oos.writeObject(message); oos.flush(); } catch (IOException e) { e.printStackTrace(); } } }
ScannerUtil打包为后续操作提供便利
public class ScannerUtil { private static final Scanner scanner = new Scanner(System.in); public static String input(){ return scanner.next(); } }
服务端与客户端创建
public class Server { public static void main(String[] args){ try ( //创建端口服务 ServerSocket serverSocket = new ServerSocket() ){ //2.随便绑定端口 serverSocket.bind(new InetSocketAddress(8888)); System.out.println("服务器已经启动,在8888端口!"); //开始监听 //建立一个连接 while (true){ Socket accept = serverSocket.accept(); new ServerThread(accept).start(); } } catch (IOException e) { e.printStackTrace(); } } } //客户端 Socket socket = new Socket(); //绑定服务端的端口 socket.connect(new InetSocketAddress(8888)); //给服务端发消息 //发消息 OutputStream outputStream = socket.getOutputStream(); InputStream inputStream = socket.getInputStream(); //一切就绪,开始进行登录操作 String username = null; public static void main(String[] args) throws IOException { Socket socket = new Socket(); //绑定服务端的端口 socket.connect(new InetSocketAddress(8888)); //给服务端发消息 //发消息 OutputStream outputStream = socket.getOutputStream(); InputStream inputStream = socket.getInputStream(); //开始登录工作 String username = null; //表示当前登录的用户 while (true) { if (username == null) { username = login(outputStream, inputStream); } else { //打印菜单 printOrder(); String input = ScannerUtil.input(); //把scanner内容转为int类型 switch (Integer.parseInt(input)) { case MessageType.LOGIN: login(outputStream, inputStream); case MessageType.TO_SERVER: sendToServer(username, outputStream, inputStream); break; case MessageType.TO_FRIEND: sendToFriend(username, outputStream, inputStream); break; case MessageType.TO_ALL: sendToAll(username, outputStream, inputStream); break; case MessageType.RECEIVER: receiveMsg(inputStream); break; default: break; } } } }
step2:开始进行各项模块的验证
登录模块
//登录模块 客户端 //登录的方法 private static String login(OutputStream outputStream, InputStream inputStream) { //登录 System.out.println("请您输入用户名:"); String name = ScannerUtil.input(); System.out.println("请输入密码"); String pwd = ScannerUtil.input(); Message message = new Message(); message.setType(MessageType.LOGIN); message.setUsername(name); message.setPassword(pwd); //发送给服务器 MsgUtils.writeMsg(outputStream, message); //接收来自服务的的消息 Optional<Message> msg = MsgUtils.readMsg(inputStream); if (msg.isPresent() && Constant.SUCCESS.equals(msg.get().getContent())) { return name; } return null; } //服务端验证 private void loginHandler(InputStream inputStream, OutputStream outputStream, Message message, Socket accept) { //判断登录的逻辑 //1.message 不存在 if (message.getUsername() == null || !Constant.DEFAULT_PASSWORD.equals(message.getPassword())) { MsgUtils.writeMsg(outputStream, new Message(MessageType.FORM_SERVER, Constant.FAIL, Constant.SERVER_NAME)); } else { //验证成功放入在线用户map中 Constant.ONLINE_USERS.put(message.getUsername(), accept); System.out.println(message.getUsername() + "登录成功!"); MsgUtils.writeMsg(outputStream, new Message(MessageType.FORM_SERVER, Constant.SUCCESS, "accept")); } }
给服务器发消息
//客户端 private static void sendToServer(String username, OutputStream outputStream, InputStream inputStream) { System.out.print(username + ":"); String msg = ScannerUtil.input(); //给服务器发消息 MsgUtils.writeMsg( outputStream, new Message(MessageType.TO_SERVER, msg, username) ); //接收消息,服务器收到消息后回消息给客户端 Optional<Message> massage = MsgUtils.readMsg(inputStream); massage.ifPresent(m -> System.out.println(m.getUsername() + ":" + m.getContent())); } //服务器端 private void sendToClient(InputStream inputStream, OutputStream outputStream, Message message) { //接收消息并打印 System.out.println(message.getUsername() + ":" + message.getContent()); //给客户端回消息 MsgUtils.writeMsg(outputStream, new Message(MessageType.FORM_SERVER, Constant.OK, Constant.SERVER_NAME)); }
给指定在线用户发消息
既然是用户之间的通信那么就应该开启多线程进行多用户登录
//之前服务器端的操作已经进行封装 //开始监听 //建立一个连接 while (true){ Socket accept = serverSocket.accept(); new ServerThread(accept).start(); }
一个人发,一个人收,所以就要再弄两个方法一个收一个发
现在做发的操作
//客户端代码 //在while循环中加入方法,具体可看前面的Client完整代码 case MessageType.TO_FRIEND: sendToFriend(username, outputStream, inputStream); break; //发送主方法 private static void sendToFriend(String username, OutputStream outputStream, InputStream inputStream) { System.out.println("请输入好友的名字:"); String friend = ScannerUtil.input(); boolean flag = true; while (flag) { System.out.print(username + ":"); String msg = ScannerUtil.input(); //退出机制 if ("bye".equals(msg)) { flag = false; } MsgUtils.writeMsg( outputStream,new Message(MessageType.TO_FRIEND, msg, username, friend) ); } } //接收方法 private static void receiveMsg(InputStream inputStream) { while (true){ Optional<Message> massage = MsgUtils.readMsg(inputStream); massage.ifPresent(m -> System.out.println(m.getUsername() + ":" + m.getContent())); } } //--------------------------------------------------- //服务端 //给目标用户发送消息 private void sendToTarget( Message message) { //先找到对应的socket try { Socket friendSocket = Constant.ONLINE_USERS.get(message.getFriendUsername()); MsgUtils.writeMsg(friendSocket.getOutputStream(),message); } catch (IOException e) { e.printStackTrace(); } }
群发消息
群发需要用到多线程的知识,因此,在一开始把服务端的所有操作都打包起来,然后重写Thread的run方法,这样可以实现一个客户端操作对应一个线程
//线程创建如下 public class ServerThread extends Thread { private Socket accept; public ServerThread() { } //构造器,在server类中进行调用 public ServerThread(Socket accept) { this.accept = accept; } @Override public void run() { try (InputStream inputStream = accept.getInputStream(); OutputStream outputStream = accept.getOutputStream() ) { while (true) { //调用readMsg方法准备接收客户端发来的消息 Optional<Message> message = MsgUtils.readMsg(inputStream); if (message.isPresent()) { //获得客户端输入内容 Message msg = message.get(); //判断客户端操作的类型,然后进行判断转跳到对应的处理方法 switch (msg.getType()) { case MessageType.LOGIN: loginHandler(inputStream, outputStream, msg, accept); break; case MessageType.TO_SERVER: sendToClient(inputStream, outputStream, msg); break; case MessageType.TO_FRIEND: sendToTarget(msg); break; case MessageType.TO_ALL: sendToAll(msg); break; default: break; } } } } catch (IOException e) { e.printStackTrace(); } } //server类中进行接收客户端传来的消息 while (true){ Socket accept = serverSocket.accept(); //每次创建一个Socket就启动一个线程,实现多用户用时在线 new ServerThread(accept).start(); } } catch (IOException e) { e.printStackTrace(); } }
客户端写群发方法
private static void sendToAll(String username, OutputStream outputStream, InputStream inputStream) { boolean flag = true; while (flag) { System.out.print(username + ":"); String msg = ScannerUtil.input(); if ("bye".equals(msg)) { flag = false; } MsgUtils.writeMsg( outputStream,new Message(MessageType.TO_ALL, msg, username) ); } }
服务端进行处理
private void sendToAll(Message message) { //遍历map for (Map.Entry<String,Socket> entry :Constant.ONLINE_USERS.entrySet()){ try { MsgUtils.writeMsg(entry.getValue().getOutputStream(), message); } catch (IOException e) { e.printStackTrace(); } } }