2010-10-27 18 views
6

C++ uygulamasında, 32 bit Linux'tan 32 bit FreeBSD 8.1'e geçiş yapıldığında ortaya çıkan bir arıza var. Bağlanamayan bir TCP soket bağlantım var. Connect() çağrısında, errno == EINVAL ile connect() 'ın man (page) for() sayfasının kapsamadığı bir hata sonucu aldım.Neden connect(), FreeBSD'ye bağlantı noktasında aralıklı EINVAL verir?

Bu hata ne anlama geliyor, hangi argüman geçersiz? Mesaj sadece "Geçersiz argüman" diyor.

family: AF_INET 
len: 16 
port: 2357 
addr: 10.34.49.13 

Her zaman olsa başarısız değildir: Burada

bağlantının bazı ayrıntılar vardır. FreeBSD sürümü, yalnızca makinenin birkaç saat boşta kalmasına izin verdikten sonra başarısız olur. Fakat bir kez başarısız olduktan sonra, uzun bir süre tekrar boşta kalmasına izin verene kadar güvenilir bir şekilde çalışır. İşte

kodunun bazı geçerli: Burada

void setSocketOptions(const int skt); 
void buildAddr(sockaddr_in &addr, const std::string &ip, 
       const ushort port); 
void deepBind(const int skt, const sockaddr_in &addr); 


void 
test(const std::string &localHost, const std::string &remoteHost, 
    const ushort localPort, const ushort remotePort, 
    sockaddr_in &localTCPAddr, sockaddr_in &remoteTCPAddr) 
{ 
    const int skt = socket(AF_INET, SOCK_STREAM, 0); 

    if (0 > skt) { 
    clog << "Failed to create socket: (errno " << errno 
     << ") " << strerror(errno) << endl; 
    throw; 
    } 

    setSocketOptions(skt); 

    // Build the localIp address and bind it to the feedback socket. Although 
    // it's not traditional for a client to bind the sending socket to a the 
    // local address, we do it to prevent connect() from using an ephemeral port 
    // which (our site's firewall may block). Also build the remoteIp address. 
    buildAddr(localTCPAddr, localHost, localPort); 
    deepBind(skt, localTCPAddr); 
    buildAddr(remoteTCPAddr, remoteHost, remotePort); 

    clog << "Info: Command connect family: " 
     << (remoteTCPAddr.sin_family == AF_INET ? "AF_INET" : "<unknown>") 
     << " len: " << int(remoteTCPAddr.sin_len) 
     << " port: " << ntohs(remoteTCPAddr.sin_port) 
     << " addr: " << inet_ntoa(remoteTCPAddr.sin_addr) << endl; 

    if (0 > ::connect(skt, (sockaddr*)& remoteTCPAddr, sizeof(sockaddr_in)))) { 
    switch (errno) { 
     case EINVAL: { 
     int value = -1; 
     socklen_t len = sizeof(value); 
     getsockopt(skt, SOL_SOCKET, SO_ERROR, &value, &len); 

     cerr << "Error: Command connect failed on local port " 
      << getLocFbPort() 
      << " and remote port " << remotePort 
      << " to remote host '" << remoteHost 
      << "' family: " 
      << (remoteTCPAddr.sin_family == AF_INET ? "AF_INET" : "<unknown>") 
      << " len: " << int(remoteTCPAddr.sin_len) 
      << " port: " << ntohs(remoteTCPAddr.sin_port) 
      << " addr: " << inet_ntoa(remoteTCPAddr.sin_addr) 
      << ": Invalid argument." << endl; 
     cerr << "\tgetsockopt => " 
      << ((value != 0) ? strerror(value): "success") << endl; 

     throw; 
     } 
     default: { 

     cerr << "Error: Command connect failed on local port " 
      << localPort << " and remote port " << remotePort 
      << ": (errno " << errno << ") " << strerror(errno) << endl; 
     throw; 
     } 
    } 
    } 
} 


void 
setSocketOptions(int skt) 
{ 
    // See page 192 of UNIX Network Programming: The Sockets Networking API 
    // Volume 1, Third Edition by W. Richard Stevens et. al. for info on using 
    // ::setsockopt(). 

    // According to "Linux Socket Programming by Example" p. 319, we must call 
    // setsockopt w/ SO_REUSEADDR option BEFORE calling bind. 
    int so_reuseaddr = 1; // Enabled. 
    int reuseAddrResult 
    = ::setsockopt(skt, SOL_SOCKET, SO_REUSEADDR, &so_reuseaddr, 
        sizeof(so_reuseaddr)); 

    if (reuseAddrResult != 0) { 
    cerr << "Failed to set reuse addr on socket."; 
    throw; 
    } 

    // For every two hours of inactivity, a keepalive occurs. 
    int so_keepalive = 1; // Enabled. See page 200 for info on SO_KEEPALIVE. 
    int keepAliveResult = 
    ::setsockopt(skt, SOL_SOCKET, SO_KEEPALIVE, &so_keepalive, 
       sizeof(so_keepalive)); 

    if (keepAliveResult != 0) { 
    cerr << "Failed to set keep alive on socket."; 
    throw; 
    } 

    struct linger so_linger; 

    so_linger.l_onoff = 1; // Turn linger option on. 
    so_linger.l_linger = 5; // Linger time in seconds. (See page 202) 

    int lingerResult 
    = ::setsockopt(skt, SOL_SOCKET, SO_LINGER, &so_linger, 
        sizeof(so_linger)); 

    if (lingerResult != 0) { 
    cerr << "Failed to set linger on socket."; 
    throw; 
    } 

    // Disable the Nagel algorithm on the command channel. SOL_TCP is not 
    // defined on FreeBSD 
#ifndef SOL_TCP 
#define SOL_TCP (::getprotobyname("TCP")->p_proto) 
#endif 

    unsigned int tcpNoDelay = 1; 
    int noDelayResult 
    = ::setsockopt(skt, SOL_TCP, TCP_NODELAY, &tcpNoDelay, 
        sizeof(tcpNoDelay)); 

    if (noDelayResult != 0) { 
    cerr << "Failed to set tcp no delay on socket."; 
    throw; 
    } 
} 

