我正在用java编写一个客户端服务器对,我希望系统在运行时分配端口。在服务器端,这可以通过 API 轻松完成,但客户端如何知道服务器正在侦听哪个端口?
2 回答
客户端不知道服务器正在侦听哪些端口,除非您将其指定给客户端
您可以考虑使用其他架构,例如 OGSI,或者您拥有所有客户端都知道的“服务存储库”的技术,因此他们可以查询它并获取有关可用服务及其端口的信息。
另一种选择是让系统管理员在您的 DNS 组织中为您的服务定义 SRV 记录,然后客户端代码可以调用 SRV 记录请求,并获取有关服务的信息,但这对于您的需求可能有点过分,需要涉及你的系统管理员
如果你对我提到的 DNS 选项感兴趣,你应该检查oVirt开源的 DnsSrvLocator 类。
在 oVirt,我们使用它来检测 Ldap 服务器(安装 Active Directory 时,系统管理员应该为 LDAP 服务器配置条目,指示主机名和它侦听的端口,然后,使用此实用程序,您可以提供以下信息srv 记录(域和协议并获取可用服务器列表)
这是它的代码 -
/**
*
*/
package org.ovirt.engine.core.dns;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.InputMismatchException;
import java.util.Random;
import java.util.Scanner;
import java.util.regex.Pattern;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.spi.NamingManager;
import org.ovirt.engine.core.utils.log.Log;
import org.ovirt.engine.core.utils.log.LogFactory;
/**
* Utility class to query DNS SRV records, and return results according to the priority/weights algorithm as specified
* in RFC 2782
*
*/
public class DnsSRVLocator {
private static final String DNS_QUERY_PREFIX = "dns:///";
private static final String SRV_RECORD = "SRV";
private static Pattern SPACE_PATTERN = Pattern.compile(" ");
private static SrvRecord invalidRecord = new SrvRecord(false, false, 0, 0, 0, "");
private int numberOfValidRecords = 0;
private Random random = new Random(System.currentTimeMillis());
public static final String TCP = "_tcp";
public static final String UDP = "_udp";
/**
* Holds information on a retrieved SRV record
*
*/
public static class SrvRecord implements Comparable<SrvRecord> {
private boolean valid;
private int weight;
private int priority;
private int sum;
private String address;
private boolean used;
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("valid: ").append(valid).append(" sum: ").append(sum).append("priority: ").append(priority)
.append(" weight: ").append(weight).append(" hostport: ").append(address);
return sb.toString();
}
public SrvRecord(int priority, int weight, String hostPort) {
this(true, false, 0, priority, weight, hostPort);
}
public SrvRecord(boolean valid, boolean used, int sum, int priority, int weight, String address) {
this.valid = valid;
this.used = used;
this.sum = sum;
this.priority = priority;
this.weight = weight;
this.address = address;
}
public boolean isValid() {
return valid;
}
public void setValid(boolean isValid) {
this.valid = isValid;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
public int getPriority() {
return priority;
}
public void setPriority(int priority) {
this.priority = priority;
}
public int getSum() {
return sum;
}
public void setSum(int sum) {
this.sum = sum;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public boolean isUsed() {
return used;
}
public void setUsed(boolean isUsed) {
this.used = isUsed;
}
@Override
public int compareTo(SrvRecord other) {
// Sort in ascending order where invalid (non parsable) records are
// last
// Records with lower priority value come first
// For a group of records with same priority, records with weight 0
// come first
if (valid && !other.valid) {
return -1;
}
if (!valid && other.valid) {
return 1;
}
if (priority < other.priority) {
return -1;
}
if (priority > other.priority) {
return 1;
}
if (weight == 0 && other.weight != 0) {
return -1;
}
if (weight != 0) {
return 1;
}
return 0;
}
}
public static class DnsSRVResult {
private int numOfValidAddresses;
private String[] addresses;
public DnsSRVResult(int numOfValidAddresses, String[] addresses) {
this.numOfValidAddresses = numOfValidAddresses;
this.addresses = addresses;
}
public int getNumOfValidAddresses() {
return numOfValidAddresses;
}
public String[] getAddresses() {
return addresses;
}
}
public DnsSRVResult getService(String service, String protocol, String domain) throws Exception {
StringBuilder dnsQuery = new StringBuilder();
dnsQuery.append(service).append(".").append(protocol).append(".").append(domain);
try {
return getService(dnsQuery.toString());
} catch (Exception ex) {
log.errorFormat("Error: could not find DNS SRV record name: {0}.{1}.{2}.\nException message is: {3}\n" +
"Possible causes: missing DNS entries in the DNS server or DNS resolving" +
" issues from engine-core machine.\nPlease Ensure correct DNS entries exist in the DNS server" +
" and ensure the DNS server is reachable from the engine-core machine.",
service,
protocol,
domain,
ex.getMessage());
throw ex;
}
}
private DnsSRVResult getService(String dnsUrl) throws NamingException {
Context ctx = NamingManager.getURLContext("dns", new Hashtable(0));
if (!(ctx instanceof DirContext)) {
return null; // cannot create a DNS context
}
StringBuilder fullDnsURL = new StringBuilder(DNS_QUERY_PREFIX);
fullDnsURL.append(dnsUrl);
Attributes attrs = ((DirContext) ctx).getAttributes(fullDnsURL.toString(), new String[] { SRV_RECORD });
if (attrs == null) {
return null;
}
Attribute attr = attrs.get(SRV_RECORD);
if (attr == null) {
return null;
}
int numOfRecords = attr.size();
String[] records = new String[numOfRecords];
for (int counter = 0; counter < numOfRecords; counter++) {
records[counter] = (String) attr.get(counter);
}
return getSRVResult(records);
}
public DnsSRVResult getSRVResult(String[] recordsList) {
int numOfRecords = recordsList.length;
if (recordsList == null || numOfRecords == 0) {
return null;
}
// Read records as retrieved from DNS
SrvRecord[] records = new SrvRecord[numOfRecords];
int counter = 0;
for (String recordStr : recordsList) {
SrvRecord srvRecord = parseSrvRecord(recordStr);
records[counter++] = srvRecord;
}
// Sort the records
Arrays.sort(records);
int priority = -1;
int lastPriorityIndex = -1;
int priorityIndex = -1;
int startPriorityIndex = -1;
String[] addresses = new String[numOfRecords];
// Total number of service addresses
int numOfAddreses = -1;
// Iterates over the records, and calculates for each
// priority the index of the first SRV record that contains
// the index of last SRVV record that contains the priority
// For each group of records with same priorities, gets a list of services
for (SrvRecord record : records) {
if (!record.isValid()) {
break;
}
lastPriorityIndex = priorityIndex;
priorityIndex++;
int currentPriority = record.getPriority();
if (currentPriority != priority) {
if (lastPriorityIndex != -1) {
// This means that this is the end of a group of records
// with same
// priority - get their service addresses
numOfAddreses = fillServiceAddress(records, startPriorityIndex, lastPriorityIndex, addresses,
numOfAddreses);
}
startPriorityIndex = priorityIndex;
priority = currentPriority;
}
lastPriorityIndex = priorityIndex;
}
numOfAddreses = fillServiceAddress(records, startPriorityIndex, lastPriorityIndex, addresses, numOfAddreses);
// numOfAddresses points to the last index of valid address in the
// addresses array
// Increase it by 1 in order to truely reflect the number of valid
// addresses
return new DnsSRVResult(numOfAddreses + 1, addresses);
}
private int fillServiceAddress(SrvRecord[] records,
int startPriorityIndex,
int lastPriorityIndex,
String[] addresses,
int numOfAddressess) {
// Run the following algorithm for determining the order of service entries for a
// group of SRV records with same
// priority:
// 1. For each SRV record calculate its sum based on the sum of weights
// of its weight and
// the weight of all preceding SRV records
// 2. Select a random value between 0 and the sum (inclusive)
// 3. Iterate over the group until a record with sum that is greater or
// above
// to the generated random value is encountered
// 4. This will be the next select SRV record - make it not used for
// next round of the algorithm
int numOfRepetitions = (lastPriorityIndex - startPriorityIndex) + 1;
int totalSum = 0;
for (int counter = 0; counter < numOfRepetitions; counter++) {
for (int index = startPriorityIndex; index <= lastPriorityIndex; index++) {
if (!records[index].isUsed()) {
totalSum += records[index].getWeight();
records[index].setSum(totalSum);
}
}
int randResult = random.nextInt(totalSum + 1);
for (int index = startPriorityIndex; index <= lastPriorityIndex; index++) {
boolean found = false;
if (!found && !records[index].isUsed()) {
if (records[index].getSum() >= randResult) {
records[index].setUsed(true);
addresses[++numOfAddressess] = records[index].getAddress();
found = true;
}
}
}
}
return numOfAddressess;
}
private SrvRecord parseSrvRecord(String recordStr) {
// SRV record looks like: PRIORITY WEIGHT PORT HOST
Scanner s = new Scanner(recordStr).useDelimiter(SPACE_PATTERN);
try {
int priority = s.nextInt();
int weight = s.nextInt();
String port = s.next();
String host = s.next();
StringBuilder sb = new StringBuilder(host);
sb.append(":").append(port);
numberOfValidRecords++;
return new SrvRecord(priority, weight, sb.toString());
} catch (InputMismatchException ex) {
log.errorFormat("the record {0} has invalid format", recordStr);
// In case there is a parsing error, the invalid record constant is
// returned
return invalidRecord;
}
}
public URI constructURI(String protocol, String address) throws URISyntaxException {
String[] parts = address.split("\\.:");
if (parts.length != 2) {
throw new IllegalArgumentException("the address in SRV record should contain host and port");
}
StringBuilder uriSB = new StringBuilder(protocol);
uriSB.append("://").append(parts[0]).append(":").append(parts[1]);
return new URI(uriSB.toString());
}
private static Log log = LogFactory.getLog(DnsSRVLocator.class);
}
一般来说,您需要使用其他一些目录或发现服务来让客户端知道服务器在哪个端口上。没有广泛可用的通用机制来询问服务器“服务器进程 X 正在侦听哪个端口”
如果您只是希望端口分配具有灵活性(例如,能够在不同的安装中使用不同的端口,或者让您的服务器的多个实例在不同的端口上运行),请考虑使用简单的环境变量或命令行覆盖以将端口提供给服务器。例如,
./start-server -p 4000
./start-server -p 4001
当然,您仍然需要使客户端可配置以查找服务器,但至少您将具有可预测性(并且不会依赖于动态系统分配的端口)。