Java soket API'si: Bir bağlantının kapatılıp kapatılmadığı nasıl anlaşılır?


94

Java soket API'si ile ilgili bazı sorunlarla karşılaşıyorum. Oyunuma şu anda bağlı olan oyuncuların sayısını görüntülemeye çalışıyorum. Bir oyuncunun ne zaman bağlandığını belirlemek kolaydır. Ancak, bir oyuncunun soket API'sini kullanarak bağlantısının ne zaman kesildiğini belirlemek gereksiz yere zor görünüyor.

isConnected()Uzaktan bağlantısı kesilmiş bir soketi aramak her zaman geri dönüyor gibi görünüyor true. Benzer şekilde, isClosed()uzaktan kapatılmış bir soketi aramak her zaman geri dönüyor gibi görünür false. Bir soketin kapanıp kapanmadığını gerçekten belirlemek için, verinin çıkış akışına yazılması ve bir istisna yakalanması gerektiğini okudum. Bu, bu durumu halletmek için gerçekten kirli bir yol gibi görünüyor. Bir soketin ne zaman kapandığını bilmek için ağ üzerinden sürekli olarak bir çöp mesajı spam etmemiz gerekirdi.

başka bir çözüm var mı?

Yanıtlar:


184

Size bağlantının mevcut durumunu söyleyecek bir TCP API yoktur. isConnected()ve size soketinizinisClosed() mevcut durumunu söyler . Aynı şey değil.

  1. isConnected()olmadığını söyler sen bağladıktan bu soketi . Sahipsin, bu yüzden doğruya dönüyor.

  2. isClosed()olmadığını söyler sen kapattık bu soketi. Sahip oluncaya kadar, yanlış döndürür.

  3. Eğer akran kapattı bağlantıyı düzenli bir şekilde

    • read() -1 döndürür
    • readLine() İadeler null
    • readXXX()EOFExceptiondiğer tüm XXX için atar .

    • Bir yazma IOException, sonunda arabelleğe alma gecikmelerine tabi olarak bir 'eş tarafından bağlantı sıfırlama' atar .

  4. Bağlantı başka bir nedenle kesildiyse, bir yazma IOExceptionsonunda yukarıdaki gibi bir atar ve bir okuma aynı şeyi yapabilir.

  5. Eş hala bağlıysa ancak bağlantıyı kullanmıyorsa, bir okuma zaman aşımı kullanılabilir.

  6. Başka bir yerde okuduklarınızın aksine, size ClosedChannelExceptionbunu söylemiyor. [Hiçbiri SocketException: socket closed.] Size sadece kanalı kapattığınızı ve ardından kullanmaya devam ettiğinizi söyler . Başka bir deyişle, sizin açınızdan bir programlama hatası. Kapalı bir bağlantı olduğunu göstermez .

  7. Windows XP'de Java 7 ile yapılan bazı deneylerin bir sonucu olarak, aşağıdaki durumlarda da ortaya çıkmaktadır:

    • üzerinde seçiyorsun OP_READ
    • select() sıfırdan büyük bir değer döndürür
    • ilişkili SelectionKeyzaten geçersiz ( key.isValid() == false)

    bu, eşin bağlantıyı sıfırladığı anlamına gelir. Ancak bu, JRE sürümüne veya platformuna özgü olabilir.


19
Bağlantı odaklı olan TCP protokolünün bağlantısının durumunu bile bilemeyeceğine inanmak zor ... Bu protokolleri bulan adamlar arabalarını gözleri kapalı kullanıyorlar mı?
PedroD

33
@PedroD Aksine: kasıtlıydı. SNA gibi önceki protokol paketlerinde bir 'çevir sesi' vardı. TCP, bir nükleer savaşta ve daha önemlisi, yönlendiricinin düşmeleri ve yükselmelerinden kurtulmak için tasarlandı: bu nedenle çevir sesi, bağlantı durumu vb. Gibi hiçbir şeyin tamamen yokluğu; ve ayrıca, RFC'lerde TCP'nin canlı kalmasının tartışmalı bir özellik olarak tanımlanmasının ve varsayılan olarak her zaman kapalı olmasının nedenidir. TCP hala bizimle. SNA? IPX? ISO? Değil. Doğru anladılar.
Marquis of Lorne

4
Bu bilgiyi bizden saklamak için bunun iyi bir bahane olduğuna inanmıyorum. Bağlantının koptuğunu bilmek, protokolün daha az hataya dayanıklı olduğu anlamına gelmez, bu her zaman bu bilgiyle ne yaptığımıza bağlıdır ... Benim için yöntem isBound ve isConnected from java saf sahte yöntemlerdir, faydaları yoktur. , ancak bir bağlantı olayı dinleyicisine olan ihtiyacı ifade edin ... Ama yineliyorum: bağlantının koptuğunu bilmek, protokolü daha da kötüleştirmez. Şimdi, söylediğin protokoller, bağlantının koptuğunu tespit ettikleri anda bağlantıyı kestiğini söylüyorsa, bu farklı bir hikaye.
PedroD