void 
buildAddr(sockaddr_in &addr, const std::string &ip, const ushort port) 
{ 
    memset(&addr, 0, sizeof(sockaddr_in)); // Clear all fields. 
    addr.sin_len = sizeof(sockaddr_in); 
    addr.sin_family = AF_INET;    // Set the address family 
    addr.sin_port = htons(port);   // Set the port. 

    if (0 == inet_aton(ip.c_str(), &addr.sin_addr)) { 
    cerr << "BuildAddr IP."; 
    throw; 
    } 
}; 

void 
deepBind(const int skt, const sockaddr_in &addr) 
{ 
    // Bind the requested port. 
    if (0 <= ::bind(skt, (sockaddr *)&addr, sizeof(addr))) { 
    return; 
    } 

    // If the port is already in use, wait up to 100 seconds. 
    int count = 0; 
    ushort port = ntohs(addr.sin_port); 

    while ((errno == EADDRINUSE) && (count < 10)) { 
    clog << "Waiting for port " << port << " to become available..." 
     << endl; 
    ::sleep(10); 
    ++count; 
    if (0 <= ::bind(skt, (sockaddr*)&addr, sizeof(addr))) { 
     return; 
    } 
    } 

    cerr << "Error: failed to bind port."; 
    throw; 
} 

örnek çıktısı olduğunda EINVAL (her zaman bazen başarılı ve şifreli alma soket üzerinden gönderilen ilk paket üzerinde başarısız, burada başarısız değil):

Info: Command connect family: AF_INET len: 16 port: 2357 addr: 10.34.49.13 
Error: Command connect failed on local port 2355 and remote port 2357 to remote host '10.34.49.13' family: AF_INET len: 16 port: 2357 addr: 10.34.49.13: Invalid argument. 
    getsockopt => success 
+0

Bazı kodlar görebilir miyiz lütfen? – blaze

+0

@blaze Kod örneği ekledim. – WilliamKF

+0

Sizin sockaddr init kodunuz (buildAddr) nedir? En azından bir işletim sistemi gördüm (ancak ne zaman elden hatırlayamıyorum) eğer nerede doldurma yapmazsanız, bu durum size şikayette bulunacaktır. IPv4 için hemen hemen tüm uygulamalarda aynı şekilde olduklarını düşünmeme rağmen gerçekten de gerçekten sizeof (sockaddr_in) geçmelisiniz. – tyranid

cevap

6

Sorunun ne olduğunu anladım, önce bir ECONNREFUSED alıyorum, Linux'u kısa bir duraklamadan sonra connect() 'i yeniden deneyebilirim, ancak FreeBSD'de aşağıdaki bağlantıyı tekrarla() EINVAL ile başarısız.

Çözüm, ECONNREFUSED'in daha fazla yedekleneceği ve bunun yerine yukarıda test() tanımının başına dönmeye başlamasıdır. Bu değişiklikle, kod artık düzgün çalışıyor.

3

FreeBSD connect() manpageEINVAL listelemek etmediğini ilginç. A different BSD manpage devletler: yüzen farklı BSD tatlar gelen farklı belgelere dayanarak

[EINVAL] An invalid argument was detected (e.g., address_len is 
      not valid for the address family, the specified 
      address family is invalid). 

, ben, FreeBSD'de dönüş kodu olanaklarını orada belgelenmemiş olabileceğini girişim örneğin here görecekti.

Tavsiyem, adres uzunluğunu ve sizeof numaranızı ve soket adres yapınızın içeriğini connect numaralı telefonu aramadan önce yazdırmaktır - bu, sorunun ne olduğunu bulmanıza yardımcı olacaktır.

Bunun ötesinde, bağlantıyı kurmak için kullandığınız kodu bize gösterirseniz, muhtemelen en iyisidir. Bu, soket adresi için kullanılan türü (struct sockaddr, struct sockaddr_in, vb.), Onu başlatan kodu ve gerçek çağrıyı connect olarak içerir. Bu, ona yardım etmeyi çok daha kolaylaştıracaktır.

+0

İsteğinize göre ayrıntılı kod ekledim. – WilliamKF

1

Yerel adresiniz nedir? Yalnızca kötü bir form gibi görünmeyen, ancak bu sorunun neden başlayabildiği bind(2) hatalarını sessizce görmezden geliyorsunuz!

+0

Hayır, bağlama() dönüşü dikkate alınmaz: cerr << "Hata: bağlantı noktası bağlanamadı."; atış; – WilliamKF

+0

Ah, haklısınız. Erken geri dönüşlerinizle karıştırıldım ve (düşündüğüm şey) koşullu şartları tersine çevirdim. Bir sabiti bir işleve veya syscall'a döndürmek için bir gerekçe olduğunu anlıyorum, fakat her modern derleyici, koşullu bir ifadenin içinde yanlışlıkla bir atama oluşturursanız sizi uyarır, bu yüzden insanların sadece kullanmasını isterdim (syscall()! = 1) '. –