จะหาพอร์ตที่ใช้ได้อย่างไร


195

ฉันต้องการเริ่มต้นเซิร์ฟเวอร์ที่ฟังพอร์ต ฉันสามารถระบุพอร์ตได้อย่างชัดเจนและใช้งานได้ แต่ฉันต้องการค้นหาพอร์ตด้วยวิธีอัตโนมัติ ในส่วนนี้ฉันมีสองคำถาม

  1. ฉันควรค้นหาหมายเลขพอร์ตในช่วงใด (ฉันใช้พอร์ต 12345, 12346 และ 12347 และมันก็ใช้ได้)

  2. ฉันจะทราบได้อย่างไรว่าพอร์ตที่กำหนดไม่มีซอฟต์แวร์อื่นครอบครองอยู่?

คำตอบ:


280

หากคุณไม่คำนึงถึงพอร์ตที่ใช้ให้ระบุพอร์ต 0 ให้กับตัวสร้าง ServerSocketและจะฟังบนพอร์ตว่างใด ๆ

ServerSocket s = new ServerSocket(0);
System.out.println("listening on port: " + s.getLocalPort());

หากคุณต้องการใช้ชุดพอร์ตเฉพาะวิธีที่ง่ายที่สุดก็น่าจะทำซ้ำไปเรื่อย ๆ จนกว่าจะใช้งานได้ บางสิ่งเช่นนี้

public ServerSocket create(int[] ports) throws IOException {
    for (int port : ports) {
        try {
            return new ServerSocket(port);
        } catch (IOException ex) {
            continue; // try next port
        }
    }

    // if the program gets here, no port in the range was found
    throw new IOException("no free port found");
}

สามารถใช้งานได้เช่น:

try {
    ServerSocket s = create(new int[] { 3843, 4584, 4843 });
    System.out.println("listening on port: " + s.getLocalPort());
} catch (IOException ex) {
    System.err.println("no available ports");
}

2
เมื่อใช้ ServerSocket ใหม่ (0) ควรปิดอย่างระมัดระวัง! จากjavasourcecode.org/html/open-source/eclipse/eclipse-3.5.2/org/ …ปรับตัวเล็กน้อยในgist.github.com/3429822
vorburger

9
@ vorburger ทำในลักษณะที่มีแนวโน้มที่จะแข่งขันเงื่อนไข มันดีกว่าที่จะฟังบนซ็อกเก็ตเซิร์ฟเวอร์ทันทีแทนที่จะเปิดหาพอร์ตว่างปิดอีกครั้งแล้วเปิดอีกครั้งบนพอร์ตฟรี (ตามเวลาที่มีโอกาสเล็กน้อยที่ตอนนี้มีสิ่งอื่นที่กำลังฟังอยู่ พอร์ต.)
Graham Edgecombe

7
เห็นด้วย แต่ขึ้นอยู่กับกรณีการใช้งานที่แน่นอน: ในกรณีของฉันฉันต้องการค้นหาหมายเลขพอร์ตฟรีเพื่อส่งไปยัง API บางตัว (พูด Jetty Starter สำหรับการทดสอบ) - API ที่ต้องการต้องการหมายเลขซ็อกเก็ต - ไม่ใช่ ซ็อกเก็ตเซิร์ฟเวอร์ที่เปิด ดังนั้นมันขึ้นอยู่กับ
vorburger

4
@vorburger สมเหตุสมผล APIs จะยอมรับเป็นศูนย์เป็นหมายเลขพอร์ตที่ถูกต้องในการฟังและจากนั้นจะบอกคุณพอร์ตที่แท้จริงกำลังฟังอยู่ อย่างไรก็ตาม, มี API ที่สมเหตุสมผลไม่มากนัก: หลายโปรแกรมทดสอบเฉพาะสำหรับพอร์ต 0 ที่ถูกส่งผ่านและปฏิเสธ ( ssh -D 127.0.0.0:0 ...? Nope!) ซึ่งน่าผิดหวังจริงๆ เราต้องทำการแก้ไขไลบรารี / โปรแกรมจำนวนมากเพื่อให้สามารถใช้งานได้
Joker_vD

2
@vorburger เมื่อใช้พอร์ตใด ๆควรระมัดระวังในการปิด new ServerSocket(0)ไม่แตกต่างกันในส่วนนี้ ไม่มีสิ่งใดในลิงค์แรกของคุณเกี่ยวกับเรื่องนี้ ลิงก์ที่สองของคุณแสดงการปฏิบัติที่ไม่ดี เมื่อคุณสร้างมันขึ้นมาแล้วServerSocketคุณควรใช้มันอย่าปิดมันและลองใช้หมายเลขพอร์ตเดิมซ้ำ ทั้งหมดนี้เป็นการสูญเสียเวลาอย่างสมบูรณ์รวมทั้งยังมีความเสี่ยงต่อปัญหาระยะเวลา
มาร์ควิสแห่ง Lorne

