SO_REUSEADDR ve SO_REUSEPORT arasındaki farklar nelerdir?


663

man pagesVe soket seçenekleri için programcı dokümantasyon SO_REUSEADDRve SO_REUSEPORTfarklı işletim sistemleri için farklı ve genellikle son derece kafa karıştırıcı bulunmaktadır. Bazı işletim sistemlerinin seçeneği bile yoktur SO_REUSEPORT. WEB, bu konuyla ilgili çelişkili bilgilerle doludur ve genellikle belirli bir işletim sisteminin yalnızca bir soket uygulaması için geçerli olan ve metinde açıkça belirtilmemiş olabilecek bilgileri bulabilirsiniz.

Peki tam olarak ne kadar SO_REUSEADDRfarklı SO_REUSEPORT?

SO_REUSEPORTDaha sınırlı olmayan sistemler mi?

Farklı işletim sistemlerinden birini kullanırsam beklenen davranış tam olarak nedir?

Yanıtlar:


1616

Taşınabilirliğin harika dünyasına hoş geldiniz ... ya da daha doğrusu eksikliği. Bu iki seçeneği ayrıntılı olarak incelemeye başlamadan ve farklı işletim sistemlerinin bunları nasıl ele aldığını daha derinlemesine incelemeden önce, BSD soket uygulamasının tüm soket uygulamalarının annesi olduğu unutulmamalıdır. Temel olarak diğer tüm sistemler BSD soketi uygulamasını bir zamanda (veya en azından arayüzleri) kopyaladı ve kendi başına geliştirmeye başladı. Tabii ki BSD soketi uygulaması da aynı zamanda gelişti ve böylece onu kopyalayan sistemler daha sonra kopyalayan sistemlerde eksik olan özelliklere sahipti. BSD soketi uygulamasını anlamak, diğer tüm soket uygulamalarını anlamanın anahtarıdır, bu nedenle bir BSD sistemi için kod yazmayı umursamıyorsanız bile bunu okumalısınız.

Bu iki seçeneğe bakmadan önce bilmeniz gereken birkaç temel unsur var. TCP / UDP bağlantısı beş değerlik bir demet ile tanımlanır:

{<protocol>, <src addr>, <src port>, <dest addr>, <dest port>}

Bu değerlerin benzersiz bir birleşimi bir bağlantıyı tanımlar. Sonuç olarak, hiçbir iki bağlantı aynı beş değere sahip olamaz, aksi takdirde sistem artık bu bağlantıları ayırt edemez.

Bir soketin protokolü, socket()işlevle bir soket oluşturulduğunda ayarlanır . Kaynak adres ve port bind()işlevi ile ayarlanır . Hedef adresi ve port connect()fonksiyonu ile ayarlanır . UDP bağlantısız bir protokol olduğundan, UDP yuvaları bağlanmadan kullanılabilir. Yine de, bunları bağlamanıza izin verilir ve bazı durumlarda kodunuz ve genel uygulama tasarımınız için çok avantajlıdır. Bağlantısız modda, veri ilk kez gönderildiğinde açıkça bağlanmayan UDP soketleri, bağlı olmayan bir UDP soketi (yanıt) verisi alamadığı için genellikle sistem tarafından otomatik olarak bağlanır. Bağlı olmayan bir TCP soketi için de aynı durum geçerlidir, bağlanmadan önce otomatik olarak bağlanır.

Bir soketi açıkça bağlarsanız 0, "herhangi bir port" anlamına gelen bağlantı noktasına bağlamak mümkündür . Bir soket gerçekten varolan tüm bağlantı noktalarına bağlanamayacağından, bu durumda sistem belirli bir bağlantı noktasını kendisi seçmelidir (genellikle önceden tanımlanmış, işletim sistemine özgü bir kaynak bağlantı noktası aralığından). Kaynak adres için "herhangi bir adres" olabilen benzer bir joker karakter vardır 0.0.0.0(IPv4 ve::IPv6 durumunda). Bağlantı noktalarından farklı olarak, bir yuva gerçekten "herhangi bir adrese" bağlanabilir, bu da "tüm yerel arabirimlerin tüm kaynak IP adresleri" anlamına gelir. Soket daha sonra bağlanırsa, sistem belirli bir kaynak IP adresi seçmelidir, çünkü bir soket bağlanamaz ve aynı zamanda herhangi bir yerel IP adresine bağlanabilir. Hedef adrese ve yönlendirme tablosunun içeriğine bağlı olarak, sistem uygun bir kaynak adresi seçecek ve "herhangi bir" bağlamayı seçilen kaynak IP adresine bir bağlamayla değiştirecektir.

