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 socketA
için A:X
ve socketB
için B:Y
nerede, A
ve B
adresleri ve X
ve Y
bağlantı noktaları vardır, uzun olduğu kadar her zaman mümkündür X != Y
geçerlidir. Bununla birlikte, doğru olsa bile X == Y
, bağlama hala mümkündür A != B
. Örneğin socketA
bir FTP sunucusu programına ait olup bağlıdır 192.168.0.1:21
ve socketB
baş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 21
olarak 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_REUSEADDR
bunu 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_REUSEADDR
temelde çakışmalar ararken joker adreslerin ("herhangi bir IP adresi") nasıl ele alınacağını değiştirir.
Olmadan SO_REUSEADDR
bağlanması, socketA
için 0.0.0.0:21
bağlayıcı sonra ve socketB
hiç 192.168.0.1:21
(hata ile başarısız olur EADDRINUSE
0.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.1
de. İle SO_REUSEADDR
o zamandan beri, başarılı olur 0.0.0.0
ve 192.168.0.1
olan 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 socketA
ve hangi sırada socketB
bağlı olduğuna bakılmaksızın doğru olduğunu unutmayın ; olmadan SO_REUSEADDR
hep başarısız olur, birlikte SO_REUSEADDR
bunun 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 socketB
yaratıldığını, SO_REUSEADDR
ayarlanıp ayarlanmadığını ve son olarak verilen adrese bağlı olduğunu varsayar socketB
. Result
için bağlama işleminin sonucudur socketB
. İlk sütun söylerse ON/OFF
, değeri SO_REUSEADDR
sonuçla ilgisizdir.
Tamam, SO_REUSEADDR
joker karakterler üzerinde bir etkisi var, bilmek güzel. Ancak bu sadece onun etkisi değil. Çoğu insanın SO_REUSEADDR
sunucu 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_WAIT
kapattığı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_LINGER
zaman 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_REUSEADDR
ayarlı değil, devlet bir soket TIME_WAIT
hala 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_REUSEADDR
bağlamaya çalıştığınız soket için ayarlanmışsa, aynı adrese ve durumdaki bağlantı noktasına bağlı başka bir soketTIME_WAIT
zaten "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_WAIT
durumdaki ö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_WAIT
durumda 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_REUSEADDR
beslenen 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_REUSEPORT
rasgele 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_REUSEPORT
SO_REUSEPORT
SO_REUSEPORT
SO_REUESADDR
SO_REUSEPORT
SO_REUSEPORT
SO_REUSEPORT
SO_REUSEPORT
ima etmez SO_REUSEADDR
. Bu, bir soket SO_REUSEPORT
bağlandığında ayarlanmadıysa ve başka bir soket SO_REUSEPORT
tam 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_WAIT
halde. Bir soketi, TIME_WAIT
durumdaki 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_REUSEPORT
SO_REUSEADDR
Daha sonra eklenenden SO_REUSEPORT
baş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_REUSEADDR
noktaya 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_REUSEADDR
gibi davranır SO_REUSEPORT
. Aslında, kod çok noktaya yayın adreslerini ele alır SO_REUSEADDR
ve SO_REUSEPORT
aynı şekilde çalışır, yani tüm çok noktaya yayın adresleri ve bunun SO_REUSEADDR
tersi SO_REUSEPORT
iç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_REUSEADDR
vardı. Bu seçenek genellikle iki önemli istisna dışında BSD'de olduğu gibi davranır:
Dinleme (sunucu) TCP soketi belirli bir bağlantı noktasına bağlı olduğu sürece, SO_REUSEADDR
bu 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_REUSEADDR
ayarlanmadan 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.
İkinci istisna, istemci soketleri için, SO_REUSEPORT
her 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_REUSEADDR
bu 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_REUSEPORT
Linux'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_REUSEPORT
diğer sistemlerde hala iki fark vardır :
"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_EXCLUSIVEADDRUSE
bayrakları biraz telafi etmek için özel bir sihir .
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_REUSEADDR
seçeneği biliyor , yok SO_REUSEPORT
. Ayar SO_REUSEADDR
ayarı gibi Windows davrandığını bir soket üzerinde SO_REUSEPORT
ve SO_REUSEADDR
tek istisnası ile, BSD bir soket üzerinde: A soketi SO_REUSEADDR
can 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_EXCLUSIVEADDRUSE
bir sokette, bağlanma başarılı olursa, kaynak adres ve bağlantı noktası kombinasyonunun yalnızca bu sokete ait olduğundan ve SO_REUSEADDR
ayarlanmış olsa bile başka bir soketin bağlanamayacağını garanti eder .
Microsoft, bayrakların SO_REUSEADDR
ve SO_EXCLUSIVEADDRUSE
Windows'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_REUSEADDR
Davranacağını hemen hemen aynı o BSD olduğu gibi. Bildiğim kadarıyla SO_REUSEPORT
Solaris 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 socketA
bir joker adrese bağlanır ve socketB
gelmiştir SO_REUSEADDR
etkinleştirilmiş ve olmayan joker adres ve aynı bağlantı noktasına bağlı olduğu socketA
, sürece bu bağlama normalde yerini alacak socketA
olan SO_EXCLBIND
etkin, bu durumda ne olursa olsun başarısız olur SO_REUSEADDR
bayrağı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.h
ve stdbool.h
; örneğin gcc
tam 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.3
tüm testlerde)
ve sonuçları güzel bir tabloya yazdırır. Ayrıca bilmeyen sistemlerde de çalışır, SO_REUSEPORT
bu 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_REUSEADDR
soketlere 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).
INADDR_ANY
bağlanmak mevcut yerel adresleri değil, gelecekteki tüm adresleri de bağlar.listen
mümkün olmadığını söylemiş olsanız bile, kesinlikle aynı protokol, yerel adres ve yerel bağlantı noktasına sahip soketler oluşturur.