57

ถ้าคุณส่ง 0 เป็นหมายเลขพอร์ตไปยังตัวสร้างของ ServerSocket มันจะจัดสรรพอร์ตให้คุณ


ใช่ฉันคิดว่ามันเป็นทางออกที่ดีที่สุด แต่ด้วยเหตุผลบางอย่างมันไม่ทำงานในกรณีของฉัน แต่ฉันยังต้องค้นหาสิ่งที่ผิดอย่างแน่นอน
โรมัน

1
@ Roman: ทำไมมันไม่ทำงาน อัปเดตคำถามของคุณเพื่อรวมสิ่งนี้ (หรือผู้คนจะแนะนำต่อไป) และอธิบายว่าทำไมโซลูชันนี้จึงล้มเหลวสำหรับคุณ
FrustratedWithFormsDesigner

2
ฉันจะหาพอร์ตนี้จากลูกค้าได้อย่างไร ;)
ed22

34

เริ่มต้นจาก Java 1.7 คุณสามารถใช้ลองกับทรัพยากรเช่นนี้:

  private Integer findRandomOpenPortOnAllLocalInterfaces() throws IOException {
    try (
        ServerSocket socket = new ServerSocket(0);
    ) {
      return socket.getLocalPort();

    }
  }

หากคุณต้องการค้นหาพอร์ตที่เปิดอยู่บนอินเทอร์เฟซที่เฉพาะเจาะจงให้ตรวจสอบเอกสารServerSocketสำหรับตัวสร้างทางเลือก

คำเตือน:รหัสใด ๆ ที่ใช้หมายเลขพอร์ตที่ส่งคืนโดยวิธีนี้จะขึ้นอยู่กับสภาพการแข่งขัน - กระบวนการ / เธรดอื่นอาจผูกเข้ากับพอร์ตเดียวกันทันทีหลังจากเราปิดอินสแตนซ์ ServerSocket


1
SO_REUSEADDRนี้อาจจะไม่ทำงานถ้าคุณไม่ได้ตั้งค่า และมีสภาพการแข่งขัน แต่มันก็ยากที่จะแก้ไข
David Ehrmann

สิ่งนี้อาจไม่ทำงานหากคุณลองเปิดอีกอัน ServerSocketด้วยหมายเลขพอร์ตเดียวกันเนื่องจากอาจใช้งานได้ในขณะเดียวกัน ทำไมคุณไม่เพียงแค่คืนค่าจริงServerSocketแทนที่จะปิดมันยังคงเป็นปริศนา
มาร์ควิสแห่ง Lorne

5
@EJP บางครั้งโค้ดของบุคคลที่สามยอมรับพอร์ต แต่ไม่ใช่ซ็อกเก็ตเอง
Captain Man

@CaptainMan แน่นอน แต่ในกรณีเหล่านั้นพอร์ตจะถูกสันนิษฐานไว้ล่วงหน้าว่าจะถูกจัดสรรล่วงหน้า พอร์ตที่จัดสรรแบบไดนามิกจะต้องให้กับลูกค้าโดยกลไกอื่น ๆ เช่นบริการการตั้งชื่อหรือออกอากาศหรือ mulitcast คำถามทั้งหมดที่นี่มีรูปแบบไม่ถูกต้อง
มาร์ควิสแห่ง Lorne

@ user207421 คุณไม่สามารถเปลี่ยนรหัสบุคคลที่สามเพื่อยอมรับServerSocketแทนการintพอร์ต
Captain Man

30

ตามที่วิกิพีเดียที่คุณควรใช้พอร์ต49152ไป65535ถ้าคุณไม่จำเป็นต้องมี 'ที่รู้จักกันดี' พอร์ต

AFAIK วิธีเดียวที่จะกำหนดว่าพอร์ตใดที่ใช้งานอยู่คือพยายามเปิด