23
@Pedro Anlamıyorsun. Bu bir 'bahane' değil. Saklanacak bilgi yok. Çevir sesi yok. TCP , siz ona bir şey yapmaya çalışıncaya kadar bağlantının başarısız olup olmadığını bilmez . Bu temel tasarım kriteriydi.
Marquis of Lorne

2
@EJP cevabınız için teşekkürler. Bir soketin "bloke" olmadan kapanıp kapanmadığını nasıl kontrol edeceğinizi biliyor musunuz? Read veya readLine'ın kullanılması engellenecek. Diğer soketin mevcut yöntemle kapatılıp kapatılmadığını anlamanın bir yolu var mı, bunun 0yerine geri dönüyor gibi görünüyor -1.
insumity

9

Çeşitli mesajlaşma protokollerinde birbirlerinin kalp atışlarını sürdürmek (ping paketleri göndermeye devam etmek), paketin çok büyük olması gerekmeyen genel bir uygulamadır. İnceleme mekanizması, bağlantısı kesilen istemciyi TCP genel olarak anlamadan önce bile tespit etmenize izin verecektir (TCP zaman aşımı çok daha yüksektir) Bir araştırma gönderin ve cevap için 5 saniye bekleyin, eğer 2-3 diyelim ki cevabı görmüyorsanız sonraki araştırmalar, oynatıcınızın bağlantısı kesilir.

Ayrıca ilgili soru


6
Bazı protokollerde 'genel uygulamadır' . Gezegende en çok kullanılan uygulama protokolü olan HTTP'nin PING işlemine sahip olmadığını not ediyorum.
Marquis of Lorne

2
@ user207421, çünkü HTTP / 1 tek seferlik bir protokoldür. İstek gönderirsiniz, yanıt alırsınız. Ve bitirdiniz. Soket kapalıdır. Websocket uzantısında PING işlemi var, HTTP / 2 de öyle.
Michał Zabielski

Mümkünse tek bir bayt ile kalp atışını tavsiye ettim :)
Stefan Reich

@ MichałZabielski Bunun nedeni HTTP tasarımcılarının bunu belirtmemesidir. Neden olmadığını bilmiyorsun. HTTP / 1.1 tek seferlik bir protokol değildir. Soket kapalı değil: HTTP bağlantıları varsayılan olarak kalıcıdır. FTP, SMTP, POP3, IMAP, TLS, ... kalp atışı yok.
Lorne Markisi

2

Diğer cevabın az önce gönderildiğini görüyorum, ancak oyununuzu oynayan müşterilerle etkileşimli olduğunuzu düşünüyorum, bu yüzden başka bir yaklaşım önerebilirim (Bazı durumlarda BufferedReader kesinlikle geçerlidir).

İsterseniz ... "kayıt" sorumluluğunu müşteriye devredebilirsiniz. Yani, her birinden alınan son mesajda bir zaman damgası olan bağlı kullanıcılardan oluşan bir koleksiyonunuz olur ... bir müşteri zaman aşımına uğrarsa, müşterinin yeniden kaydını zorlarsınız, ancak bu aşağıdaki alıntı ve fikre yol açar.

Bunu gerçekten bir soketin kapalı olup olmadığını belirlemek için verinin çıkış akışına yazılması ve bir istisna yakalanması gerektiğini okudum. Bu, bu durumu halletmek için gerçekten kirli bir yol gibi görünüyor.

Java kodunuz Soketi kapatmadıysa / bağlantısını kesmediyse, uzak ana bilgisayarın bağlantınızı kapattığı konusunda başka nasıl bilgilendirilirsiniz? Nihayetinde, deneme / yakalama işleminiz, GERÇEK soketteki olayları dinleyen bir yoklayıcının yapacağı şeyi kabaca yapıyor. Aşağıdakileri göz önünde bulundur:

  • yerel sisteminiz size haber vermeden soketinizi kapatabilir ... Bu sadece Soket uygulamasıdır (yani donanımı / sürücüyü / bellenimi / durum değişikliği için yoklamaz).
  • yeni Soket (Proxy p) ... sizinle olan bağlantıyı kapatabilecek birden fazla taraf (gerçekten 6 uç nokta) var ...

