Bagaimana saya bisa mengganggu metode ServerSocket accept ()?

143

Di utas utama saya, saya memiliki while(listening)loop yang memanggil accept()objek ServerSocket saya, kemudian memulai utas klien baru dan menambahkannya ke Koleksi ketika klien baru diterima.

Saya juga memiliki utas Admin yang ingin saya gunakan untuk mengeluarkan perintah, seperti 'keluar', yang akan menyebabkan semua utas klien dimatikan, matikan sendiri, dan matikan utas utama, dengan memutar mendengarkan false.

Namun, accept()panggilan dalam while(listening)loop blok, dan sepertinya tidak ada cara untuk menghentikannya, sehingga kondisi sementara tidak dapat diperiksa lagi dan program tidak dapat keluar!

Apakah ada cara yang lebih baik untuk melakukan ini? Atau beberapa cara untuk mengganggu metode pemblokiran?

lukeo05
sumber
Untuk pepole yang ingin menunggu x waktu dan kemudian bereaksi menggunakan setSoTimeout ().
Abdullah Orabi

Jawaban:

151

Anda dapat menelepon close()dari utas lainnya, dan accept()panggilan itu akan melempar a SocketException.

Simon Groenewolt
sumber
2
Terima kasih, sangat jelas, bahkan tidak terpikir olehku! Saya menelepon close () setelah saya keluar dari loop.
lukeo05
4
Aneh, bahwa tidak ada info ini di docs: download.oracle.com/javase/6/docs/api/java/net/... metode tidak ditandai sebagai melempar SocketException. Hal ini hanya disebutkan di sini download.oracle.com/javase/1.4.2/docs/api/java/net/...
Vladislav Rastrusny
1
Apakah saya boleh menelepon close(), maksud saya itu akan membuang pengecualian untuk melakukannya, jadi apakah ada cara lain (yang tidak membuang pengecualian, juga tidak berdasarkan batas waktu) untuk berhenti mendengarkan permintaan?
Kushal
3
Dan apa yang harus dilakukan jika koneksi sudah diterima dan utas sedang menunggu beberapa data: while ((s = in.readLine ())! = Null)?
Alex Fedulov
1
@AlexFedulov Matikan soket itu untuk input. readLine()kemudian akan mengembalikan nol dan operasi penutupan normal yang seharusnya sudah ada di thread akan terjadi.
Marquis of Lorne
31

Setel batas waktu aktif accept(), maka panggilan akan menghentikan pemblokiran setelah waktu yang ditentukan:

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

Tetapkan batas waktu pada Socketoperasi pemblokiran :

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

Opsi harus diatur sebelum memasuki operasi pemblokiran agar berlaku. Jika batas waktu berakhir dan operasi akan terus memblokir, java.io.InterruptedIOExceptiondinaikkan. Tidak Socketditutup dalam kasus ini.

Juha Syrjälä
sumber
4

Anda bisa membuat soket "void" untuk break serversocket.accept ()

Sisi server

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
           // ...
       }
  }

Metode untuk memutus siklus server

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

Alasan ServerSocket.close()melempar pengecualian adalah karena Anda memiliki outputstreamatau inputstream melekat pada soket itu. Anda dapat menghindari pengecualian ini dengan aman dengan terlebih dahulu menutup aliran input dan output. Kemudian coba tutup ServerSocket. Berikut ini sebuah contoh:

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();
  }
}

Anda dapat memanggil metode ini untuk menutup soket apa pun dari mana saja tanpa mendapatkan pengecualian.

Soham Malakar
sumber
7
Input dan output stream tidak melekat pada ServerSockettetapi ke a Socketdan kita berbicara tentang menutup ServerSocketbukan Socket, sehingga ServerSocketdapat ditutup tanpa menutup Socketstream a.
icza
1

OK, saya membuat ini bekerja dengan cara yang menjawab pertanyaan OP lebih langsung.

Terus baca melewati jawaban singkat untuk contoh Utas tentang bagaimana saya menggunakan ini.

Jawaban singkat:

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.
      */
}

Contoh dunia nyata:

Dalam contoh ini, saya memiliki ServerSocket yang menunggu koneksi di dalam sebuah Thread. Ketika saya menutup aplikasi, saya ingin mematikan utas (lebih khusus, soket) dengan cara yang bersih sebelum saya menutup aplikasi, jadi saya menggunakan .setSoTimeout () pada ServerSocket kemudian saya menggunakan interupsi yang dilemparkan setelah batas waktu untuk memeriksa dan melihat apakah orang tua mencoba untuk mematikan utas. Jika demikian, maka saya mengatur tutup soket, kemudian mengatur bendera yang menunjukkan bahwa thread selesai, kemudian saya keluar dari loop Threads yang mengembalikan nol.

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;
    }
};

}

Kemudian, di kelas Kontroler saya ... (Saya hanya akan menunjukkan kode yang relevan, pijat ke dalam kode Anda sendiri sesuai kebutuhan)

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());
        });
    }

}

Saya harap ini membantu seseorang di jalan.

Michael Sims
sumber
0

Hal lain yang dapat Anda coba adalah pembersih, adalah memeriksa tanda pada loop terima, dan kemudian ketika admin Anda ingin mematikan pemblokiran utas pada tanda terima, atur tanda (buat amankan utas) dan kemudian buat soket klien koneksi ke soket mendengarkan. Penerimaan akan berhenti memblokir dan mengembalikan soket baru. Anda dapat menemukan beberapa hal protokol sederhana yang memberitahu utas pendengar untuk keluar dari utas dengan bersih. Dan kemudian tutup soket di sisi klien. Tidak ada pengecualian, jauh lebih bersih.

stu
sumber
ini tidak masuk akal ... ketika Anda memiliki kode seperti socket ini = serverSocket.accept (); pada saat itu, pemblokiran dimulai dan loop bukan bagian dari kode kita, jadi ada cara agar kode pemblokiran mencari bendera yang saya atur ... setidaknya saya belum dapat menemukan cara untuk melakukan ini. .. jika Anda memiliki kode kerja, silakan bagikan?
Michael Sims
Ya, sepertinya saya meninggalkan sedikit informasi penting yang akan membuatnya tidak masuk akal. Anda perlu membuat soket accept non blocking dan hanya memblokir untuk beberapa periode waktu sebelum loop diulang, di area mana Anda akan memeriksa flag keluar.
stu
Bagaimana, tepatnya, Anda membuat soket terima, tidak menghalangi?
Michael Sims
panjang = 1L; if (ioctl (socket, (int) FIONBIO, (char *) & on))
stu
@stu Pertanyaan ini untuk Soket Java.
Kröw