Varsayılan olarak, aynı kaynak adresi ve kaynak bağlantı noktası kombinasyonuna iki soket bağlanamaz. Kaynak bağlantı noktası farklı olduğu sürece, kaynak adresi aslında önemsizdir. Bağlama socketAiçin A:Xve socketBiçin B:Ynerede, Ave Badresleri ve Xve Ybağlantı noktaları vardır, uzun olduğu kadar her zaman mümkündür X != Ygeçerlidir. Bununla birlikte, doğru olsa bile X == Y, bağlama hala mümkündür A != B. Örneğin socketAbir FTP sunucusu programına ait olup bağlıdır 192.168.0.1:21ve socketBbaşka bir FTP sunucu programı aittir ve bağlı olduğu 10.0.0.1:21, her iki bağlamaları başarılı olur. Bununla birlikte, bir soketin yerel olarak "herhangi bir adrese" bağlı olabileceğini unutmayın. Bir soket bağlıysa0.0.0.0:21, mevcut tüm yerel adreslere aynı anda bağlıdır ve bu durumda , tüm yerel yerel IP adresleriyle çakışma 21olarak hangi IP adresine bağlanmaya çalıştığından bağımsız olarak bağlantı noktasına başka bir soket bağlanamaz 0.0.0.0.

Şu ana kadar söylenen her şey tüm büyük işletim sistemleri için hemen hemen eşittir. Adres yeniden kullanımı devreye girdiğinde işler işletim sistemine özel olmaya başlar. BSD ile başlıyoruz, çünkü yukarıda söylediğim gibi, tüm soket uygulamalarının annesi.

BSD

SO_REUSEADDR

Eğer SO_REUSEADDRbunu bağlayıcı öncesinde soket üzerinde etkindir bağlı başka sokete bir çakışma olmadıkça, soket başarıyla bağlanmış olabilir tam olarak kaynak adresi ve port aynı kombinasyonu. Şimdi bunun öncekinden nasıl farklı olduğunu merak edebilirsiniz. Anahtar kelime "tam olarak" dır. SO_REUSEADDRtemelde çakışmalar ararken joker adreslerin ("herhangi bir IP adresi") nasıl ele alınacağını değiştirir.

Olmadan SO_REUSEADDRbağlanması, socketAiçin 0.0.0.0:21bağlayıcı sonra ve socketBhiç 192.168.0.1:21(hata ile başarısız olur EADDRINUSE0.0.0.0 vasıta "herhangi bir yerel IP adresi", böylece tüm yerel IP adresleri bu soket tarafından kullanılmakta kabul edilir ve bu içerdiğinden,) 192.168.0.1de. İle SO_REUSEADDRo zamandan beri, başarılı olur 0.0.0.0ve 192.168.0.1olan tam değil aynı adres, tek tüm yerel adresler için bir joker diğeri çok özel yerel adresidir. Yukarıdaki ifadenin hangi sırada socketAve hangi sırada socketBbağlı olduğuna bakılmaksızın doğru olduğunu unutmayın ; olmadan SO_REUSEADDRhep başarısız olur, birlikte SO_REUSEADDRbunun her zaman başarılı olur.

Size daha iyi bir genel bakış sunmak için, burada bir tablo oluşturalım ve tüm olası kombinasyonları listeleyelim:

SO_REUSEADDR soketA soketB Sonuç
-------------------------------------------------- -------------------
  AÇIK / KAPALI 192.168.0.1:21 192.168.0.1:21 Hatası (EADDRINUSE)
  AÇIK / KAPALI 192.168.0.1:21 10.0.0.1:21 Tamam
  AÇIK / KAPALI 10.0.0.1:21 192.168.0.1:21 Tamam
   KAPALI 0.0.0.0:21 192.168.1.0:21 Hatası (EADDRINUSE)
   OFF 192.168.1.0:21 0.0.0.0:21 Hatası (EADDRINUSE)
   AÇIK 0.0.0.0:21 192.168.1.0:21 Tamam
   AÇIK 192.168.1.0:21 0.0.0.0:21 TAMAM
  AÇIK / KAPALI 0.0.0.0:21 0.0.0.0:21 Hatası (EADDRINUSE)

