简体   繁体   English

客户端/服务器对:临时端口

[英]Client-Server Pair: Ephemeral Port

I'm coding a client server pair in java and I would like the ports to be assigned at runtime by the system. 我正在用Java对客户端服务器对进行编码,我希望系统在运行时分配端口。 From the server's side this can be easily done through APIs but how does the client know which port the server is listening on? 从服务器方面,这可以通过API轻松完成,但是客户端如何知道服务器正在侦听哪个端口?

The client has no knowledge which ports the server is listening on, unless you specify it to the client 客户端不知道服务器正在侦听哪个端口,除非您将其指定给客户端
You may consider using other architectures, such as OGSI, or technologies in which you have a "service repository" which is known to all clients, so they can query it and get information on the service available and their ports. 您可以考虑使用其他体系结构,例如OGSI,或者使用其中具有所有客户机都知道的“服务存储库”的技术,以便他们可以查询它并获取有关可用服务及其端口的信息。
Another option is to let the system administrator define in your DNS organization SRV records for your service , and then the client code can invoke an SRV record request, and get information on the service, but this might be an overkill for your needs and requires involving your sys admin 另一个选择是让系统管理员在DNS组织中为您的服务定义SRV记录,然后客户端代码可以调用SRV记录请求,并获取有关服务的信息,但这对于您的需求而言可能是过大的,需要涉及您的系统管理员
If you're interested at the DNS option I mentioned you should check for the DnsSrvLocator class at oVirt open source. 如果您对我提到的DNS选项感兴趣,则应在oVirt开放源代码中检查DnsSrvLocator类。
At oVirt we use this in order to detect Ldap servers (when Active Directory is installed , the Sys Admin should configure entries for the LDAP server indicating the host name and the port it listens to, then , using this utility you can provide the information of the srv record (the domain and the protocol and get a list of servers available) 在oVirt,我们使用它来检测Ldap服务器(安装Active Directory时,系统管理员应为LDAP服务器配置条目,指示主机名和侦听的端口,然后,使用此实用程序,您可以提供以下信息: srv记录(域和协议,并获取可用服务器列表)
Here is its code - 这是它的代码-

/**
 *
 */
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);

}

Generally speaking, you'll need to use some other directory or discovery service to let the client know what port the server is on. 一般来说,您需要使用其他目录或发现服务来让客户端知道服务器所处的端口。 There is no widely available, general purpose mechanism to ask a server "what port is server process X listening on" 没有广泛使用的通用机制来询问服务器“服务器进程X正在监听的端口是什么”

If you're just looking to have flexibility in the port assignment (for example, to be able to use different ports in different installations, or to have multiple instances of your server running on different ports), consider using a simple environment variable or command line override to feed a port to the server. 如果您只是想灵活地分配端口(例如,能够在不同的安装中使用不同的端口,或者使服务器的多个实例在不同的端口上运行),请考虑使用简单的环境变量或命令行覆盖以将端口提供给服务器。 Eg, 例如,

./start-server -p 4000
./start-server -p 4001

Of course, you'll still need to make the client configurable to find the server, but at least you'll have predictability (and won't be relying on dynamically system-assigned ports). 当然,您仍然需要使客户端可配置以找到服务器,但是至少您将具有可预测性(并且不会依赖于系统动态分配的端口)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM