网络编程指南(1)

 

网络到本机字节顺序,

你能够转换两种类型: 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。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值