Yukarıdaki tablo socketA, zaten verilen adrese başarıyla bağlanmış olduğunu socketA, daha sonra socketByaratıldığını, SO_REUSEADDRayarlanıp ayarlanmadığını ve son olarak verilen adrese bağlı olduğunu varsayar socketB. Resultiçin bağlama işleminin sonucudur socketB. İlk sütun söylerse ON/OFF, değeri SO_REUSEADDRsonuçla ilgisizdir.

Tamam, SO_REUSEADDRjoker karakterler üzerinde bir etkisi var, bilmek güzel. Ancak bu sadece onun etkisi değil. Çoğu insanın SO_REUSEADDRsunucu programlarında ilk etapta kullanmasının nedeni de iyi bilinen başka bir etki daha vardır . Bu seçeneğin diğer önemli kullanımları için TCP protokolünün nasıl çalıştığına daha ayrıntılı bir şekilde bakmalıyız.

Bir soketin bir gönderme arabelleği vardır ve send()işleve yapılan bir çağrı başarılı olursa, istenen verilerin gerçekten gerçekten gönderildiği anlamına gelmez, bu sadece verilerin gönderme arabelleğine eklendiği anlamına gelir. UDP soketleri için, veriler hemen olmasa da genellikle çok yakında gönderilir, ancak TCP soketleri için gönderme arabelleğine veri ekleme ile TCP uygulamasının gerçekten bu verileri göndermesi arasında nispeten uzun bir gecikme olabilir. Sonuç olarak, bir TCP soketini kapattığınızda, gönderme tamponunda henüz gönderilmemiş, ancak kodunuz gönderildiği kabul edilir.send()çağrı başarılı. TCP uygulaması soketi isteğiniz üzerine derhal kapatıyorsa, tüm bu veriler kaybolur ve kodunuz bunu bile bilmez. TCP'nin güvenilir bir protokol olduğu ve bunun gibi verilerin kaybedilmesinin çok güvenilir olmadığı söylenir. Bu yüzden hala gönderilecek verileri olan bir soket, TIME_WAITkapattığınızda çağrılan duruma geçecektir . Bu durumda, beklemedeki tüm veriler başarıyla gönderilinceye veya bir zaman aşımı gerçekleşene kadar beklenir, bu durumda soket zorla kapatılır.

Çekirdeğin hala uçuşta veri olup olmadığına bakılmaksızın, çekirdeğin soketi kapatmadan önce bekleyeceği süreye Linger Süresi denir . Gecikme süresi (iki dakika Birçok sistemlerde bulacaksınız ortak bir değerdir) çoğu sistemlerde ve oldukça uzun varsayılan olarak global yapılandırılabilir. Ayrıca SO_LINGERzaman aşımı süresini kısaltmak veya daha uzun yapmak ve hatta tamamen devre dışı bırakmak için kullanılabilen soket seçeneği kullanılarak soket başına yapılandırılabilir . Tamamen devre dışı bırakmak çok kötü bir fikirdir, çünkü bir TCP soketini zarifçe kapatmak biraz karmaşık bir süreçtir ve birkaç paket göndermeyi ve geri göndermeyi içerir (kaybolmaları durumunda bu paketleri yeniden göndermenin yanı sıra) ve tüm bu yakın süreci aynı zamanda Linger Time ile sınırlıdır. Kalan süreyi devre dışı bırakırsanız, soketiniz uçuş sırasında sadece veri kaybetmekle kalmaz, aynı zamanda zarif bir şekilde değil, her zaman zorla kapatılır, bu genellikle önerilmez. TCP bağlantısının nasıl zarif bir şekilde kapatıldığıyla ilgili ayrıntılar bu cevabın kapsamı dışındadır, hakkında daha fazla bilgi edinmek istiyorsanız, bu sayfaya göz atmanızı tavsiye ederim . Ve devam etmeyi devre dışı bırakmış olsanız bile SO_LINGER, işleminiz soketi açıkça kapatmadan ölürse, BSD (ve muhtemelen diğer sistemler) yine de yapılandırdığınız şeyi yok sayar. Bu, örneğin, kodunuz yalnızcaexit()(küçük, basit sunucu programları için oldukça yaygındır) veya işlem bir sinyalle öldürülür (yasadışı bellek erişimi nedeniyle basitçe çökme olasılığını içerir). Bu nedenle, bir soketin her koşulda asla çalmayacağından emin olmak için yapabileceğiniz hiçbir şey yoktur.

