【问题标题】:Why is the addr variable type-converted?为什么 addr 变量要进行类型转换?
【发布时间】:2014-04-13 22:29:12
【问题描述】:

在网上冲浪时,我发现了一个发送 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*)&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;
}

【问题讨论】:

  • 因为connect() 需要sockaddr 类型作为参数。如此处所示,int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • 我明白了,但是为什么要定义一个端口等然后更改结构以使其不再包含这些信息。我错过了什么吗?
  • 您可能忽略了一个事实,即有多个套接字地址族,需要不同的信息,但都使用相同的函数组。

标签: c++ sockets network-programming


【解决方案1】:

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

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

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

所以,(sockaddr*)&amp;addr 是一个向上转换,产生一个指向有效基类子对象的指针。当您将其传递给 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;
  }
}

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 我明白了,但是为什么要定义一个端口等然后更改结构以使其不再包含这些信息。我错过了什么吗?
    • 它没有被改变。正在转换指针,但所有相同的信息仍在内存中。 connect 将根据 sin_familyAF_INET 的事实将其转换回 sockaddr_in
    【解决方案3】:
    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 网络编程”中所说的:

    通用套接字地址结构

    当作为参数传递给 任何套接字函数。但是任何将这些指针之一作为 参数必须处理来自支持协议的 any 的套接字地址结构 家庭。 在如何声明所传递的指针的类型时出现了一个问题。使用 ANSI C, 解决方案很简单:void * 是通用指针类型。但是,套接字函数早于 ANSI C,1982 年选择的解决方案是在 &lt;sys/socket.h&gt; 标头 (...)

    中定义一个通用套接字地址结构
    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 函数原型中的 bind 函数:

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

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

    【讨论】:

    • 你有点倒退了。 sockaddr 通常用于套接字。 sockaddr_in 用于 IP,还有其他类型用于其他(非 IP)协议。
    • 这正是我所说的
    • 您的措辞不清楚 - 您在显示通用 sockaddr 结构后立即说“IP 地址”,并说 sockaddr_in 是“专门用于网络(技术上 i> 是的,但重点是sockaddr_in 是用于 IP)。
    • @AndrewMedico 是的,我已经更正了,谢谢
    【解决方案4】:

    回到windows的connect函数::connect

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

    需要 sockaddr 作为参数类型

    【讨论】:

    • 我明白了,但是为什么要定义一个端口等然后更改结构以使其不再包含这些信息。我错过了什么吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多