关于sockaddr_in结构与sockaddr的异同问题

在socket编程中经常会看到这样的情况:

BOOL client(UINT uPort, LPCTSTR lpIpAddr) {
    SOCKET sok = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    int Ret = 0;

    if (socket == INVALID_SOCKET)
        return FALSE;
    sockaddr_in server = {0};
    server.sin_family = AF_INET;
    server.sin_port = htons(uPort);
    server.sin_addr.s_addr = inet_addr(lpIpAddr);
    
    //这里的(sockaddr *)&server,把sockaddr_in地址强制转成sockaddr类型地址
    if ((Ret = connect(sok, (sockaddr *)&server, sizeof(server))) != SOCKET_ERROR) {
        ...;
    }
    closesocket(sok);

    return TRUE;
}

这是一个客户端,可以看到在connect中,把sockaddr_in类型地址转成了sockaddr类型地址,这是为何? 首先来看一下connect函数的原型:

离一下题, 可以看到第二个形参是一个指向sockaddr的常量指针,可能会有疑惑为何不写成这样:

connect(sok, (const sockaddr *)&server, sizeof(server));

这样不是更符合吗?  但因为加了const后的指针可以接受非const类型的变量,反过来不行(非const类型指针不能指向const类型变量)。所以这里是可以的,当然你这么写也没问题。

 

言归正传,从API语法来看,这样子没毛病,但让人困惑的是sockaddr与sockaddr_in这样直接强转没问题吗?里面结构的内容是什么,在这之前先把关于Windows版本的一些预定义量给出:

#define _WIN32_WINNT_WIN6                   0x0600 // Windows Vista 
#define _WIN32_WINNT_VISTA                  0x0600 // Windows Vista 
#define _WIN32_WINNT_WS08                   0x0600 // Windows Server 2008 
#define _WIN32_WINNT_LONGHORN               0x0600 // Windows Vista

接下来看一下这两个结构中的内容:

// 这边是sockaddr_in结构
// IPv4 Socket address, Internet style
//

typedef struct sockaddr_in {

#if(_WIN32_WINNT < 0x0600)
    short   sin_family;    
#else //(_WIN32_WINNT < 0x0600)
    ADDRESS_FAMILY sin_family; //typedef USHORT ADDRESS_FAMILY
#endif //(_WIN32_WINNT < 0x0600)

    USHORT sin_port;
    IN_ADDR sin_addr;  //typedef in_addr IN_ADDR
    CHAR sin_zero[8];
} SOCKADDR_IN, *PSOCKADDR_IN;


// 这里是in_addr结构
// IPv4 Internet address
// This is an 'on-wire' format structure.
//
typedef struct in_addr {
        union {
                struct { UCHAR s_b1,s_b2,s_b3,s_b4; } S_un_b;
                struct { USHORT s_w1,s_w2; } S_un_w;
                ULONG S_addr;
        } S_un;
#define s_addr  S_un.S_addr /* can be used for most tcp & ip code */
#define s_host  S_un.S_un_b.s_b2    // host on imp
#define s_net   S_un.S_un_b.s_b1    // network
#define s_imp   S_un.S_un_w.s_w2    // imp
#define s_impno S_un.S_un_b.s_b4    // imp #
#define s_lh    S_un.S_un_b.s_b3    // logical host
} IN_ADDR, *PIN_ADDR, FAR *LPIN_ADDR;

首先看一下sockaddr_in结构,这个结构中首先是预定义选择语句。

显然根据我们上面的预定义值我们可以知道会进入#else分支,但那没什么影响只是short是否是无符号数的区别。这个地方我们一般都填AF_INET。

接着就是sin_port, sin_addr, 注意到最后一个字段是sin_zero[8],这8个字节实际上没有任何作用,只是为了填充并且与sockaddr对齐。

关于sin_addr,它是in_addr结构类型

这个类型中只有一个联合类型,请注意第一个预定义也就是:

#define s_addr  S_un.S_addr /* can be used for most tcp & ip code */
//这一步简化了下面那条语句
//这里本来应该是:
server.sin_addr.S_un.S_addr = inet_addr(lpIpAddr);

//由于那条预定义语句简化成这样
server.sin_addr.s_addr = inet_addr(lpIpAddr); 

也就是说in_addr中只使用了ULONG S_addr这个字段,可以把它想像成这样:

typedef struct in_addr {
        union {
                ULONG S_addr;
        } S_un;
#define s_addr  S_un.S_addr /* can be used for most tcp & ip code */
} IN_ADDR, *PIN_ADDR, FAR *LPIN_ADDR;

因为其他变量基本没有使用到,当然只能那么想而已。

再来看一下sockaddr结构的定义:

//
// Structure used to store most addresses.
//
typedef struct sockaddr {

#if (_WIN32_WINNT < 0x0600)
    u_short sa_family;
#else 
    ADDRESS_FAMILY sa_family;           // Address family.
#endif //(_WIN32_WINNT < 0x0600)

    CHAR sa_data[14];                   // Up to 14 bytes of direct address.
} SOCKADDR, *PSOCKADDR, FAR *LPSOCKADDR;

那条预定义选择语句与sockaddr_in一模一样,而sockaddr结构的后面部分显然比sockaddr_in结构简单许多, 它只有一个14字节的char数组。

也就是说,sockaddr与sockaddr_in的结构大小完全相同,而唯一的区别就是sa_family字段的后面部分,但是他们在内存中完全对齐。这也是为何sockaddr与sockaddr_in能够互相转换的原因。

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值