Soru şu: Sistem bir soketi nasıl ele alıyor TIME_WAIT? Eğer SO_REUSEADDRayarlı değil, devlet bir soket TIME_WAIThala sürece sürebilir kaynak adresi ve port ve soket gerçekten kapatılana kadar başarısız olur aynı adrese ve bağlantı noktasına yeni bir soket bağlamak için herhangi bir girişimde, bağlı olduğu kabul edilmektedir yapılandırılmış Oyalanma Süresi . Bu yüzden bir soketin kaynak adresini kapattıktan hemen sonra yeniden bağlayabileceğinizi düşünmeyin. Çoğu durumda bu başarısız olur. Ancak, SO_REUSEADDRbağlamaya çalıştığınız soket için ayarlanmışsa, aynı adrese ve durumdaki bağlantı noktasına bağlı başka bir soketTIME_WAITzaten "yarı ölü" den sonra basitçe yok sayılır ve soketiniz herhangi bir sorun olmadan tam olarak aynı adrese bağlanabilir. Bu durumda, diğer soketin aynı adrese ve bağlantı noktasına sahip olması bir rol oynamaz. Bir soketin TIME_WAITdurumdaki ölmekte olan bir soketle tam olarak aynı adrese ve bağlantı noktasına bağlanmasının , diğer soketin hala "iş başında" olması durumunda beklenmedik ve genellikle istenmeyen yan etkilere sahip olabileceğini, ancak bu cevabın kapsamının dışında olduğunu ve Neyse ki bu yan etkiler pratikte oldukça nadirdir.

Bilmeniz gereken son bir şey var SO_REUSEADDR. Yukarıda yazılı olan her şey, bağlamak istediğiniz sokette adres yeniden kullanımı etkin olduğu sürece çalışır. Zaten bağlı olan veya bir TIME_WAITdurumda olan diğer soketin de bağlandığında bu bayrağa sahip olması gerekli değildir . Bağlamanın başarılı olup olmayacağına karar veren kod, yalnızca çağrıya SO_REUSEADDRbeslenen soketin bayrağını inceler, bind()incelenen diğer tüm soketler için bu bayrağa bile bakılmaz.

SO_REUSEPORT

SO_REUSEPORTçoğu insanın olmasını beklediği şey budur SO_REUSEADDR. Temel olarak, önceden bağlanmış tüm soketler de bağlanmadan önce ayarlandığı sürece, SO_REUSEPORTrasgele sayıda soketi tam olarak aynı kaynak adrese ve bağlantı noktasına bağlamanızı sağlar . Bir adrese ve bağlantı noktasına bağlanan ilk soket ayarlanmamışsa, diğer soket ayarlanmış olsun ya da olmasın, ilk soket yeniden bağlanana kadar başka hiçbir soket aynı adrese ve bağlantı noktasına bağlanamaz. Kod kullanımından farklı olarak, yalnızca o anda bağlı olan soketin ayarlandığını doğrulamakla kalmaz, aynı zamanda adres ve bağlantı noktası çakışan soketin bağlandığında ayarlandığını da doğrular .SO_REUSEPORTSO_REUSEPORTSO_REUSEPORTSO_REUESADDRSO_REUSEPORTSO_REUSEPORTSO_REUSEPORT

SO_REUSEPORTima etmez SO_REUSEADDR. Bu, bir soket SO_REUSEPORTbağlandığında ayarlanmadıysa ve başka bir soket SO_REUSEPORTtam olarak aynı adrese ve bağlantı noktasına bağlandığında ayarlandıysa, bağlanma başarısız olur, ancak diğer soket zaten ölüyorsa da başarısız olur ve olduğu TIME_WAIThalde. Bir soketi, TIME_WAITdurumdaki başka bir soketle aynı adreslere ve bağlantı noktasına bağlayabilmek için SO_REUSEADDR, o sokette ya da bağlamadan önce her iki sokette deSO_REUSEPORT ayarlanmış olması gerekir . Tabii ki her iki yaymaya bırakıldı edilir ve bir soket üzerinde.SO_REUSEPORTSO_REUSEADDR

Daha sonra eklenenden SO_REUSEPORTbaşka söylenecek çok şey yok SO_REUSEADDR, bu yüzden onu bu seçenek eklenmeden önce BSD kodunu "çatallayan" diğer sistemlerin birçok soket uygulamasında bulamayacaksınız ve Bu seçeneği kullanmadan önce iki soketi BSD'de tam olarak aynı soket adresine bağlamanın yolu.

