八月 24, 2010

【原】从sockaddr中取得Ip地址和端口号

Written by

在socket编程中,服务器端accept()等待一个客户端的连接,当连接成功后,accept拷贝客户端的地址信息到sockaddr里面。我们知道,由于sockaddr只包含了socket family和元数据两个字段,无法直接得到IP信息的,所以我们无法直接从一个sockaddr中取得IP信息的,只能通过一定的转换才能达到目的。

我们如何从sockaddr取得此客户端的Ip地址和端口号呢?

实际上,当sockaddr_in.sin_family = AF_INET时,sockaddr = sockaddr_in。我们先分别看看sockaddr和sockaddr_in的内存布局:

sockaddr内存布局:

/* Structure describing a generic socket address.  */
struct sockaddr
  {
    __SOCKADDR_COMMON (sa_); /* Common data: address family and length.  */
    char sa_data[14];  /* Address data.  */
  };

 
这个没有什么需要特别说明的,它的结构非常简单,只包含了一个socket family的sa和char型的data数据块。

sockaddr_in内存布局:


/* Structure describing an Internet socket address.  */
struct sockaddr_in
  {
    __SOCKADDR_COMMON (sin_);
    in_port_t sin_port;   /* Port number.  */
    struct in_addr sin_addr;  /* Internet address.  */

    /* Pad to size of `struct sockaddr'.  */
    unsigned char sin_zero[sizeof (struct sockaddr) -
      __SOCKADDR_COMMON_SIZE -
      sizeof (in_port_t) -
      sizeof (struct in_addr)];
  };

其中有个in_addr结构体如下定义:

/* Internet address. */
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};

可见,sockaddr_in包含了四个字段:(sin_)、sin_port、 sin_addr、sin_zero,据此,我们就可以很方便的取得port和addr了。

port不用说,直接一个uint16:

typedef uint16_t in_port_t;

sin_addr需要用 inet_ntoa()转换一下,因为他是

typedef uint32_t in_addr_t;

上面我们也看到了。

需要着重说明的是最后一个字段:sin_zero,就是它保证了sockaddr与sockaddr_in的一致性:sockaddr和sockaddr_in的socket family标记是第一个字段,sockaddr的第二个字段是char sa_data[14],大小为14个字节,而sockaddr_in的第二个字段port、第三个字段addr和第四个字段组合,大小与sockaddr的第二个字段一致,所以,我们可以把一个sockaddr直接memcpy到sockaddr_in!

下面是一段代码演示了如何从sin_addr获取Ip、port的详细步骤:


int new_fd = accept(sock, &clientAddr, &sin_size);
if(new_fd<0)
{
char msg[64];
bzero(msg,sizeof(msg));
sprintf(msg,"accept failed");
log::outputSysErr(msg);
}
else
{
// 将sockaddr强制转换为 sockaddr_in
sockaddr_in sin;
memncpy(&sin, &clientAddr, sizoef(sin));
// 取得ip和端口号
sprintf(info.ip, inet_ntoa(sin.sin_addr));
info.port = sin.sin_port;
info.sock = new_fd;
}

上面说的“转换”看起来是不是有些奇怪?实际上,你可以通过真正意义上的强制转来转换:

sockaddr_in* pSin = (sockaddr_in*)&clientAddr; 

而第一种方法,间接说明了另外一个意思:他们占用的内存大小是一样的,当sockaddr_in.sin_family = AF_INET时,他们的内存布局也一样的!看看sockaddr结构体自身就知道了,它仅仅是个char数组,大小与sockaddr_in等同。正因如此,我们才可以像第一种方法那样,直接将一个sockaddr数据拷贝到sockaddr_in变量中。

Category : C/C++Linux/Unix

Tags :

发表评论

电子邮件地址不会被公开。

Proudly powered by WordPress and Sweet Tech Theme