ฉันจะขัดจังหวะวิธี ServerSocket accept () ได้อย่างไร?


143

ในเธรดหลักของฉันฉันมีการwhile(listening)วนรอบซึ่งเรียกaccept()บนวัตถุ ServerSocket ของฉันจากนั้นเริ่มเธรดไคลเอนต์ใหม่และเพิ่มลงในคอลเลกชันเมื่อยอมรับไคลเอนต์ใหม่

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

อย่างไรก็ตามการaccept()โทรในwhile(listening)บล็อกวนซ้ำและดูเหมือนจะไม่มีทางขัดขวางดังนั้นจึงไม่สามารถตรวจสอบเงื่อนไขในขณะที่อีกครั้งและโปรแกรมไม่สามารถออกได้!

มีวิธีที่ดีกว่าในการทำเช่นนี้? หรือวิธีการขัดจังหวะวิธีการบล็อก?



สำหรับ pepole ที่ต้องการรอเวลา x แล้วให้ใช้ setSoTimeout ()
Abdullah Orabi

คำตอบ:


151

คุณสามารถโทรclose()จากหัวข้ออื่นและโทรจะโยนaccept()SocketException


2
ขอบคุณชัดเจนมากเลยไม่ได้เกิดขึ้นกับฉัน! ฉันโทรมาใกล้ () หลังจากฉันออกจากลูป
lukeo05

4
แปลกที่ไม่มีข้อมูลนี้ใน docs: download.oracle.com/javase/6/docs/api/java/net/…วิธีการไม่ได้ถูกทำเครื่องหมายว่าเป็นการขว้าง SocketException มีการกล่าวถึงที่นี่เท่านั้นdownload.oracle.com/javase/1.4.2/docs/api/java/net/…
Vladislav Rastrusny

1
โทรไม่เป็นไรclose()ฉันหมายความว่ามันจะทำให้เกิดข้อยกเว้นในการทำมันดังนั้นจะมีวิธีอื่นอีกไหม (ซึ่งไม่ได้ยกเว้นข้อยกเว้นไม่ได้อิงตามการหมดเวลา) เพื่อหยุดฟังคำขอ?
Kushal

3
และจะทำอย่างไรถ้าการเชื่อมต่อได้รับการยอมรับแล้วและเธรดกำลังรอข้อมูลบางอย่าง: ในขณะที่ ((s = in.readLine ())! = null)?
Alex Fedulov

1
@AlexFedulov ปิดเครื่องซ็อกเก็ตสำหรับการป้อนข้อมูล readLine()จะส่งคืน null และการดำเนินการปิดตามปกติที่เธรดควรมีอยู่แล้วจะเกิดขึ้น
มาร์ควิสแห่ง Lorne

31

ตั้งค่าการหมดเวลาaccept()แล้วการโทรจะหมดเวลาการบล็อกหลังจากเวลาที่ระบุ:

http://docs.oracle.com/javase/7/docs/api/java/net/SocketOptions.html#SO_TIMEOUT

ตั้งค่าการหมดเวลาสำหรับการSocketดำเนินการบล็อก:

ServerSocket.accept();
SocketInputStream.read();
DatagramSocket.receive();

ต้องตั้งค่าตัวเลือกก่อนเข้าสู่การดำเนินการบล็อกเพื่อให้มีผล หากการหมดเวลาหมดอายุและการดำเนินการจะปิดกั้นต่อไปjava.io.InterruptedIOExceptionจะเพิ่มขึ้น Socketไม่ได้ปิดในกรณีนี้



4

คุณสามารถสร้างซ็อกเก็ต "void" เพื่อหยุดเซิร์ฟเวอร์ocket.accept ()

ฝั่งเซิร์ฟเวอร์

private static final byte END_WAITING = 66;
private static final byte CONNECT_REQUEST = 1;

while (true) {
      Socket clientSock = serverSocket.accept();
      int code = clientSock.getInputStream().read();
      if (code == END_WAITING
           /*&& clientSock.getInetAddress().getHostAddress().equals(myIp)*/) {
             // End waiting clients code detected
             break;
       } else if (code == CONNECT_REQUEST) { // other action
           // ...
       }
  }

วิธีการหยุดวงจรเซิร์ฟเวอร์

void acceptClients() {
     try {
          Socket s = new Socket(myIp, PORT);
          s.getOutputStream().write(END_WAITING);
          s.getOutputStream().flush();
          s.close();
     } catch (IOException e) {
     }
}

4

เหตุผลServerSocket.close()โยนข้อยกเว้น เป็นเพราะคุณมีoutputstreamหรือinputstream แนบกับซ็อกเก็ตที่ คุณสามารถหลีกเลี่ยงข้อยกเว้นนี้ได้อย่างปลอดภัยโดยการปิดอินพุตและเอาต์พุตสตรีมเป็นครั้งแรก ServerSocketแล้วลองปิด นี่คือตัวอย่าง:

void closeServer() throws IOException {
  try {
    if (outputstream != null)
      outputstream.close();
    if (inputstream != null)
      inputstream.close();
  } catch (IOException e1) {
    e1.printStackTrace();
  }
  if (!serversock.isClosed())
    serversock.close();
  }
}

คุณสามารถเรียกวิธีนี้เพื่อปิดซ็อกเก็ตใดก็ได้จากทุกที่โดยไม่มีข้อยกเว้น


7
กระแสเข้าและเอาท์พุทไม่ได้แนบกับServerSocketแต่ไปยังSocketและเรากำลังพูดถึงการปิดServerSocketไม่ใช่Socketเพื่อให้ServerSocketสามารถปิดได้โดยไม่ต้องปิดSocketกระแสของ
icza