Connect () EADDRINUSE geri dönüyor mu?

Çoğu kişi bunun bind()hatayla başarısız olabileceğini bilir EADDRINUSE, ancak adres yeniden kullanımıyla oynamaya başladığınızda connect(), bu hatayla başarısız olan garip bir durumla karşılaşabilirsiniz . Bu nasıl olabilir? Connect'in bir sokete eklediği her şeyden sonra, uzak bir adres nasıl kullanılıyor olabilir? Birden fazla soketin tam olarak aynı uzak adrese bağlanması daha önce hiç sorun olmamıştı, peki burada sorun ne?

Cevabımın en üstünde söylediğim gibi, bir bağlantı beş değerden oluşan bir demet ile tanımlanır, hatırlıyor musunuz? Ayrıca, bu beş değerin benzersiz olması gerektiğini söyledim, aksi takdirde sistem artık iki bağlantıyı ayırt edemez, değil mi? Adres yeniden kullanımı ile aynı protokolün iki soketini aynı kaynak adrese ve bağlantı noktasına bağlayabilirsiniz. Bu, bu beş değerden üçünün bu iki soket için zaten aynı olduğu anlamına gelir. Şimdi bu soketlerin her ikisini de aynı hedef adrese ve bağlantı noktasına bağlamaya çalışırsanız, tuples'ları tamamen aynı olan iki bağlı soket oluşturacaksınız. Bu işe yaramaz, en azından TCP bağlantıları için geçerli değildir (UDP bağlantıları zaten gerçek bağlantı değildir). İki bağlantıdan biri için veri geldiğinde, sistem verilerin hangi bağlantıya ait olduğunu söyleyemedi.

Dolayısıyla, aynı protokolün iki soketini aynı kaynak adrese ve bağlantı noktasına bağlar ve her ikisini de aynı hedef adrese ve bağlantı noktasına bağlamaya çalışırsanız, bağlamaya çalıştığınız ikinci soketin connect()hatasında başarısız olur EADDRINUSE, yani bir beş değerin aynı demetine sahip soket zaten bağlı.

Çok Noktaya Yayın Adresleri

Çoğu insan çok noktaya yayın adreslerinin olduğu gerçeğini görmezden gelir, ancak vardır. Tek noktaya yayın adresleri bire bir iletişim için kullanılırken, çok noktaya yayın adresleri bire çok iletişim için kullanılır. Çoğu kişi IPv6'yı öğrendiklerinde çok noktaya yayın adreslerinin farkına vardı, ancak IPv4'te çok noktaya yayın adresleri de vardı, ancak bu özellik genel İnternet'te asla yaygın olarak kullanılmadı.

Çok SO_REUSEADDRnoktaya yayın adreslerinde yapılan değişikliklerin anlamı, birden çok soketin aynı kaynak çok noktaya yayın adresi ve bağlantı noktası kombinasyonuna bağlanmasına izin verir. Diğer bir deyişle, çok noktaya yayın adresleri için tek noktaya yayın adresleri SO_REUSEADDRgibi davranır SO_REUSEPORT. Aslında, kod çok noktaya yayın adreslerini ele alır SO_REUSEADDRve SO_REUSEPORTaynı şekilde çalışır, yani tüm çok noktaya yayın adresleri ve bunun SO_REUSEADDRtersi SO_REUSEPORTiçin geçerli olduğunu söyleyebilirsiniz .


FreeBSD / OpenBSD / NetBSD

Bütün bunlar orijinal BSD kodunun oldukça geç çatallarıdır, bu yüzden üçü de BSD ile aynı seçenekleri sunar ve ayrıca BSD'deki gibi davranırlar.


macOS (MacOS X)

Özünde, macOS, BSD kodunun (BSD 4.3) oldukça geç bir çatalına dayanan " Darwin " adlı bir BSD tarzı UNIX'tir ve daha sonra (o anda mevcut) FreeBSD ile yeniden senkronize edilmiştir. Mac OS 10.3 sürümü için 5 kod tabanı, böylece Apple tam POSIX uyumluluğu elde edebilir (macOS POSIX sertifikalıdır). Çekirdeğinde (" Mach ") bir mikro çekirdeğe sahip olmasına rağmen, çekirdeğin geri kalanı (" XNU ") temelde sadece bir BSD çekirdeğidir ve bu yüzden macOS BSD ile aynı seçenekleri sunar ve BSD ile aynı şekilde davranırlar. .

iOS / watchOS / tvOS

