Soket kitaplığında recv çağırırken recv arabelleğim ne kadar büyük olmalı


129

C'deki soket kitaplığı hakkında birkaç sorum var. Sorularımda değineceğim bir kod parçası.

char recv_buffer[3000];
recv(socket, recv_buffer, 3000, 0);
  1. Recv_buffer'ın ne kadar büyük olacağına nasıl karar veririm? 3000 kullanıyorum ama keyfi.
  2. recv()arabelleğimden daha büyük bir paket alırsa ne olur ?
  3. Recv'yi tekrar aramadan tüm mesajı alıp almadığımı ve alınacak hiçbir şey olmadığında sonsuza kadar beklettiğimi nasıl bilebilirim?
  4. Sabit bir alana sahip olmayan bir arabellek yapabilmemin bir yolu var mı, böylece alan tükenme korkusu olmadan ona ekleme yapmaya devam edebilir miyim? belki kullanarak strcatson bitiştirmek için recv()tampon yanıtı?

Birinde birçok soru olduğunu biliyorum, ancak herhangi bir yanıt için çok minnettar olurum.

Yanıtlar:


230

Bu soruların yanıtları, bir akım soketi ( SOCK_STREAM) veya bir veri birimi soketi ( SOCK_DGRAM) kullanmanıza bağlı olarak değişir - TCP / IP içinde, ilki TCP'ye ve ikincisi UDP'ye karşılık gelir.

Tamponun ne kadar büyük olacağını nereden biliyorsun recv()?

  • SOCK_STREAM: Gerçekten çok önemli değil. Protokolünüz işlemsel / etkileşimli bir protokol ise, makul olarak beklediğiniz en büyük bireysel mesajı / komutu tutabilecek bir boyut seçin (3000 büyük olasılıkla iyidir). Protokolünüz toplu veri aktarıyorsa, daha büyük tamponlar daha verimli olabilir - iyi bir temel kural, çekirdeğin soketin arabellek boyutunu almasıyla aynıdır (genellikle yaklaşık 256kB).

  • SOCK_DGRAM: Uygulama düzeyindeki protokolünüzün gönderdiği en büyük paketi tutacak kadar büyük bir arabellek kullanın. UDP kullanıyorsanız, genel olarak uygulama düzeyindeki protokolünüz yaklaşık 1400 bayttan daha büyük paketler göndermemelidir, çünkü kesinlikle parçalanmaları ve yeniden birleştirilmeleri gerekecektir.

recvArabellekten daha büyük bir paket alırsa ne olur ?

  • SOCK_STREAM: Soru, ifade edildiği gibi gerçekten mantıklı değil, çünkü akış soketlerinin bir paket konsepti yok - sadece sürekli bir bayt akışı. Okumak için arabelleğinizde yer olduğundan daha fazla bayt varsa, bunlar işletim sistemi tarafından sıraya alınır ve bir sonraki aramanız için kullanılabilir recv.

  • SOCK_DGRAM: Fazla baytlar atılır.

Mesajın tamamını alıp almadığımı nasıl bilebilirim?

  • SOCK_STREAM: Uygulama düzeyi protokolünüzde mesajın sonunu belirlemenin bir yolunu oluşturmanız gerekir. Genellikle bu, bir uzunluk ön ekidir (her mesaja mesajın uzunluğuyla başlar) veya bir mesaj sonu sınırlayıcıdır (örneğin, metin tabanlı bir protokoldeki bir satırsonu olabilir). Daha az kullanılan üçüncü bir seçenek, her mesaj için sabit bir boyut belirlemektir. Bu seçeneklerin kombinasyonları da mümkündür - örneğin, bir uzunluk değeri içeren sabit boyutlu bir başlık.

  • SOCK_DGRAM: Tek bir recvçağrı her zaman tek bir datagram döndürür.

Sabit bir alana sahip olmayan bir arabellek oluşturmamın bir yolu var mı, böylece alan tükenme korkusu olmadan ona ekleme yapmaya devam edebilir miyim?

Hayır. Bununla birlikte, tamponu kullanarak yeniden boyutlandırmayı deneyebilirsiniz realloc()(eğer başlangıçta malloc()veya ile ayrılmışsa calloc()).


