为什么addr变量是类型转换的?

时间:2014-03-03 14:19:01

标签: c++ sockets network-programming

在互联网上冲浪时,我发现了一个发送GET请求的c ++示例。我无法理解的代码部分是为什么(参见粗体部分)变量addr是类型转换的。我知道该函数只接受某种类型。但为什么要使用类型然后将其转换为另一种?

    #include <iostream>

    #include <sys/socket.h>
    #include <resolv.h>
    #include <arpa/inet.h>

    using namespace std;

    int main()

{
    int s, error;
    struct sockaddr_in addr;

    if((s = socket(AF_INET,SOCK_STREAM,0))<0)
    {
        cout<<"Error 01: creating socket failed!\n";
        close(s);
        return 1;
    }

    addr.sin_family = AF_INET;
    addr.sin_port = htons(80);
    inet_aton("204.27.61.92",&addr.sin_addr);

error = connect(s,( sockaddr *)&amp; addr ,sizeof(addr));

    if(error!=0)
    {
        cout<<"Error 02: conecting to server failed!\n";
        close(s);
        return 1;
    }

    char msg[] = "GET /beej/inet_ntoaman.html http/1.1\nHOST: retran.com\n\n";
    char answ[1024];
    //cin.getline(&msg[0],256);

    send(s,msg,sizeof(msg),0);

    while(recv(s,answ,1024,0)!=0)
        cout<<answ<<endl;

    close(s);
    cin.getline(&msg[0],1);
    return 0;
}

4 个答案:

答案 0 :(得分:2)

这是运行时多态,C风格。

sockaddr实际上是一个抽象基类 - 它并不特定于任何具体类型的套接字。但是,每种套接字类型都需要特定于类型的信息。

由于C没有多态性的语言支持,因此该技术是编写一个“派生”类型,它具有“基础”类型作为它的第一个数据成员。这样他们就有相同的地址,你可以在它们之间施放。

因此,(sockaddr*)&addr是一个upcast,产生一个指向有效基类子对象的指针。当您将其传递给AF_INET套接字时,它会将指针强制转换回(sockaddr_in*)并恢复特定于inet的数据。


等效C ++供参考,因为它更具表现力:

class SockAddr {
public:
  virtual ~SockAddr();
  enum Family { INET, UNIX, ... };
  Family get_family() const { return family; }
protected:
  explicit SockAddr(Family f) : family(f) {}
  Family family;
};

class SockAddrInet: public SockAddr {
  uint16_t port;
  uint32_t addr;
public:
  SockAddrInet(uint16_t port, uint32_t addr)
   : SockAddr(SockAddr::INET), port(htons(port)), addr(addr)
  {}
};    

class SockAddrUnix: public SockAddr {
  std::string path;
public:
  explicit SockAddrInet(std::string path)
   : SockAddr(SockAddr::UNIX), path(path) {}
};

void connect(Socket &s, SockAddr const &addr) {
  switch (addr.get_family()) {
  case SockAddr::INET:
    connect_inet(s, dynamic_cast<SockAddrInet const&>(addr));
    break;
  case SockAddr::UNIX:
    connect_unix(s, dynamic_cast<SockAddrUnix const&>(addr));
    break;
  }
}

答案 1 :(得分:1)

这是一种在C中使用“子类型”的技术。还有各种其他sockaddr类型(例如,IPv6的sockaddr_in6)。被调用的函数检查sin_family字段以确定传入哪种结构并相应地处理它。

答案 2 :(得分:1)

struct sockaddr {
    u_short sa_family;
    char    sa_data[14];
};

我们必须记住,套接字API可以使用许多不同的网络协议。每个协议的地址工作方式都有完全不同的格式。因此,此sockaddr结构是通用结构。它包含一个放置识别地址族的位置,以及可以放置地址的通用“数据”字段,而不管地址的格式如何。

但是sockaddr_in结构:

     struct sockaddr_in {            /* socket address (internet)   */
         short  sin_family;          /* address family (AF_INET)    */
       u_short  sin_port;            /* port number                 */
        struct  in_addr  sin_addr;   /* IP address                  */
          char  sin_zero[8];         /* reserved - must be 0x00's   */
     };

专为网络,IP地址而设计。此类型可以安全地类型转换为sockaddr*,因为它与它对齐。

这就是Richard Stevens在他的“UNIX网络编程”中所说的:

通用套接字地址结构

作为参数传递时,套接字地址结构总是通过引用传递 任何套接字功能。但任何套接字函数都将这些指针中的一个作为一个 参数必须处理来自支持协议的任何的套接字地址结构 家庭。 如何声明传递的指针类型会出现问题。使用ANSI C, 解决方案很简单:void *是通用指针类型。但是,套接字函数早于ANSI C,而1982年选择的解决方案是在<sys/socket.h>标题中定义通用套接字地址结构(...)

struct sockaddr {
    uint8_t     sa_len;
    sa_family_t sa_family;    /* address family: AF_xxx value */
    char        sa_data[14];  /* protocol-specific address */
};

然后将套接字函数定义为获取指向通用套接字地址的指针 结构,如此处所示,用于绑定函数的ANSI C函数原型:

int bind(int, struct sockaddr *, socklen_t);

这要求对这些函数的任何调用必须将指向特定于协议的套接字地址结构的指针强制转换为指向通用套接字地址结构的指针。 (......) 从内核的角度来看,使用指向通用套接字地址的指针的另一个原因 结构作为参数是内核必须接受调用者的指针,将其强制转换为struct sockaddr *,然后查看sa_family的值以确定结构的类型。 但是从应用程序员的角度来看,如果指针类型为void *则更简单,省略了显式转换的需要。

答案 3 :(得分:-1)

返回windows的connect函数:: connect

http://msdn.microsoft.com/en-us/library/windows/desktop/ms737625%28v=vs.85%29.aspx

它需要sockaddr作为参数类型