iOS, biraz değiştirilmiş ve kesilmiş bir çekirdeğe, biraz kullanıcı alanı araç setini ve biraz farklı bir varsayılan çerçeve kümesine sahip bir macOS çataldır. watchOS ve tvOS, iOS çatallarıdır, bunlar daha da aşağı çekilir (özellikle watchOS). En iyi bildiğim kadarıyla hepsi tam olarak macOS gibi davranıyor.


Linux

Linux <3,9

Linux 3.9'dan önce sadece seçenek SO_REUSEADDRvardı. Bu seçenek genellikle iki önemli istisna dışında BSD'de olduğu gibi davranır:

  1. Dinleme (sunucu) TCP soketi belirli bir bağlantı noktasına bağlı olduğu sürece, SO_REUSEADDRbu bağlantı noktasını hedefleyen tüm soketler için seçenek tamamen yok sayılır. İkinci bir soketin aynı porta bağlanması, ancak SO_REUSEADDRayarlanmadan BSD'de de mümkün olduğunda mümkündür . Örneğin, bir joker adrese ve daha sonra belirli bir adrese ya da başka bir yolla bağlantı kuramazsınız SO_REUSEADDR. Yapabileceğiniz şey, her zaman izin verildiği gibi aynı bağlantı noktasına ve iki farklı joker olmayan adrese bağlanabilmenizdir. Bu açıdan Linux, BSD'den daha kısıtlayıcıdır.

  2. İkinci istisna, istemci soketleri için, SO_REUSEPORTher ikisinin de bu bayrağı bağlanmadan önce ayarlandığı sürece , bu seçeneğin BSD'deki gibi davranmasıdır . Buna izin vermenin nedeni, çeşitli protokoller için birden fazla soketin tam olarak aynı UDP soket adresine bağlanabilmesinin önemli olmasıydı ve daha SO_REUSEPORTönce 3.9'dan önce olmadığı için, SO_REUSEADDRbu boşluğu doldurmak için davranışı buna göre değiştirildi. . Bu açıdan Linux, BSD'den daha az kısıtlayıcıdır.

Linux> = 3,9

Linux 3.9 seçeneği SO_REUSEPORTLinux'a da eklendi . Bu seçenek, BSD'deki seçenek gibi davranır ve tüm soketlerin bağlamadan önce bu seçeneğe sahip olduğu sürece, tam olarak aynı adrese ve bağlantı noktası numarasına bağlanmaya izin verir.

Yine de, SO_REUSEPORTdiğer sistemlerde hala iki fark vardır :

  1. "Bağlantı noktası kaçırma" önlemek için özel bir sınırlama vardır: Aynı adresi ve bağlantı noktası birleşimini paylaşmak isteyen tüm soketler aynı etkili kullanıcı kimliğini paylaşan işlemlere ait olmalıdır! Yani bir kullanıcı başka bir kullanıcının portlarını "çalamaz". Bu, eksikleri SO_EXCLBIND/ SO_EXCLUSIVEADDRUSEbayrakları biraz telafi etmek için özel bir sihir .

  2. Ayrıca çekirdek SO_REUSEPORT, diğer işletim sistemlerinde bulunmayan soketler için bazı "özel büyü" gerçekleştirir : UDP soketleri için, datagramları eşit olarak dağıtmaya çalışır, TCP dinleme soketleri için, gelen bağlantı isteklerini (arayarak kabul edilenleri accept()) dağıtmaya çalışır Aynı adres ve port kombinasyonunu paylaşan tüm soketlerde eşit olarak. Böylece bir uygulama aynı portu birden fazla alt süreçte kolayca açabilir ve daha sonra SO_REUSEPORTçok ucuz bir yük dengelemesi elde etmek için kullanabilir .


Android

Tüm Android sistemi çoğu Linux dağıtımından biraz farklı olsa da, özünde biraz değiştirilmiş bir Linux çekirdeği çalışıyor, bu nedenle Linux için geçerli olan her şey Android için de geçerli olmalı.


pencereler

