为什么对 addr 变量进行类型转换?

Why is the addr variable type-converted?

本文关键字:类型转换 变量 addr 为什么      更新时间:2023-10-16

在互联网上冲浪时,我发现了一个发送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.1nHOST: retran.comnn";
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;
}

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

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

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

因此,(sockaddr*)&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;
}
}

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

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 *的,省略了显式强制转换的需要,那就更简单了。

返回窗口的连接功能 ::connect

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

它需要 sockaddr 作为参数类型