winsock编程宝典之获取网络资讯(有修改)

来源:互联网 发布:全景图 球形投影算法 编辑:程序博客网 时间:2024/05/10 04:44

转载:http://blog.csdn.net/msgsnd/article/details/2153768

获取网路资讯

在前两期的文章中,笔者介绍了如何在 Winsock 环境下建立主从架构的TCP Socket,以及如何利用 Socket 来收送资料;今天,我们接著来看一看如何利用 Winsock 所提供的函式来取得一些基本的网路资料,包括我们本身主机的名称是什么吗、系统主动指定给我们的 Socket 的 IP 位址及 端口号;我们的 Socket 所连接的对方是谁、如何查得某些主机的 IP 位址或名称、以及某些well-known 服务(如 ftp、telnet 等)所用的 port number 是哪一个等等。

今天我们使用的展示程式是笔者以前所撰写的一个针对 Winsock 1.1 的 46 个函式做测试或教学用的程式,有兴趣了解 46 个函式该如何呼叫的读者,可用anonymous ftp 方式到「tpts1.seed.net.tw」的「UPLOAD/WINKING/JNLIN」目录下取得此程式的执行档及原始程式码,档名为 hello.*。读者们也可利用hello 程式来模拟 Server 或 Client 程式,以验证我们所做的动作。

【如何知道我们所使用的 local 主机名称】

通常我们都会帮我们自己所使用的这台主机设定一个名称;在程序中,我们也可以透过 Winsock 所提供的一个称为 gethostname()的函式来取得这一个主机名称。

gethostname():获取目前使用者使用的 local host 的名称。

格 式: 

int gethostname( char FAR *name, int namelen);

参 数: 

name 用来存放 local host 名称的暂存区

namelen name 的大小

传回值: 成功 – 0

失败 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因)

说明: 此函式用来获取 local host 的名称。在程序中我们呼叫的方法如下:

gethostname( (char FAR *) hname, sizeof(hname) )

读者们如果使用过 Trumpet Winsock 的话,可能知道 Trumpet 的环境设定中并没有让我们设定 local host 名称的栏位,所以在执行一些 Public Domain 的Winsock 应用程序(如 ws_ping、wintalk)时,在呼叫 gethostname() 时会产生错误;解决的方法是在 Trumpet 的 「hosts」 档中加上您的主机 IP 位址及名称,那么呼叫这个函式时就不会再产生错误了。

【如何得知系统主动指定给我们的 IP 位址及端口号】

方法一:

以前的文章中,笔者曾提到 Client 端的 TCP Socket 在呼叫 connect() 函式去连接 Server 端之前,可以呼叫 bind() 函式来指定 Client 端 Socket 所用的IP 位址及 port number;但是一般而言,我们 Client 端并不需要呼叫 bind() 来指定特定的 IP 位址及 port number 的,而是交由系统主动帮我们的 Socket 设定 IP 位址及端口号 (呼叫 connect() 函数的时候)。但是我们如何得知系统指定了什么IP位址及 port number 给我们呢?这就要借助 getsockname() 这个函式了。

getsockname():获取 Socket 的 Local 位址及 端口号

格式: 

int getsockname( SOCKET s, struct sockaddr FAR *name, int FAR *namelen );

参 数: 

s Socket 的识别码

name 存放此 Socket 的 Local 位址的暂存区

namelen name 的长度

传回值: 

成功 – 0

失败 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因)

说明: 此函数是用来取得已设定位址或已连接之 Socket 的本端位置信息。若是此 Socket 被设定为 INADDR_ANY,则需等真正建立连接成功后才会传回正确的位置。

在程序中呼叫的方法为:

struct sockaddr_in sa;int salen = sizeof(sa);getsockname( sd, (struct sockaddr FAR *)&sa, &salen )

方法二:

通过使用函数gethostbyname()函数来检索主机名上所绑定的终端

gethostbyname()函数:检索主机名所对应的所有IP及端口

格式:

struct hostent FAR *gethostbyname( const char FAR*name );

返回结构体类型:

struct hostent

 { char FAR * h_name; 

 char FAR * FAR * h_aliases;

 short h_addrtype; 

 short h_length; 

 char FAR * FAR * h_addr_list;};

下面的代码显示主机上所有使用的IP