Windows sadece SO_REUSEADDRseçeneği biliyor , yok SO_REUSEPORT. Ayar SO_REUSEADDRayarı gibi Windows davrandığını bir soket üzerinde SO_REUSEPORTve SO_REUSEADDRtek istisnası ile, BSD bir soket üzerinde: A soketi SO_REUSEADDRcan daima bağlama zaten bağlı soket ile tam olarak aynı kaynak adresi ve port için diğer soket bu seçeneği yoktu bile ne zaman bağlı olduğunu ayarlayın . Bu davranış, bir uygulamanın başka bir uygulamanın bağlı bağlantı noktasını "çalmasına" izin verdiği için biraz tehlikelidir. Söylemeye gerek yok, bunun büyük güvenlik sonuçları olabilir. Microsoft bunun bir sorun olabileceğini fark etti ve böylece başka bir soket seçeneği ekledi SO_EXCLUSIVEADDRUSE. AyarSO_EXCLUSIVEADDRUSEbir sokette, bağlanma başarılı olursa, kaynak adres ve bağlantı noktası kombinasyonunun yalnızca bu sokete ait olduğundan ve SO_REUSEADDRayarlanmış olsa bile başka bir soketin bağlanamayacağını garanti eder .

Microsoft, bayrakların SO_REUSEADDRve SO_EXCLUSIVEADDRUSEWindows'ta nasıl çalıştığı, bağlamayı / yeniden bağlanmayı nasıl etkilediğiyle ilgili daha fazla ayrıntı için , lütfen bu cevabın üst kısmına yakın bir yerde tabloma benzer bir tablo sağladı. Sadece bu sayfayı ziyaret edin ve biraz aşağı kaydırın. Aslında üç tablo var, birincisi eski davranışı (Windows 2003'ten önce), ikincisi davranışı (Windows 2003 ve üstü) ve üçüncüsü de bind()aramalar yapıldığında Windows 2003 ve sonraki sürümlerde davranışın nasıl değiştiğini gösterir farklı kullanıcılar.


Solaris

Solaris, SunOS'un halefidir. SunOS başlangıçta BSD çatalına dayanıyordu, SunOS 5 ve daha sonra SVR4 çatalına dayanıyordu, ancak SVR4 BSD, System V ve Xenix'in bir birleşimidir, bu nedenle bir dereceye kadar Solaris de bir BSD çatal ve oldukça erken bir. Sonuç olarak Solaris sadece biliyor SO_REUSEADDR, yok SO_REUSEPORT. SO_REUSEADDRDavranacağını hemen hemen aynı o BSD olduğu gibi. Bildiğim kadarıyla SO_REUSEPORTSolaris ile aynı davranışı elde etmenin bir yolu yok , yani iki soketi tam olarak aynı adrese ve bağlantı noktasına bağlamak mümkün değil.

Windows'da olduğu gibi, Solaris de bir sokete özel bir bağlama yapma seçeneğine sahiptir. Bu seçenek adlandırılmıştır SO_EXCLBIND. Bu seçenek bağlamadan önce bir soket üzerinde ayarlanırsa SO_REUSEADDR, iki soket adres çakışması açısından test edilirse, başka bir sokette ayar yapmanın bir etkisi olmaz. Eğer Örneğin socketAbir joker adrese bağlanır ve socketBgelmiştir SO_REUSEADDRetkinleştirilmiş ve olmayan joker adres ve aynı bağlantı noktasına bağlı olduğu socketA, sürece bu bağlama normalde yerini alacak socketAolan SO_EXCLBINDetkin, bu durumda ne olursa olsun başarısız olur SO_REUSEADDRbayrağını socketB.


Diğer Sistemler

Sisteminizin yukarıda listelenmemesi durumunda, sisteminizin bu iki seçeneği nasıl ele aldığını öğrenmek için kullanabileceğiniz küçük bir test programı yazdım. Ayrıca, sonuçlarımın yanlış olduğunu düşünüyorsanız , lütfen yorum göndermeden ve yanlış iddialarda bulunmadan önce bu programı çalıştırın.

Kodun oluşturulması gereken tek şey biraz POSIX API (ağ parçaları için) ve bir C99 derleyicisidir (aslında C99 olmayan çoğu derleyici sundukları kadar iyi çalışır inttypes.hve stdbool.h; örneğin gcctam C99 desteği sunmadan önce her ikisi de desteklenir) .

Programın çalışması gereken tek şey, sisteminizdeki en az bir arabirime (yerel arabirim dışında) bir IP adresinin atanmış olması ve bu arabirimi kullanan varsayılan bir yolun ayarlanmış olmasıdır. Program bu IP adresini toplar ve ikinci "özel adres" olarak kullanır.