1

ตกลงฉันทำงานนี้ในแบบที่ตอบคำถามของ OP โดยตรง

โปรดอ่านคำตอบสั้น ๆ ที่ผ่านมาสำหรับตัวอย่างของการใช้วิธีนี้

คำตอบสั้น ๆ :

ServerSocket myServer;
Socket clientSocket;

  try {    
      myServer = new ServerSocket(port)
      myServer.setSoTimeout(2000); 
      //YOU MUST DO THIS ANYTIME TO ASSIGN new ServerSocket() to myServer‼!
      clientSocket = myServer.accept();
      //In this case, after 2 seconds the below interruption will be thrown
  }

  catch (java.io.InterruptedIOException e) {
      /*  This is where you handle the timeout. THIS WILL NOT stop
      the running of your code unless you issue a break; so you
      can do whatever you need to do here to handle whatever you
      want to happen when the timeout occurs.
      */
}

ตัวอย่างโลกแห่งความจริง:

ในตัวอย่างนี้ฉันมี ServerSocket รอการเชื่อมต่อภายในเธรด เมื่อฉันปิดแอพฉันต้องการปิดเธรด (โดยเฉพาะอย่างยิ่งซ็อกเก็ต) ในลักษณะที่สะอาดก่อนที่จะให้แอปปิดดังนั้นฉันจึงใช้. setSoTimeout () บน ServerSocket จากนั้นฉันใช้อินเทอร์รัปต์ที่โยน หลังจากหมดเวลาเพื่อตรวจสอบและดูว่าผู้ปกครองพยายามที่จะปิดกระทู้ ถ้าเป็นเช่นนั้นฉันจะปิดซ็อกเก็ตจากนั้นตั้งค่าสถานะที่ระบุว่าด้ายเสร็จแล้วฉันแยกออกจากวงวนเธรดที่ส่งกลับเป็นโมฆะ

package MyServer;

import javafx.concurrent.Task;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;

import javafx.concurrent.Task;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;

public class Server {

public Server (int port) {this.port = port;}

private boolean      threadDone        = false;
private boolean      threadInterrupted = false;
private boolean      threadRunning     = false;
private ServerSocket myServer          = null;
private Socket       clientSocket      = null;
private Thread       serverThread      = null;;
private int          port;
private static final int SO_TIMEOUT    = 5000; //5 seconds

public void startServer() {
    if (!threadRunning) {
        serverThread = new Thread(thisServerTask);
        serverThread.setDaemon(true);
        serverThread.start();
    }
}

public void stopServer() {
    if (threadRunning) {
        threadInterrupted = true;
        while (!threadDone) {
            //We are just waiting for the timeout to exception happen
        }
        if (threadDone) {threadRunning = false;}
    }
}

public boolean isRunning() {return threadRunning;}


private Task<Void> thisServerTask = new Task <Void>() {
    @Override public Void call() throws InterruptedException {

        threadRunning = true;
        try {
            myServer = new ServerSocket(port);
            myServer.setSoTimeout(SO_TIMEOUT);
            clientSocket = new Socket();
        } catch (IOException e) {
            e.printStackTrace();
        }
        while(true) {
            try {
                clientSocket = myServer.accept();
            }
            catch (java.io.InterruptedIOException e) {
                if (threadInterrupted) {
                    try { clientSocket.close(); } //This is the clean exit I'm after.
                    catch (IOException e1) { e1.printStackTrace(); }
                    threadDone = true;
                    break;
                }
            } catch (SocketException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
};

}

จากนั้นในคลาส Controller ของฉัน ... (ฉันจะแสดงเฉพาะรหัสที่เกี่ยวข้องนวดมันเป็นรหัสของคุณเองตามต้องการ)

public class Controller {

    Server server = null;
    private static final int port = 10000;

    private void stopTheServer() {
        server.stopServer();
        while (server.isRunning() {
        //We just wait for the server service to stop.
        }
    }

    @FXML private void initialize() {
        Platform.runLater(()-> {
            server = new Server(port);
            server.startServer();
            Stage stage = (Stage) serverStatusLabel.getScene().getWindow();
            stage.setOnCloseRequest(event->stopTheServer());
        });
    }

}

ฉันหวังว่านี่จะช่วยให้ใครบางคนเดินไปตามถนน


0

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


มันไม่สมเหตุสมผล ... เมื่อคุณมีรหัสเช่น socket = serverSocket.accept นี้ (); ในขณะนั้นการเริ่มต้นการบล็อกและลูปไม่ได้เป็นส่วนหนึ่งของรหัสของเราดังนั้นจึงมีวิธีที่จะให้รหัสการค้นหานั้นมองหาการตั้งค่าสถานะที่ฉันตั้งค่า ... อย่างน้อยฉันก็ไม่สามารถหาวิธีการทำเช่นนี้ได้ .. หากคุณมีรหัสการทำงานโปรดแชร์?
Michael Sims

ใช่ดูเหมือนว่าฉันจะทิ้งข้อมูลสำคัญเพียงเล็กน้อยซึ่งจะทำให้มันไม่สมเหตุสมผล คุณจะต้องทำให้ซ็อกเก็ตที่ยอมรับไม่ถูกบล็อกและบล็อกเพียงช่วงระยะเวลาหนึ่งก่อนที่จะวนซ้ำซ้ำในพื้นที่ที่คุณจะต้องตรวจสอบธงทางออก
stu

อย่างไรคุณทำซ็อกเก็ตยอมรับการไม่ปิดกั้น?
Michael Sims

long on = 1L; if (ioctl (socket, (int) FIONBIO, (char *) & on))
stu

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