网络到本机字节顺序,
你能够转换两种类型: short (两个字节)和 long (四个字节)。这个 函数对于变量类型 unsigned 也适用。假设你想将 short 从本机字节顺序 转换为网络字节顺序。用 "h" 表示 "本机 (host)",接着是 "to",然后用 "n" 表示 "网络 (network)",最后用 "s" 表示 "short": h-to-n-s, 或者 htons() ("Host to Network Short")。
太简单了...
如果不是太傻的话,你一定想到了组合 "n","h","s",和 "l"。但是这里没有 stolh() ("Short to Long Host") 函数,但是这里有:
htons()--"Host to Network Short"
htonl()--"Host to Network Long"
ntohs()--"Network to Host Short"
ntohl()--"Network to Host Long"
最后一点:为什么在数据结构 struct sockaddr_in 中, sin_addr 和 sin_port 需要转换为网络字节顺序,而 sin_family 不需要呢? 答案是:sin_addr 和 sin_port 分别封装在包的 IP 和 UDP 层。因此,他们必须要是网络字节顺序。 但是 sin_family 域只是被内核 (kernel) 使用来决定在数据结构中包含什么 类型的地址,所以他应该是本机字节顺序。也即 sin_family 没有 发 送到网络上,他们可以是本机字节顺序。
inet_addr() 返回的地址已经是按照网络字节顺序的,你没有必要再去调用 htonl()。
首先,假设你用 struct sockaddr_in ina,你想将 IP 地址 "132.241.5.10" 储存到其中。你要用的函数是 inet_addr(),转换 numbers-and-dots 格式的 IP 地址到 unsigned long。这个工作可以这样来做:
ina.sin_addr.s_addr = inet_addr("132.241.5.10");
上面的代码可不是很健壮 (robust),因为没有错误检查。inet_addr() 在发生错误 的时候返回-1。记得二进制数吗? 在 IP 地址为 255.255.255.255 的时候返回的是 (unsigned)-1!这是个广播地址!记住正确的使用错误检查。
你现在可以转换字符串形式的 IP 地址为 long 了。那么你有一个数据结构 struct in_addr,该如何按照 numbers-and-dots 格式打印呢? 在这个 时候,也许你要用函数 inet_ntoa() ("ntoa" 意思是 "network to ascii"):
printf("%s",inet_ntoa(ina.sin_addr));
他将打印 IP 地址。注意的是:函数 inet_ntoa() 的参数是 struct in_addr,而不是 long。同时要注意的是他返回的是一个指向字符的指针。 在 inet_ntoa 内部的指针静态地储存字符数组,因此每次你调用 inet_ntoa() 的时候他将覆盖以前的内容。
例如:char *a1, *a2;
a1 = inet_ntoa(ina1.sin_addr); /* this is 198.92.129.1 */
a2 = inet_ntoa(ina2.sin_addr); /* this is 132.241.5.10 */
printf("address 1: %s\n",a1);
printf("address 2: %s\n",a2);
运行结果是:
address 1: 132.241.5.10
address 2: 132.241.5.10
如果你想保存地址,那么用 strcpy() 保存到自己的字符数组中。
-------------------------------------------------------------------------------
socket()--得到文件描述符!
bind()--我在哪个端口?
connect()--Hello!
listen()--有人给我打电话吗?
accept()--"Thank you for calling port 3490."
send() 和 recv()--Talk to me, baby!
sendto() 和 recvfrom()--Talk to me, DGRAM-style
close() 和 shutdown()--滚开!
getpeername()--你是谁?
gethostname()--我是谁?
DNS--你说“白宫”,我说 "198.137.240.100"
select()--多路同步 I/O,酷!
——————————————————————————————————————
bind()
在 bind() 主题中最后要说的话是,在处理自己的 IP 地址和/或端口的时候,有些工作 是可以自动处理的。
my_addr.sin_port = 0; /* choose an unused port at random */
my_addr.sin_addr.s_addr = INADDR_ANY; /* use my IP address */
通过将0赋给 my_addr.sin_port,你告诉 bind() 自己选择合适的端口。同样, 将 my_addr.sin_addr.s_addr 设置为 INADDR_ANY,你告诉他自动填上 他所运行的机器的 IP 地址。
在你调用 bind() 的时候,你要小心的另一件事情是:不要采用小于1024的端口号。所有小于1024的端口号都 被系统保留!你可以选择从1024到65535(如果他们没有被别的程序使用的话)。
你要注意的另外一件小事是:有时候你根本不需要调用他。如果你使用 connect() 来 和远程机器通讯,你不要关心你的本地端口号(就象你在使用 telnet 的时候),你只要 简单的调用 connect() 就够可,他会检查套接口是否绑定,如果没有,他会自己绑定 一个没有使用的本地端口。
————————————————————————————————————————————————
connect() 系统调用是这样的:
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
sockfd 是系统调用 socket() 返回的套接口文件描述符。serv_addr 是保存着目的地端口和 IP 地址的数据结构 struct sockaddr。addrlen 设置为 sizeof(struct sockaddr)。
——————————————————————————————————
系统调用 listen 相当简单。
int listen(int sockfd, int backlog);
sockfd 是调用 socket() 返回的套接口文件描述符。backlog 是 在进入队列中允许的连接数目。是什么意思呢? 进入的连接是在队列中一直等待直到你接受 (accept() 请看下面的文章)的连接。他们的数目限制于队列的允许。大多数系统 的允许数目是20,你也可以设置为5到10。
在你调用 listen() 前你或者要调用 bind() 或者让 内核随便选择一个端口。如果你想侦听进入的连接,那么系统调用的顺序可能是这样的:
socket();
bind();
listen();
/* accept() goes here */
------------------------------------------------------------------------------------------------------------------
accept()--"Thank you for calling port 3490."
准备好了,系统调用 accept() 会有点古怪的地方的!你可以想象发生这样的事情: 有人从很远的地方通过一个你在侦听 (listen()) 的端口连接 (connect()) 到你的机器。他的连接将加入到等待接受 (accept()) 的队列中。你调用 accept() 告诉他你有空闲的连接。他将返回一个新的套接口文件描述符! 原来的一个还在侦听你的那个端口,新的最后在准备发送 (send()) 和接收 ( recv()) 数据。这就是这个过程!
函数是这样定义的:
#include <sys/socket.h>
int accept(int sockfd, void *addr, int *addrlen);
sockfd 相当简单,是和 listen() 中一样的套接口描述符。addr 是个指向局部的数据结构 struct sockaddr_in 的指针。This is where the information about the incoming connection will go (and you can determine which host is calling you from which port). 在他的地址传递给 accept 之前,addrlen 是个局部的整形变量,设置为 sizeof(struct sockaddr_in)。accept 将 不会将多余的字节给 addr。如果你放入的少些,那么在 addrlen 的值中反映 出来。
同样,在错误时返回-1并设置全局变量 errno。
(((((((((-----------------example-----(((((((((
SOCKET sockSrv=socket(AF_INET,SOCK_STREAM,0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);
bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
listen(sockSrv,5);
SOCKADDR_IN addrClient;
int len=sizeof(SOCKADDR);
while(1)
{
SOCKET sockConn=accept(sockSrv,(SOCKADDR*)&addrClient,&len);
char sendBuf[100];
sprintf(sendBuf,"Welcome %s to SiChuan University ",inet_ntoa(addrClient.sin_addr));
send(sockConn,sendBuf,strlen(sendBuf)+1,0);
char recvBuf[100];
recv(sockConn,recvBuf,100,0);
printf("%s\n",recvBuf);
closesocket(sockConn);
}
))))))))))))))))))
注意,在系统调用 send() 和 recv() 中你应该使用新的文件描述符。 如果你只想让一个连接进来,那么你可以使用 close() 去关闭原来的文件描述 符 sockfd 来避免同一个端口更多的连接。
------------------------------------------------
send() and recv()--Talk to me, baby!
这两个函数用于流式套接口和数据报套接口的通讯。如果你喜欢使用无连接的数据报 套接口,你应该看一看下面关于 sendto() 和 recvfrom() 的章节。
send() 是这样的:
int send(int sockfd, const void *msg, int len, int flags);
sockfd 是你想发送数据的套接口描述符(或者是调用 socket() 或者是 accept() 返回的。)msg 是指向你想发送的数据的指针。len 是 数据的长度。把 flags 设置为 0 就可以了。(详细的资料请看 send() 的 man page)。
send() 返回实际发送的数据的字节数--他可能小于你要求发送的数目! 也即你告诉他要发送一堆数据可是他不能处理成功。他只是发送他可能发送的数据,然后 希望你以后能够发送其他的数据。记住,如果 send() 返回的数据和 len 不 匹配,你应该发送其他的数据。但是这里也有个好消息:如果你要发送的包很小(小于大约 1K),他可能处理让数据一次发送完。最后,在错误的时候返回-1,并设置 errno。
recv() 函数很相似:
int recv(int sockfd, void *buf, int len, unsigned int flags);
sockfd 是要读的套接口描述符。buf 是要读的信息的缓冲。len 是 缓冲的最大长度。flags 也可以设置为0。(请参考recv() 的 man page。)
recv() 返回实际读入缓冲的数据的字节数。或者在错误的时候返回-1,同时设置 errno。
很简单,不是吗? 你现在可以在流式套接口上发送数据和接收数据了。
-------------------------------------------------------------------------------------------------------
sendto() 和 recvfrom()--Talk to me, DGRAM-style
"这很不错啊",我听到你说,"但是你还没有讲无连接数据报套接口呢。"没问题,现在我们开始 这个内容。
既然数据报套接口不是连接到远程主机的,那么在我们发送一个包之前需要什么信息呢? 不错,是目标地址!看看下面的:
int sendto(int sockfd, const void *msg, int len, unsigned int flags,
const struct sockaddr *to, int tolen);
你已经看到了,除了另外的两个信息外,其余的和函数 send() 是一样的。 to 是个指向数据结构 struct sockaddr 的指针,他包含了目的地的 IP 地址和端口信息。tolen 可以简单地设置为 sizeof(struct sockaddr)。
和函数 send() 类似,sendto() 返回实际发送的字节数(他也可能小于你 想要发送的字节数!),或者在错误的时候返回 -1。
相似的还有函数 recv() 和 recvfrom()。recvfrom() 的定义是 这样的:
int recvfrom(int sockfd, void *buf, int len, unsigned int flags
struct sockaddr *from, int *fromlen);
又一次,除了一点多余的参数外,这个函数和 recv() 也是一样的。from 是 一个指向局部数据结构 struct sockaddr 的指针,他的内容是源机器 的 IP 地址和端口信息。fromlen 是个 int 型的局部指针,他的初始值 为 sizeof(struct sockaddr)。函数调用后,fromlen 保存着 实际储存在 from 中的地址的长度。
recvfrom() 返回收到的字节长度,或者在发生错误后返回 -1。
记住,如果你是用 connect() 连接一个数据报套接口,你可以简单的调用 send() 和 recv() 来满足你的要求。这个时候依然是数据报套接口,依然使用 UDP,系统 自动的加上了目标和源的信息。
---------------------------------------------------------------------------------------------------------------------------------------------
close() 和 shutdown()--Get outta my face!
你已经整天都在发送 (send()) 和接收 (recv()) 数据了,现在你准备 关闭你的套接口描述符了。这很简单,你可以使用一般的 Unix 文件描述符的 close() 函 数:
close(sockfd);
他将防止套接口上更多的数据的读写。任何在另一端读写套接口的企图都将返回错误信息。
如果你想在如何关闭套接口上有多一点的控制,你可以使用函数 shutdown()。他能够让 你将一定方向的通讯或者双向的通讯(就象 close() 一样)关闭,你可以使用:
int shutdown(int sockfd, int how);
sockfd 是你想要关闭的套接口文件描述复。how 的值是下面的其中之一:
0 - Further receives are disallowed
1 - Further sends are disallowed
2 - Further sends and receives are disallowed (和 close() 一样
shutdown() 成功时返回 0,失败时返回 -1(同时设置 errno。)
如果在无连接的数据报套接口中使用 shutdown(),那么只不过是让 send() 和 recv() 不能使用(记得你在数据报套接口中使用了 connect 后是可以 使用他们的吗?)
----------------------------------------------------------------------------------------------------
getpeername()--Who are you?
函数 getpeername() 告诉你在连接的流式套接口上谁在另外一边。函数是这样的:
#include <sys/socket.h>
int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);
sockfd 是连接的流式套接口的描述符。addr 是一个指向结构 struct sockaddr (或者是 struct sockaddr_in) 的指针,他保存着 连接的另一边的信息。addrlen 是一个 int 型的指针,他初始化为 sizeof(struct sockaddr)。
函数在错误的时候返回 -1,设置相应的 errno。
一旦你获得他们的地址,你可以使用 inet_ntoa() 或者 gethostbyaddr() 来 打印或者获得更多的信息。但是你不能得到他的帐号。(如果他运行着愚蠢的守护进程,这是 可能的,但是他的讨论已经超出了本文的范围,请参考 RFC-1413 以获得更多的信息。)
------------------------------------------------------------------------------------------------------
gethostname()--Who am I?
甚至比 getpeername() 还简单的函数是 gethostname()。他返回你程序 所运行的机器的主机名字。然后
你可以使用 gethostbyname() 以获得你的机器的 IP 地址。
下面是定义:
#include <unistd.h>
int gethostname(char *hostname, size_t size);
参数很简单:hostname 是一个字符数组指针,他将在函数返回时保存 主机名。size 是 hostname 数组的字节长度。
函数调用成功时返回 0,失败时返回 -1,并设置 errno。