文章目录
Socket 是一种网络通信的端点,它允许程序在网络上进行数据的发送和接收。它通常由一个 IP 地址和一个端口号组成,能够在网络上唯一标识一个通信端点。
工作原理:
客户端和服务器:在网络通信中,Socket 通常分为客户端和服务器两部分。客户端通过 Socket 连接到服务器的 Socket,然后可以进行数据的发送和接收。
连接建立:在 TCP(传输控制协议)中,客户端和服务器之间的连接建立过程称为“三次握手”。这确保了通信双方的准备状态,并建立起可靠的连接。
数据传输:一旦连接建立,数据可以通过 Socket 进行传输。TCP 提供可靠的数据传输服务,而 UDP(用户数据报协议)提供无连接、不保证可靠性的传输服务。
效果演示
开启 3 个客户端, 并登录上去三个账户.
私聊消息
yaya 私发消息给 snow:
snow 接受到的消息:
snow 回复 yaya :
yaya 收到消息:
群聊消息
查看在线用户
群发
收到消息:
代码结构
代码
客户端
客户端连接服务端线程
import java.io.ObjectInputStream;
import java.net.Socket;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* @description: 客户端连接服务端线程
* @author: Snow
* @date: 2024/6/25
* **************************************************
* 修改记录(时间--修改人--修改说明):
*/
public class ClientConnectServerThread extends Thread {
/** 该线程持有Socket */
private Socket socket;
public ClientConnectServerThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
// 因为该线程需要在后台与服务端进行通信, 所以需要使用while循环
while ( true ) {
try {
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
// 如果服务器没有发生消息,线程会阻塞到这里
Message message = (Message) ois.readObject();
// 判断消息类型
if( MessageType.MESSAGE_RET_ONLINE_FRIEND.equals(message.getMessageType()) ){
// 去除消息 并显示
String[] split = message.getContent().split(" ");
System.out.println("===当前在线用户列表===");
for (int i = 0; i < split.length; i++) {
System.out.println("用户:" + split[i]);
}
} else if( MessageType.MESSAGE_COMMON_MSG.equals(message.getMessageType()) ){
// 接收消息并打印
String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss "));
System.out.println( time + message.getSender() + " 对 " + message.getGetter() + " 说: " + message.getContent());
}else if( MessageType.MESSAGE_TO_ALL_MSG.equals(message.getMessageType()) ){
// 接收消息并打印
String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss "));
System.out.println( time + message.getSender() + " 对所有在线用户说: " + message.getContent());
} else {
System.out.println("其他消息类型,暂时不处理!");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public Socket getSocket() {
return socket;
}
public void setSocket(Socket socket) {
this.socket = socket;
}
}
管理客户端连接到服务器线程
import java.util.concurrent.ConcurrentHashMap;
/**
* @description: 管理客户端连接到服务器线程
* @author: Snow
* @date: 2024/6/25
* **************************************************
* 修改记录(时间--修改人--修改说明):
*/
public class ManageClientConnectServerThread {
// 存放线程 key 为 uniqueId , value 为 ClientConnectServerThread
private static ConcurrentHashMap<String, ClientConnectServerThread> hm = new ConcurrentHashMap<>();
// 添加线程
public static void addClientConnectServerThread(String uniqueId, ClientConnectServerThread clientConnectServerThread) {
hm.put(uniqueId, clientConnectServerThread);
}
// 获取线程
public static ClientConnectServerThread getClientConnectServerThread(String uniqueId) {
return hm.get(uniqueId);
}
public static void removeClientConnectServerThread(String uniqueId) {
hm.remove(uniqueId);
}
}
消息服务
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.Socket;
/**
* @description: 消息服务
* @author: Snow
* @date: 2024/6/27
* **************************************************
* 修改记录(时间--修改人--修改说明):
*/
public class MessageService {
/** 发消息给某人 */
public void sendMessageToOne(String content, String getter, String sender) {
Message message = new Message();
message.setMessageType(MessageType.MESSAGE_COMMON_MSG);
message.setSender(sender);
message.setGetter(getter);
message.setContent(content);
try {
// 获取当前线程的 socket
Socket socket = ManageClientConnectServerThread.getClientConnectServerThread(sender).getSocket();
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(message);
} catch (Exception e) {
e.printStackTrace();
}
}
/** 群发消息 */
public void sendMessageToAll(String content, String sender) {
Message message = new Message();
message.setMessageType(MessageType.MESSAGE_TO_ALL_MSG);
message.setSender(sender);
message.setContent(content);
try {
ClientConnectServerThread clientConnectServerThread = ManageClientConnectServerThread.getClientConnectServerThread(sender);
Socket socket = clientConnectServerThread.getSocket();
new ObjectOutputStream(socket.getOutputStream()).writeObject(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端用户服务
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;
/**
* @description: 客户端用户服务
* @author: Snow
* @date: 2024/6/25
* **************************************************
* 修改记录(时间--修改人--修改说明):
*/
public class UserClientService {
private User user = new User();
private Socket socket;
public boolean checkUser(String userId, String password) {
user.setUserId(userId);
user.setPassword(password);
boolean b = false;
try {
// 服务端的地址和端口
socket = new Socket(InetAddress.getByName("127.0.0.1"), 8888);
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
// 发送user对象
oos.writeObject(user);
// 读取从 服务端来的 message 对象
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Message message = (Message)ois.readObject();
// 解析登录结果
if( MessageType.MESSAGE_LOGIN_OK.equals(message.getMessageType()) ){
// 创建和服务器端保存通信的线程
ClientConnectServerThread clientThread = new ClientConnectServerThread(socket);
// 启动线程
clientThread.start();
// 为了客户端的扩展, 将线程放入到集合中
ManageClientConnectServerThread.addClientConnectServerThread(userId, clientThread);
b = true;
} else {
// 关闭 socket
socket.close();
}
} catch (Exception e) {
e.printStackTrace();
}
return b;
}
/** 向服务端请求获取在线用户列表 */
public void getOnlineFriendList() {
// 发送一个 message 对象到服务端
Message message = new Message();
message.setMessageType(MessageType.MESSAGE_GET_ONLINE_FRIEND);
message.setSender(user.getUserId());
try {
// 获取当前线程的 socket
Socket socket = ManageClientConnectServerThread.getClientConnectServerThread(user.getUserId()).getSocket();
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(message);
} catch (Exception e){
e.printStackTrace();
}
}
/** 退出 */
public void exit() {
// 发送一个 message 对象到服务端
Message message = new Message();
message.setMessageType(MessageType.MESSAGE_CLIENT_EXIT);
message.setSender(user.getUserId());
try {
// 获取当前线程的 socket
Socket socket = ManageClientConnectServerThread.getClientConnectServerThread(user.getUserId()).getSocket();
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(message);
System.out.println(user.getUserId() + " 退出系统");
ManageClientConnectServerThread.removeClientConnectServerThread(user.getUserId());
socket.close(); // 关闭 socket
System.exit(0); // 结束进程
} catch (Exception e){
e.printStackTrace();
}
}
}
客户端面板
/**
* @description: 客户端面板
* @author: Snow
* @date: 2024/6/24
* **************************************************
* 修改记录(时间--修改人--修改说明):
*/
public class MainClientView {
public static void main(String[] args) {
MainClientView qqView = new MainClientView();
qqView.mainMenu();
System.out.println("谢谢使用");
}
/** 控制是否显示菜单 */
private boolean loop = true;
/** 接收用户的键盘输入 */
private String key = "";
/** 客户端用户服务 */
private UserClientService userClientService = new UserClientService();
private MessageService messageService = new MessageService();
private void mainMenu() {
while (loop){
System.out.println("=======================WELCOME================");
System.out.println("\t\t 1.登录");
System.out.println("\t\t 9.退出");
System.out.print("请输入你的选择...");
key = Utility.readString(1);
switch (key){
case "1":
System.out.println("请输入用户...");
String userId = Utility.readString(10);
System.out.println("请输入密码...");
String password = Utility.readString(10);
if ( userClientService.checkUser(userId, password) ){
System.out.println("============== WELCOME " + userId + " 登录成功 =============");
while (loop){
System.out.println("\n===========二级菜单(userId:" + userId + ")==============");
System.out.println("\t\t 1.显示在线用户列表");
System.out.println("\t\t 2.群发消息");
System.out.println("\t\t 3.私聊消息");
System.out.println("\t\t 4.发送文件");
System.out.println("\t\t 9.退出系统");
System.out.print("请输入你的选择...");
System.out.println();
key = Utility.readString(1);
switch (key){
case "1":
userClientService.getOnlineFriendList();
break;
case "2":
System.out.println("\n===========群发窗口==============");
System.out.println("\t\t 请输入要发送的内容...");
String s = Utility.readString(100);
messageService.sendMessageToAll(s, userId);
break;
case "3":
System.out.println("\n===========私聊窗口==============");
System.out.println("\t\t 1.请选择要私聊的心动对象...");
String getUserId = Utility.readString(10);
System.out.println("\t\t 2.请输入要发送的内容...");
String content = Utility.readString(100);
messageService.sendMessageToOne(content, getUserId, userId);
break;
case "4":
System.out.println("发送文件");
break;
case "9":
loop = false;
userClientService.exit();
break;
}
}
}else {
System.out.println("用户名或密码错误!");
}
break;
case "9":
loop = false;
break;
}
}
}
}
服务端
管理服务器连接的客户端线程
import java.util.concurrent.ConcurrentHashMap;
/**
* @description: 管理服务器连接的客户端线程
* @author: Snow
* @date: 2024/6/25
* **************************************************
* 修改记录(时间--修改人--修改说明):
*/
public class ManageClientsThread {
private static ConcurrentHashMap<String, ServerConnectClientThread> hm = new ConcurrentHashMap();
public static void addServerConnectClientThread(String uniqueId, ServerConnectClientThread serverConnectClientThread) {
hm.put(uniqueId, serverConnectClientThread);
}
public static ServerConnectClientThread getServerConnectClientThread(String uniqueId) {
return hm.get(uniqueId);
}
/** 返回在线唯一标识id集合 */
public static String getOnlineUser() {
StringBuilder sb = new StringBuilder();
for (String uniqueId : hm.keySet()) {
sb.append(uniqueId + " ");
}
return sb.toString();
}
/** 删除指定在线用户 */
public static void removeServerConnectClientThread(String uniqueId) {
hm.remove(uniqueId);
}
}
该服务端和客户端保持通信的线程
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
/**
* @description: 该服务端和客户端保持通信的线程
* @author: Snow
* @date: 2024/6/25
* **************************************************
* 修改记录(时间--修改人--修改说明):
*/
public class ServerConnectClientThread extends Thread {
private Socket socket;
private String userId;
public ServerConnectClientThread(Socket socket, String userId) {
this.socket = socket;
this.userId = userId;
}
public Socket getSocket() {
return socket;
}
public String getUserId() {
return userId;
}
public void run() {
// 发送和接收消息
while (true) {
System.out.println("服务端和客户端["+userId+"]保持通信,读取数据...");
try {
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Message messageRequest = (Message) ois.readObject();
if( MessageType.MESSAGE_GET_ONLINE_FRIEND.equals(messageRequest.getMessageType()) ){
System.out.println("客户端["+userId+"]请求在线用户列表");
String onlineUser = ManageClientsThread.getOnlineUser();
Message messageResponse = new Message();
messageResponse.setMessageType(MessageType.MESSAGE_RET_ONLINE_FRIEND);
messageResponse.setContent(onlineUser);
messageResponse.setGetter(messageRequest.getSender());
// 返回给客户端
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(messageResponse);
}else if( MessageType.MESSAGE_CLIENT_EXIT.equals(messageRequest.getMessageType()) ){
// 将该线程从集合中移除
ManageClientsThread.removeServerConnectClientThread(messageRequest.getSender());
// 关闭socket
socket.close();
System.out.println("客户端["+userId+"]退出了");
break;
} else if( MessageType.MESSAGE_COMMON_MSG.equals(messageRequest.getMessageType()) ){
// 私发消息
Message messageResponse = new Message();
messageResponse.setMessageType( MessageType.MESSAGE_COMMON_MSG );
// 获取 收取人的 socket
ServerConnectClientThread serverConnectClientThread = ManageClientsThread.getServerConnectClientThread(messageRequest.getGetter());
if( serverConnectClientThread == null ){
// 接受人不存在
messageResponse.setSender(messageRequest.getSender());
messageResponse.setGetter(messageRequest.getSender());
messageResponse.setContent("接受人 " + messageRequest.getGetter() + " 不存在");
new ObjectOutputStream(socket.getOutputStream()).writeObject(messageResponse);
}else{
messageResponse.setSender(messageRequest.getSender());
messageResponse.setGetter(messageRequest.getGetter());
messageResponse.setContent(messageRequest.getContent());
Socket socket = serverConnectClientThread.getSocket();
new ObjectOutputStream(socket.getOutputStream()).writeObject(messageResponse);
}
} else if( MessageType.MESSAGE_TO_ALL_MSG.equals(messageRequest.getMessageType()) ){
// 获取在线用户
String onlineUser = ManageClientsThread.getOnlineUser();
String[] onlineUsers = onlineUser.split(" ");
if( onlineUsers == null || onlineUsers.length == 0 ){
System.out.println("没有在线用户");
}
for (int i = 0; i < onlineUsers.length; i++) {
if( !onlineUsers[i].equals(messageRequest.getSender()) ){
// 获取 收取人的 socket
ServerConnectClientThread serverConnectClientThread = ManageClientsThread.getServerConnectClientThread(onlineUsers[i]);
Socket socket = serverConnectClientThread.getSocket();
new ObjectOutputStream(socket.getOutputStream()).writeObject(messageRequest);
}
}
} else {
System.out.println("先不处理");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
服务端Main
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @description: 服务端Main
* @author: Snow
* @date: 2024/6/25
* **************************************************
* 修改记录(时间--修改人--修改说明):
*/
public class MainServer {
public static void main(String[] args) {
new MainServer();
}
private ServerSocket serverSocket = null;
public MainServer() {
try {
serverSocket = new ServerSocket(8888);
// 当和客户端建立连接时,服务端会返回一个socket对象,继续监听
while ( true ) {
Socket socket = serverSocket.accept();
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
// 获取客户端传来的 user 验证其身份
User user = (User)ois.readObject();
Message message = new Message();
if( "123456".equals(user.getPassword()) ){
// 创建一个线程和客户端保持通信
ServerConnectClientThread serverThread = new ServerConnectClientThread(socket, user.getUserId());
// 启动线程
serverThread.start();
// 把该线程对象放入客户端集合中,进行管理
ManageClientsThread.addServerConnectClientThread(user.getUserId(), serverThread);
// 向客户端写回消息
message.setMessageType( MessageType.MESSAGE_LOGIN_OK );
oos.writeObject(message);
}else{
message.setMessageType( MessageType.MESSAGE_LOGIN_FAIL );
// 向客户端写回消息
oos.writeObject(message);
socket.close();
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭资源
if (serverSocket != null) {
try {
serverSocket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
实体对象
Message
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* @description:
* @author: Snow
* @date: 2024/6/24
* **************************************************
* 修改记录(时间--修改人--修改说明):
*/
public class Message implements Serializable {
private String sender;
private String getter;
private String content;
private LocalDateTime sendTime;
private String messageType;
public Message () {
}
public Message(String sender, String getter, String content, LocalDateTime sendTime, String messageType) {
this.sender = sender;
this.getter = getter;
this.content = content;
this.sendTime = sendTime;
this.messageType = messageType;
}
public String getSender() {
return sender;
}
public void setSender(String sender) {
this.sender = sender;
}
public String getGetter() {
return getter;
}
public void setGetter(String getter) {
this.getter = getter;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public LocalDateTime getSendTime() {
return sendTime;
}
public void setSendTime(LocalDateTime sendTime) {
this.sendTime = sendTime;
}
public String getMessageType() {
return messageType;
}
public void setMessageType(String messageType) {
this.messageType = messageType;
}
}
User
import java.io.Serializable;
/**
* @description:
* @author: Snow
* @date: 2024/6/24
* **************************************************
* 修改记录(时间--修改人--修改说明):
*/
public class User implements Serializable {
private String userId;
private String password;
public User() {
}
public User(String userId, String password) {
this.userId = userId;
this.password = password;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
MessageType
/**
* @description:
* @author: Snow
* @date: 2024/6/24
* **************************************************
* 修改记录(时间--修改人--修改说明):
*/
public interface MessageType {
/** 登录成功 */
String MESSAGE_LOGIN_OK = "1";
/** 登录失败 */
String MESSAGE_LOGIN_FAIL = "2";
/** 普通消息 */
String MESSAGE_COMMON_MSG = "3";
/** 群发消息 */
String MESSAGE_TO_ALL_MSG = "4";
/** 要求在线用户列表 */
String MESSAGE_GET_ONLINE_FRIEND = "5";
/** 返回在线用户列表 */
String MESSAGE_RET_ONLINE_FRIEND = "6";
/** 客户端请求退出 */
String MESSAGE_CLIENT_EXIT = "7";
}
工具类
import java.util.Scanner;
public class Utility {
private static Scanner scanner;
static {
scanner=new Scanner(System.in);
}
public Utility(){
}
public static char readMenuSelection(){
while (true){
String str=readKeyBoard(1,false);
char c=str.charAt(0);
if (c == '1' || c == '2' || c == '3' || c == '4' || c == '5') {
return c;
}
System.out.print("选择错误,请重新输入:");
}
}
public static char readChar(){
String str=readKeyBoard(1,false);
return str.charAt(0);
}
public static char readChar(char defaultValue){
String str=readKeyBoard(1, true);
return str.length()==0?defaultValue:str.charAt(0);
}
public static int readInt(){
while (true){
String str=readKeyBoard(2, false);
try {
int n=Integer.parseInt(str);
return n;
}catch (NumberFormatException var3){
System.out.println("数字输入错误,请重新输入:");
}
}
} public static int readInt(int defaultValue) {
while(true) {
String str = readKeyBoard(2, true);
if (str.equals("")) {
return defaultValue;
}
try {
int n = Integer.parseInt(str);
return n;
} catch (NumberFormatException var4) {
System.out.print("数字输入错误,请重新输入:");
}
}
}
public static String readString(int limit) {
return readKeyBoard(limit, false);
}
public static String readString(int limit, String defaultValue) {
String str = readKeyBoard(limit, true);
return str.equals("") ? defaultValue : str;
}
public static char readConfirmSelection(){
while (true){
String str=readKeyBoard(1,false).toUpperCase();
char c=str.charAt(0);
if(c=='Y'||c=='N'){
return c;
}
System.out.print("选择错误,请重新输入:");
}
}
private static String readKeyBoard(int limit, boolean blankReturn){
String line="";
while (scanner.hasNextLine()){
line=scanner.nextLine();
if(line.length()==0){
if(blankReturn){
return line;
}
}else {
if(line.length()>=1 && line.length()<=limit){
break;
}
System.out.println("输入长度(不大于" + limit + ")错误,请重新输入:");
}
}
return line;
}
}
结尾
以上就是简单的实现多人聊天的简陋版代码了;