1
Kullandığım protokoldeki bir mesajın sonunda bir "/ r / n / r / n" var. Ve bir do while döngüm var, içinde recv'i arıyorum, mesajı recv_buffer'ın başına yerleştiriyorum. ve while ifadem buna benzer while ((! (strstr (recv_buffer, "\ r \ n \ r \ n")); Sorum şu ki, bir recv'nin "\ r \ n" alması mümkün mü ve next recv "\ r \ n" olsun, böylece while
durumum

3
Evet öyle. Tam bir mesajınız yoksa, bir sonrakinden baytları recvkısmi mesajın ardından tampona doldurarak, bu sorunu çözebilirsiniz . strstr()Doldurulan ham tamponda kullanmamalısınız - geçersiz recv()sonlandırıcı içerdiğine dair bir garanti yoktur, bu nedenle çökmeye neden olabilir strstr().
kafe

3
UDP durumunda, 1400 baytın üzerindeki UDP paketlerinin gönderilmesinde yanlış bir şey yoktur. Parçalanma tamamen yasaldır ve IP protokolünün temel bir parçasıdır (IPv6'da bile, yine de ilk gönderenin her zaman parçalanma gerçekleştirmesi gerekir). UDP için 64 KB'lik bir arabellek kullanırsanız her zaman tasarruf edersiniz, çünkü hiçbir IP paketinin (v4 veya v6) boyutu 64 KB'nin üzerinde olamaz (parçalanmış olsa bile) ve bu IIRC başlıklarını bile içerir, böylece veriler her zaman 64 KB'nin altında kesinlikle.
Mecki

1
@caf recv () 'e yapılan her çağrıda tamponu boşaltmanız gerekiyor mu? Kod döngüsünü gördüm ve verileri topladım ve daha fazla veri toplayacak şekilde tekrar döngüye aldım. Ama eğer arabellek dolarsa, yazma nedeniyle bellek ihlalini önlemek için arabellek için ayrılan bellek miktarını geçmesine gerek yok mu?
Alex_Nabu

1
@Alex_Nabu: İçinde biraz boşluk kaldığı sürece onu boşaltmanıza gerek yok ve kalan alandan recv()daha fazla bayt yazmayı söylemiyorsunuz .
kafe

16

TCP gibi akış protokolleri için, tamponunuzu herhangi bir boyuta hemen hemen ayarlayabilirsiniz. Bununla birlikte, 4096 veya 8192 gibi 2'nin üsleri olan ortak değerler önerilir.

Arabelleğinizden daha fazla veri varsa, bir sonraki aramanız için çekirdeğe kaydedilecektir recv.

Evet, tamponunuzu büyütmeye devam edebilirsiniz. Tamponun ortasında ofsetten başlayarak bir recv yapabilirsiniz idx, bunu yaparsınız:

recv(socket, recv_buffer + idx, recv_buffer_size - idx, 0);

6
İkinin gücü, birçok yönden daha verimli olabilir ve şiddetle tavsiye edilir.
Yann Ramin

3
@theatrus üzerinde ayrıntılı olarak, dikkate değer bir verimlilik, modulo operatörünün bitsel ve bir maskeyle (örneğin x% 1024 == x & 1023) değiştirilebilmesi ve tamsayı bölmesinin bir sağa kaydırma işlemiyle (örneğin, x / 1024 = = x / 2 ^ 10 == x >> 10)
vicatcu

15

Bir SOCK_STREAMsoketiniz varsa recv, akıştan "ilk 3000 bayta kadar" alırsınız . Tamponun ne kadar büyük olacağına dair net bir rehber yoktur: Bir akışın ne kadar büyük olduğunu bildiğiniz tek an, her şeyin bittiği zamandır ;-).

Bir SOCK_DGRAMsoketiniz varsa ve verikatarı tampondan büyükse, tamponu recvdatagramın ilk kısmı ile doldurur, -1 döndürür ve errno'yu EMSGSIZE olarak ayarlar. Ne yazık ki, protokol UDP ise, bu, verikatarının geri kalanının kaybolduğu anlamına gelir - UDP'nin neden güvenilmez bir protokol olarak adlandırılmasının bir parçası ( güvenilir datagram protokolleri olduğunu biliyorum, ancak bunlar çok popüler değil - yapamadım ikincisini iyi bilmenize rağmen, TCP / IP ailesinde bir isim verin ;-).

Bir tamponu dinamik olarak büyütmek için, başlangıçta onu tahsis edin mallocve reallocgerektiğinde kullanın . Ancak bu, recvbir UDP kaynağından size yardımcı olmayacaktır .


7
UDP her zaman en fazla bir UDP paketi döndürdüğünden (birden çok soket arabelleğinde olsa bile) ve hiçbir UDP paketi 64 KB'nin üzerinde olamayacağından (bir IP paketi, parçalanmış olsa bile en fazla 64 KB olabilir), 64 KB arabellek kullanmak kesinlikle güvenlidir ve bir UDP soketindeki bir geri alma sırasında hiçbir veriyi kaybetmemenizi garanti eder.
Mecki

7

İçin SOCK_STREAMsadece bekleyen bayt bazı çekerek ve bir sonraki görüşme daha alabilir çünkü soket, tampon boyutu, gerçekten önemli değil. Sadece karşılayabileceğiniz tampon boyutunu seçin.

İçin SOCK_DGRAMsoket, sen bekleme mesajın uydurma bölümünü alacak ve dinlenme atılır. Bekleyen datagram boyutunu aşağıdaki ioctl ile alabilirsiniz:

#include <sys/ioctl.h>
int size;
ioctl(sockfd, FIONREAD, &size);

Alternatif olarak , bekleyen datagram boyutunu elde etmek için çağrının MSG_PEEKve MSG_TRUNCbayraklarını kullanabilirsiniz recv().

ssize_t size = recv(sockfd, buf, len, MSG_PEEK | MSG_TRUNC);

İhtiyacınız MSG_PEEKpeek (almazsa) bekleyen ileti - recv döner gerçek değil kesilmiş boyut; ve MSG_TRUNCmevcut arabelleğinizi taşmamalısınız.

O zaman sadece malloc(size)gerçek tampon ve recv()datagram yapabilirsiniz.


MSG_PEEK | MSG_TRUNC hiçbir anlam ifade etmiyor.
Lorne Markisi

3
MSG_PEEK'in bekleyen mesaja göz atmasını (almamasını), boyutunu elde etmesini (recv kısaltılmamış boyuta döndürür) ve MSG_TRUNC'nin geçerli arabelleğinizi taşmaması için ihtiyacınız var. Boyutu elde ettiğinizde, doğru tamponu ayırırsınız ve bekleme mesajını alırsınız (gözetlemezsiniz, kesmezsiniz).
smokku

@Alex Martelli 64KB'nin bir UDP paketinin maksimum boyutu olduğunu söylüyor, bu yüzden 64KB'lik malloc()bir arabellek için o MSG_TRUNCzaman gereksiz mi?
mLstudent33

1
IP protokolü parçalanmayı destekler, bu nedenle veri birimi tek bir paketten daha büyük olabilir - parçalanır ve birden çok paket halinde iletilir. Ayrıca SOCK_DGRAMsadece UDP değil.
smokku

1

Sorunuzun kesin bir cevabı yoktur, çünkü teknoloji her zaman uygulamaya özgü olmak zorundadır. UDP'de iletişim kurduğunuzu varsayıyorum çünkü gelen arabellek boyutu TCP iletişimine sorun çıkarmaz.

RFC 768'e göre , UDP için paket boyutu (başlık dahil) 8 ila 65 515 bayt arasında değişebilir. Bu nedenle, gelen arabellek için arızaya dayanıklılık boyutu 65.507 bayttır (~ 64KB)

Ancak, tüm büyük paketler ağ aygıtları tarafından düzgün bir şekilde yönlendirilemez, daha fazla bilgi için mevcut tartışmaya bakın:

Maksimum verim için bir UDP paketinin en uygun boyutu nedir?
İnternetteki en büyük Güvenli UDP Paket Boyutu nedir


-4

16kb hemen hemen doğru; gigabit ethernet kullanıyorsanız, her paketin boyutu 9 kb olabilir.


3
TCP soketleri akışlardır, yani bir recv birden çok paketten toplanan verileri döndürebilir, dolayısıyla paket boyutu TCP için tamamen alakasızdır. UDP durumunda, her bir geri alma çağrısı en fazla tek bir UDP paketi döndürür, burada paket boyutu ilgilidir ancak doğru paket boyutu yaklaşık 64 KB'dir, çünkü gerekirse bir UDP paketi parçalanabilir (ve genellikle olacaktır). Bununla birlikte, hiçbir IP paketi 64 KB'nin üzerinde olamaz, parçalanmayla bile, bu nedenle bir UDP soketindeki recv en fazla 64 KB döndürebilir (ve döndürülmeyenler mevcut paket için atılır!)
Mecki
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.