6
+1 เนื่องจาก Wikipedia ไม่ได้เป็นแหล่งของความจริง / ข้อเท็จจริงที่แน่นอนเสมอไปฉันจึงคิดว่าอาจเป็นประโยชน์ที่จะต้องทราบว่าการอ้างอิงสำหรับหน้า Wikipedia นั้นมาจาก "Internet Assigned Numbers Authority (IANA)" [ชื่อบริการและหมายเลขพอร์ตโปรโตคอลการขนส่ง รีจิสทรี ( iana.org/assignments/service-names-port-numbers/ … "หน้าอ้างอิงจากRFC 6335 - ส่วนที่ 6 (เช่น" Solid "อ้างอิงในกรณีนี้! :))
OzgurH

25

หากคุณต้องการใช้งานในช่วง :

public int nextFreePort(int from, int to) {
    int port = randPort(from, to);
    while (true) {
        if (isLocalPortFree(port)) {
            return port;
        } else {
            port = ThreadLocalRandom.current().nextInt(from, to);
        }
    }
}

private boolean isLocalPortFree(int port) {
    try {
        new ServerSocket(port).close();
        return true;
    } catch (IOException e) {
        return false;
    }
}

สงสัยว่าจะตรวจสอบพอร์ตได้อย่างไรจากนั้นยกเลิกการเชื่อมต่อ ขอบคุณ SergeyB!
Vlad Ilie

5
หากทุกพอร์ตในช่วง [จากถึงถึง) ใช้งานอยู่รหัสนี้จะวนซ้ำไม่สิ้นสุด (หรืออย่างน้อยก็จนกว่าพอร์ตใดพอร์ตหนึ่งจะว่าง) หากคุณทำการสแกนตามลำดับแทนที่จะเลือกพอร์ตในช่วงที่สุ่มคุณสามารถหลีกเลี่ยงความเป็นไปได้นี้ (เพียงโยนข้อยกเว้นเมื่อคุณไปถึงจุดสิ้นสุดของช่วงโดยไม่ต้องค้นหาพอร์ตว่าง) หากคุณต้องการเลือกพอร์ตแบบสุ่มจริง ๆ คุณต้องมีชุดเพื่อติดตามพอร์ตที่คุณได้ลองมาแล้วเพิ่มข้อผิดพลาดเมื่อขนาดของชุดนั้นเท่ากับ - จาก
Simon Kissane

พอร์ต 0 นั้นง่ายกว่ามากและไม่จำเป็นต้องสร้างสองServerSocketsกรณีที่ประสบความสำเร็จและไม่ประสบปัญหาเกี่ยวกับหน้าต่างเวลาของรหัสนี้
มาร์ควิสแห่งลอร์น

21

สำหรับผู้ที่อยากรู้อยากเห็นภายใต้ฝากระโปรงSpringUtils.findAvailableTcpPort()จะทำเหมือนกับที่แนะนำในคำตอบอื่น ๆ : new ServerSocket(port)เลือกพอร์ตแล้วลอง
Alexey Berezkin

11

Eclipse SDK มีคลาสSocketUtilซึ่งเป็นสิ่งที่คุณต้องการ คุณอาจจะมีลักษณะเป็นคอมไพล์รหัสที่มา


2
ดูรหัสของ Eclipse พวกเขาทำสิ่งเดียวกันกับคำตอบของ Graham Edgecombe
KevinL


6

สิ่งนี้ใช้ได้กับฉันใน Java 6

    ServerSocket serverSocket = new ServerSocket(0);
    System.out.println("listening on port " + serverSocket.getLocalPort());

6

ฉันเพิ่งเปิดตัวห้องสมุดขนาดเล็กสำหรับทำเช่นนั้นโดยการทดสอบในใจ Maven พึ่งพาคือ:

<dependency>
    <groupId>me.alexpanov</groupId>
    <artifactId>free-port-finder</artifactId>
    <version>1.0</version>
</dependency>

เมื่อติดตั้งแล้วจะสามารถรับหมายเลขพอร์ตได้ฟรีผ่าน:

int port = FreePortFinder.findFreeLocalPort();

4

หากเซิร์ฟเวอร์ของคุณเริ่มทำงานแสดงว่าไม่ได้ใช้ซ็อกเก็ตนั้น

แก้ไข

สิ่งที่ต้องการ:

ServerSocket s = null ;

try { 
    s = new ServerSocket( 0 ); 
} catch( IOException ioe ){
   for( int i = START; i < END ; i++ ) try {
        s = new ServerSocket( i );
    } catch( IOException ioe ){}
}
// At this point if s is null we are helpless
if( s == null ) {
    throw new IOException(
       Strings.format("Unable to open server in port range(%d-%d)",START,END));
}

ไม่ทราบว่าใครโหวตคุณ แต่ฉันโหวตให้คุณสำรอง คุณสามารถตั้งค่าฟังก์ชั่นวนซ้ำเพื่อพยายามติดตั้ง ServerSocket และถ้าคุณได้รับ IOException (หรืออะไรก็ตาม) คุณลองอีกครั้งจนกว่าจะได้รับพอร์ตสำเร็จ
jonescb

ฉันคิดว่ามันเป็นการดีกว่าที่จะตรวจสอบว่าพอร์ตนั้นพร้อมใช้งานหรือไม่แล้วลองเริ่มฟังพอร์ตนี้ ดูเหมือนจะไม่สง่างามที่จะเริ่มสิ่งใดสิ่งหนึ่งแล้วพบว่ามีปัญหาบางอย่างแล้วลองแก้ไขปัญหาเหล่านี้
โรมัน

2
@ โรมันดีใช่ว่าจะดีกว่ายกเว้นความจริงไม่มีวิธีการที่จะรู้ว่าพอร์ตที่มีอยู่ หากnew ServerSocket(0)ไม่ได้ผลกับทางเลือกของคุณก็คือ ฉันคิดว่ามี 85% ของความเป็นไปได้ที่คุณจะใช้คำแนะนำของฉัน
OscarRyz

รหัสนี้จะคงไว้ซึ่งการเปิดและการรั่วไหลServerSocketsตลอดไปและยังคงรักษารหัสสุดท้ายที่ประสบความสำเร็จเท่านั้น มันต้องการbreakคำสั่ง
มาร์ควิสแห่ง Lorne

4

หากคุณต้องการสร้างเซิร์ฟเวอร์ของคุณเองโดยใช้ a ServerSocketคุณสามารถให้มันเลือกพอร์ตฟรีให้กับคุณ:

  ServerSocket serverSocket = new ServerSocket(0);
  int port = serverSocket.getLocalPort();

การใช้งานเซิร์ฟเวอร์อื่น ๆ มักจะมีการสนับสนุนที่คล้ายกัน ตัวอย่างเช่นท่าเทียบเรือจะเลือกพอร์ตฟรีเว้นแต่คุณจะตั้งค่าไว้อย่างชัดเจน:

  Server server = new Server();
  ServerConnector connector = new ServerConnector(server);
  // don't call: connector.setPort(port);
  server.addConnector(connector);
  server.start();
  int port = connector.getLocalPort();

3

อาจไม่ช่วยอะไรคุณได้มากนัก แต่ในเครื่อง (Ubuntu) ฉันมีไฟล์ / etc / services ซึ่งอย่างน้อยมีการใช้พอร์ต / สำรองโดยแอพบางตัว นี่เป็นพอร์ตมาตรฐานสำหรับแอพเหล่านั้น

ไม่รับประกันว่าสิ่งเหล่านี้จะทำงานเพียงพอร์ตเริ่มต้นแอพเหล่านี้ใช้ (ดังนั้นคุณไม่ควรใช้ถ้าเป็นไปได้)

มีพอร์ตที่กำหนดไว้มากกว่า 500 พอร์ตเล็กน้อยประมาณครึ่ง UDP และครึ่ง TCP

ไฟล์ที่จะทำโดยใช้ข้อมูลจาก IANA ดูIANA หมายเลขพอร์ตที่ได้รับมอบหมาย


มีรายการที่คล้ายกัน (และสมบูรณ์มากขึ้น IIRC) ที่มาเป็นส่วนหนึ่งของ nmap +1
rmeador

3

หากคุณใช้ Spring คำตอบของMichail Nikolaevนั้นเป็นคำตอบที่ง่ายและสะอาดที่สุดและ IMO ควรได้รับการสนับสนุน เพื่อความสะดวกฉันจะเพิ่มตัวอย่างแบบอินไลน์โดยใช้วิธี Springframwework SocketUtils .findAvailableTcpPort () :

int randomAvailablePort = SocketUtils.findAvailableTcpPort();

มันง่ายอย่างนั้นแค่บรรทัดเดียว :) แน่นอนชั้น Utils เสนอวิธีการที่น่าสนใจอื่น ๆ อีกมากมายฉันขอแนะนำให้ดูเอกสาร


1

การใช้คลาส 'ServerSocket' เราสามารถระบุได้ว่าพอร์ตที่กำหนดนั้นใช้งานอยู่หรือว่างเปล่า ServerSocket ให้ตัวสร้างที่ใช้จำนวนเต็ม (ซึ่งเป็นหมายเลขพอร์ต) เป็นอาร์กิวเมนต์และซ็อกเก็ตเซิร์ฟเวอร์เริ่มต้นบนพอร์ต ถ้า ServerSocket มีข้อยกเว้น IO ใด ๆ ดังนั้นเราสามารถสันนิษฐานได้ว่าพอร์ตนี้มีการใช้งานแล้ว

ตัวอย่างต่อไปนี้ใช้เพื่อรับพอร์ตที่มีอยู่ทั้งหมด

for (int port = 1; port < 65535; port++) {
         try {
                  ServerSocket socket = new ServerSocket(port);
                  socket.close();
                  availablePorts.add(port);
         } catch (IOException e) {

         }
}

การอ้างอิงการเชื่อมโยง


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.