Aklınıza gelebilecek tüm olası kombinasyonları test eder:

  • TCP ve UDP protokolü
  • Normal soketler, dinleme (sunucu) soketleri, çok noktaya yayın soketleri
  • SO_REUSEADDR soket1, soket2 veya her iki sokete takılı
  • SO_REUSEPORT soket1, soket2 veya her iki sokete takılı
  • Yapabileceğiniz tüm adres kombinasyonları 0.0.0.0(joker karakter), 127.0.0.1(belirli adres) ve birincil arayüzünüzde bulunan ikinci belirli adres (çok noktaya yayın için sadece 224.1.2.3tüm testlerde)

ve sonuçları güzel bir tabloya yazdırır. Ayrıca bilmeyen sistemlerde de çalışır, SO_REUSEPORTbu durumda bu seçenek test edilmez.

Programın kolayca test edemediği şey, bir soketi bu durumda zorlamak ve tutmak çok zor olduğu için durumdaki SO_REUSEADDRsoketlere nasıl davranır TIME_WAIT. Neyse ki çoğu işletim sistemi burada BSD gibi davranıyor gibi görünüyor ve çoğu zaman programcılar bu durumun varlığını görmezden gelebilirler.

İşte kod (burada ekleyemiyorum, cevapların bir boyut sınırı var ve kod bu cevabı sınırın üzerine itecektir).


9
Örneğin, "kaynak adres" gerçekten de "yerel adres" olmalıdır, sonraki üç alan da aynı şekilde. İle INADDR_ANYbağlanmak mevcut yerel adresleri değil, gelecekteki tüm adresleri de bağlar. listenmümkün olmadığını söylemiş olsanız bile, kesinlikle aynı protokol, yerel adres ve yerel bağlantı noktasına sahip soketler oluşturur.
Ben Voigt

9
@ Ben Kaynak ve Hedef IP adresleme için kullanılan resmi terimlerdir (birincil olarak bahsettiğim). Yerel ve Uzak anlamsızdır, çünkü Uzak adres aslında bir "Yerel" adres olabilir ve Hedefin tersi Yerel değil Kaynak'tır. Sorununuzun ne olduğunu bilmiyorum INADDR_ANY, gelecekteki adreslere bağlanmayacağını asla söylemedim. Ve listenhiçbir soket oluşturmaz, bu da tüm cümlenizi biraz garip hale getirir.
Mecki

7
@Ben Sisteme yeni bir adres eklendiğinde, aynı zamanda bir "varolan yerel adres" dir, henüz var olmaya başladı. "Bütün demedi anda mevcut yerel adresler". Aslında, soketin aslında joker karaktere gerçekten bağlı olduğunu söyleyebilirim, yani soket , şimdi, yarın ve yüz yıl içinde bu joker karakterle eşleşen her şeye bağlı demektir. Kaynak ve hedef için benzer şekilde, burada sadece nitpicking. Gerçek bir teknik katkınız var mı?
Mecki

8
@Mecki: Gerçekten varolan kelimenin şu anda var olmayan ama gelecekte olacak şeyleri içerdiğini mi düşünüyorsunuz? Kaynak ve hedef bir nitpick değildir. Gelen paketler bir soketle eşleştiğinde, paketteki hedef adresin soketin "kaynak" adresiyle eşleşeceğini mi söylüyorsunuz? Bu yanlış ve biliyorsunuz, zaten kaynak ve hedefin karşıt olduğunu söylediniz . Soketteki yerel adres, gelen paketlerin hedef adresiyle eşleştirilir ve giden paketlerdeki kaynak adrese yerleştirilir .
Ben Voigt

10
@Mecki: "Soketin yerel adresi, giden paketlerin kaynak adresi ve gelen paketlerin hedef adresidir" derseniz çok daha mantıklıdır. Paketlerin kaynak ve hedef adresleri vardır. Ana bilgisayarlar ve ana bilgisayarlardaki soketler bunu yapmaz. Datagram soketleri için her iki eş eşittir. TCP soketleri için, üç yönlü el sıkışma nedeniyle, bir yönlendirici (istemci) ve bir yanıtlayıcı (sunucu) vardır, ancak yine de bağlantı veya bağlı soketlerin bir kaynağı ve hedefi olduğu anlamına gelmez , çünkü trafik her iki yönde de akar.
Ben Voigt

0

Mecki'nin cevabı kesinlikle mükemmel, ancak FreeBSD'nin SO_REUSEPORT_LBLinux'un SO_REUSEPORTdavranışını taklit eden desteklediğini de eklemeye değer - yükü dengeler; bkz. setsockopt (2)

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.