char szHost[128];::gethostname(szHost, 128);hostent *pHost = ::gethostbyname(szHost);in_addr addr;for(int i = 0; ; i++){char *p = pHost->h_addr_list[i];if(p == NULL)break;memcpy(&addr.S_un.S_addr, p, pHost->h_length);printf(" bind to local address -> %s:%ld \n", ::inet_ntoa(addr), SERVER_PORT);}

【如何知道和我们的 Socket 连接的对方是谁】

连接的 Socket 是有两端的,所以相对于 getsockname() 函式,Winsock 也提供了一个 getpeername() 函式,来让我们获得与我们连接的对方的 IP 与端口号。

getpeername():获取连接成功之 Socket 的对方 IP 位址及 port number

格 式: 

int getpeername( SOCKET s, struct sockaddr FAR *name, int FAR *namelen );

参 数:

s Socket 的识别码

name 储存与此 Socket 连接的对方 IP 位址的暂存区

namelen name 的长度

传回值: 

成功 – 0

失败 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因)

说明:

 此函式可用来取得已连接成功的 Socket 的彼端之位置信息。

呼叫的方式如下:

struct sockaddr_in sa;int salen = sizeof(sa);getpeername( sd, (struct sockaddr FAR *)&sa, &salen )

现在我们仍然利用 WinKing 来当我们的 Winsock Stack,并利用它所提供的工具来观察 Sockets 的连结及资料是否正确。由图1,我们可以由 WinKing 的视窗看到我们设定这台主机的名称是「vincent」,IP 位址是 「140.92.61.24」。我们并利用两个hello 程式,一个当成 Client (画面右边打开者),一个当成 Server (画面左边最小化者)。Server所用的 port number 是 「7016」; Client 并没有呼叫 bind() 来指定 port number,而是呼叫 connect() 时由系统指定。我们呼叫 gethostname(),得到的答案是 「vincent」;而 Client 呼叫getsockname() 得到自己的 IP 位址是 「140.92.61.24」,port number 是 「2110」(笔者以前曾提过,由系统主动指定的 port number 会介於 1024 到 5000 间);再呼叫 getpeername() 得到与 Client 连接的Server 端 IP 位址是 「140.92.61.24」(因为我们的 Client 和 Server 都在同一台主机),port number 是 「7016」。果然没错!(由 WinKing 的 Sockets' Status 视窗亦可观察到相互连接的 Sockets 资料,与我们呼叫函式所得结果相同)

读者必须注意一点:

getsockname() 及 getpeername() 所取得的 IP 位址及端口号 都是 network byte order,而不是host byte order;如果您想转成 host byte order,就必须借ntohl() 及 ntohs() 两个函数。

而我们能看到 IP位置以「字串」方式表达出来,则又是利用了 inet_ntoa() 函数;相对地,我们也可利用inet_addr() 函数将字串方式的 IP 位址转换成in_addr 格式(network byte order 的unsigned long)。

1. inet_ntoa():将一网路位址转换成「点格式」字串。

格 式: char FAR * PASCAL FAR inet_ntoa( struct in_addr in );

参 数: in 一个代表 Internet host 位址的结构

传回值: 成功 - 一个代表位址的「点格式」(dotted) 字串

失败 – NULL

说明: 此函式将一 Internet 位址转换成「a.b.c.d」字串格式。

struct sockaddr {

        u_short sa_family;              /* address family */

        char    sa_data[14];            /* up to 14 bytes of direct address */

};

struct in_addr {

        union {

                struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;

                struct { u_short s_w1,s_w2; } S_un_w;

                u_long 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 */

};

struct sockaddr_in {

        short   sin_family;

        u_short sin_port;

        struct  in_addr sin_addr;

        char    sin_zero[8];

};

 2. inet_addr():将字串格式的位址转换成 32 位元 in_addr 的格式。

格 式: unsigned long PASCAL FAR inet_addr( const char FAR *cp ); 
参 数: cp 一个代表 IP 位址的「点格式」(dotted) 字串 
传回值: 成功 - 一个代表 Internet 位址的 unsigned long 
失败 - INADDR_NONE 
说明: 此函式将一「点格式」的位址字串转换成适用之 Intenet 位址。 
「点格式」字串可为以下四种方式之任一: 
(i) a.b.c.d (ii) a.b.c (iii) a.b (iv) a

图 1 的 hello 程式中,我们将 Local 资料写到 dispmsg 中,再显示出来;其

用法如下:

wsprintf((LPSTR)dispmsg, "OK! local ip=%s, local port=%d", 
inet_ntoa(sa.sin_addr), ntohs(sa.sin_port));