Soyutlanmış dillerin özelliklerinden biri de ayrıntılardan soyutlanmış olmanızdır. SqlConnection s için C # (dene / nihayet) anahtar kelimesini kullanmayı düşünün ... bu sadece iş yapmanın maliyeti ...


Yerel sisteminiz size haber verseniz de olmasanız da soketinizi kapatamaz. Sorunuz 'Java kodunuz soketi kapatmadıysa / bağlantısını kesmediyse ...' hiç mantıklı değil.
Marquis of Lorne

1
Sistemin (örneğin Linux) soketi kapatmaya zorlamasını tam olarak ne engelliyor? Bunu "gdb" den "çağrı kapat" komutunu kullanarak yapmak hala mümkün mü?
Andrey Lebedenko

@AndreyLebedenko Hiçbir şey 'tam olarak engellemiyor', ama yapmıyor.
Marquis of Lorne

@EJB kim yapar o zaman?
Andrey Lebedenko

@AndreyLebedenko Kimse yapmıyor. Yalnızca uygulama kendi soketlerini kapatabilir, bunu yapmadan çıkmazsa, bu durumda işletim sistemi temizlenir.
Lorne Markisi

1

Sanırım bu, tcp bağlantılarının doğasıdır, bu standartlara göre, bağlantının koptuğu sonucuna varmadan önce iletimde yaklaşık 6 dakikalık sessizlik gerekir! Bu yüzden bu soruna kesin bir çözüm bulabileceğinizi sanmıyorum. Belki daha iyi bir yol, sunucunun bir kullanıcı bağlantısının ne zaman kapandığını varsayması gerektiğini tahmin etmek için bazı kullanışlı kodlar yazmaktır.


2
İki saate kadar bundan çok daha fazlasını alabilir.
Marquis of Lorne

0

@ User207421'in dediği gibi, TCP / IP Protokol Mimarisi Modeli nedeniyle bağlantının mevcut durumunu bilmenin bir yolu yoktur. Bu nedenle, sunucunun bağlantıyı kapatmadan önce sizi fark etmesi gerekir veya siz kendiniz kontrol edersiniz.
Bu, soketin sunucu tarafından kapatıldığını nasıl bileceğinizi gösteren basit bir örnektir:

sockAdr = new InetSocketAddress(SERVER_HOSTNAME, SERVER_PORT);
socket = new Socket();
timeout = 5000;
socket.connect(sockAdr, timeout);
reader = new BufferedReader(new InputStreamReader(socket.getInputStream());
while ((data = reader.readLine())!=null) 
      log.e(TAG, "received -> " + data);
log.e(TAG, "Socket closed !");

0

Ben de benzer bir sorunla karşılaştım. Benim durumumda müşteri periyodik olarak veri göndermelidir. Umarım aynı ihtiyacın vardır. Sonra belirtilen süre dolduğunda atılacak socket.setSoTimeout(1000 * 60 * 5);olan SO_TIMEOUT'u ayarladım java.net.SocketTimeoutException. Böylece ölü müşteriyi kolayca tespit edebilirim.


-2

Ben böyle idare ediyorum

 while(true) {
        if((receiveMessage = receiveRead.readLine()) != null ) {  

        System.out.println("first message same :"+receiveMessage);
        System.out.println(receiveMessage);      

        }
        else if(receiveRead.readLine()==null)
        {

        System.out.println("Client has disconected: "+sock.isClosed()); 
        System.exit(1);
         }    } 

result.code == null ise


readLine()İki kez aramanıza gerek yok . Zaten elseblokta boş olduğunu biliyorsunuz .
Marquis of Lorne

-4

Linux'ta, diğer tarafın sizin bilmediğiniz, kapattığı bir sokete yazarken () bir SIGPIPE sinyali / istisnası tetikleyecektir, ancak siz onu çağırın. Ancak, SIGPIPE tarafından yakalanmak istemiyorsanız, MSG_NOSIGNAL bayrağıyla send () kullanabilirsiniz. Send () çağrısı -1 ile geri dönecektir ve bu durumda, errno.h'ye göre EPIPE değerine sahip kırık bir boru (bu durumda bir soket) yazmaya çalıştığınızı söyleyen errno'yu kontrol edebilirsiniz. 32. EPIPE'ye bir tepki olarak geri dönüp soketi yeniden açıp bilgilerinizi tekrar göndermeyi deneyebilirsiniz.


send()Çağrı dönecektir -1 giden verilerinin süresinin bitmesine gönderme sayaçları için yeterince uzun tamponlu var yalnızca. Her iki uçtaki tamponlama send()ve kaputun altındaki asenkron yapı nedeniyle, bağlantı kesildikten sonraki ilk gönderimde neredeyse kesinlikle olmayacak .
Marquis of Lorne
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.