@Override
	public void connect() throws IOException {
		if (closed)
			throw new IOException("Cannot re-connect to closed resource. Please create a new one.");
		client.setBroadcastListener((messageType, message) -> {
			switch (messageType) {
				case MessageConstants.ID_BROADCAST_LOADER:
					// New loader reported
					BroadcastClassloaderMessage loaderMessage = (BroadcastClassloaderMessage) message;
					ClassLoaderInfo loaderInfo = loaderMessage.getClassLoader();
					int loaderId = loaderInfo.getId();
					remoteLoaders.put(loaderId, loaderInfo);

					// If the loader was one that we saw classes for earlier, now we can
					// link those to this loader.
					Set<ClassData> pendingClasses = queuedClasses.remove(loaderId);
					if (pendingClasses != null) {
						for (ClassData pendingClass : pendingClasses) {
							handleReceiveClassData(pendingClass, null);
						}
					}
					break;
				case MessageConstants.ID_BROADCAST_CLASS:
					// New class, or update to existing class reported
					BroadcastClassMessage classMessage = (BroadcastClassMessage) message;
					ClassData data = classMessage.getData();
					handleReceiveClassData(data, null);
					break;
				default:
					// unknown broadcast packet
					break;
			}
		});

		// Try to connect
		logger.info("Connecting to remote JVM '{}' over port {}", virtualMachine.id(), client.getPort());
		try {
			client.connectThrowing();
		} catch (Exception ex) {
			throw new IOException("Could not connect to remote JVM " + virtualMachine.id() +
					" over " + client.getIp() + ":" + client.getPort(), ex);
		}

		// Request known classloaders
		logger.info("Sending initial request for classloaders & initial classes...");
		client.sendAsync(new RequestClassloadersMessage(), loaderReply -> {
			Collection<ClassLoaderInfo> classLoaders = loaderReply.getClassLoaders();
			logger.info("Received initial response for classloaders, count={}", classLoaders.size());
			for (ClassLoaderInfo loader : classLoaders) {
				if (loader.isBootstrap())
					continue;
				int loaderId = loader.getId();
				remoteLoaders.put(loaderId, loader);

				// Get/create bundle for loader
				ClassLoaderInfo loaderInfo = remoteLoaders.get(loaderId);
				RemoteJvmClassBundle bundle = remoteBundleMap
						.computeIfAbsent(loaderId, id -> createRemoteBundle(loaderInfo));

				// Request all classes from classloader
				logger.info("Sending initial request for class names in classloader {}...", loader.getName());
				client.sendAsync(new RequestClassloaderClassesMessage(loaderId), classesReply -> {
					Collection<String> classes = classesReply.getClasses();
					logger.info("Received initial response for class names in classloader {}, count={}",
							loader.getName(), classes.size());
					for (String className : classes) {
						// If class does not exist in bundle, then request it from remote server
						if (bundle.get(className) == null) {
							client.sendAsync(new RequestClassMessage(loaderId, className), reply -> {
								if (reply.hasData()) {
									ClassData data = reply.getData();
									handleReceiveClassData(data, bundle);
								}
							});
						}
					}
				});
			}
		});
	}
@param virtualMachine
		Instance of remote VM.
@param client
		Client to communicate to the remote VM.
recaf-core/src/main/java/software/coley/recaf/workspace/model/resource/AgentServerRemoteVmResource.java