『转』Linux socket网络编程指南 22007/10/24 11:43 P.M.
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。很简单,不是吗? 你现在可以在流式套接字上发送数据和接收数据了。 你现在是 Unix 网络程序员了!
--------------------------------------------------------------------------------
sendto() 和 recvfrom()函数"这很不错啊",你说,"但是你还没有讲无连接数据报套接字呢?" 没问题,现在我们开始这个内容。既然数据报套接字不是连接到远程主机的,那么在我们发送一个包之 前需要什么信息呢? 不错,是目标地址!看看下面的: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()函数你已经整天都在发送 (send()) 和接收 (recv()) 数据了,现在你准备关 闭你的套接字描述符了。这很简单,你可以使用一般的 Unix 文件描述符 的 close() 函数:close(sockfd);它将防止套接字上更多的数据的读写。任何在另一端读写套接字的企 图都将返回错误信息。如果你想在如何关闭套接字上有多一点的控制,你可以使用函数 shutdown()。它允许你将一定方向上的通讯或者双向的通讯(就象close()一 样)关闭,你可以使用:int shutdown(int sockfd, int how);sockfd 是你想要关闭的套接字文件描述复。how 的值是下面的其中之 一:0 – 不允许接受1 – 不允许发送2 – 不允许发送和接受(和 close() 一样)shutdown() 成功时返回 0,失败时返回 -1(同时设置 errno。) 如果在无连接的数据报套接字中使用shutdown(),那么只不过是让 send() 和 recv() 不能使用(记住你在数据报套接字中使用了 connect 后 是可以使用它们的)。
--------------------------------------------------------------------------------
getpeername()函数这个函数太简单了。它太简单了,以至我都不想单列一章。但是我还是这样做了。 函数 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()函数甚至比 getpeername() 还简单的函数是 gethostname()。它返回你程 序所运行的机器的主机名字。然后你可以使用 gethostbyname() 以获得你 的机器的 IP 地址。下面是定义:#include <unistd.h>;int gethostname(char *hostname, size_t size);参数很简单:hostname 是一个字符数组指针,它将在函数返回时保存主机名。size是hostname 数组的字节长度。函数调用成功时返回 0,失败时返回 -1,并设置 errno。
--------------------------------------------------------------------------------
域名服务(DNS)如果你不知道 DNS 的意思,那么我告诉你,它代表域名服务(Domain Name Service)。它主要的功能是:你给它一个容易记忆的某站点的地址, 它给你 IP 地址(然后你就可以使用 bind(), connect(), sendto() 或者其它 函数) 。当一个人输入:$ telnet whitehouse.govtelnet 能知道它将连接 (connect()) 到 "198.137.240.100"。但是这是如何工作的呢? 你可以调用函数 gethostbyname():#include <netdb.h>;struct hostent *gethostbyname(const char *name);很明白的是,它返回一个指向 struct hostent 的指针。这个数据结构 是这样的:struct hostent {char *h_name;char **h_aliases;int h_addrtype;int h_length;char **h_addr_list;};#define h_addr h_addr_list[0]这里是这个数据结构的详细资料:struct hostent:h_name – 地址的正式名称。h_aliases – 空字节-地址的预备名称的指针。h_addrtype –地址类型; 通常是AF_INET。h_length – 地址的比特长度。h_addr_list – 零字节-主机网络地址指针。网络字节顺序。h_addr - h_addr_list中的第一地址。gethostbyname() 成功时返回一个指向结构体 hostent 的指针,或者 是个空 (NULL) 指针。(但是和以前不同,不设置errno,h_errno 设置错 误信息。请看下面的 herror()。)但是如何使用呢? 有时候(我们可以从电脑手册中发现),向读者灌输 信息是不够的。这个函数可不象它看上去那么难用。这里是个例子:C++代码
1. #include <stdio.h>;
2. #include <stdlib.h>;
3. #include <errno.h>;
4. #include <netdb.h>;
5. #include <sys/types.h>;
6. #include <netinet/in.h>;
7. int main(int argc, char *argv[])
8. {
9. struct hostent *h;
10. if (argc != 2) { /* 检查命令行 */
11. fprintf(stderr,"usage: getip address\n");
12. exit(1);
13. }
14. if ((h=gethostbyname(argv[1])) == NULL) { /* 取得地址信息 */
15. herror("gethostbyname");
16. exit(1); 1
7. }
18. printf("Host name : %s\n", h->;h_name);
19. printf("IP Address : %s\n",inet_ntoa(*((struct in_addr *)h->;h_addr)));
20. return 0;
21. }
在使用 gethostbyname() 的时候,你不能用 perror() 打印错误信息 (因为 errno 没有使用),你应该调用 herror()。相当简单,你只是传递一个保存机器名的字符串(例如 "whitehouse.gov") 给 gethostbyname(),然后从返回的数据结构 struct hostent 中获取信息。唯 一也许让人不解的是输出 IP 地址信息。h->;h_addr 是一个 char *, 但是 inet_ntoa() 需要的是 struct in_addr。因此,我转换 h->;h_addr 成 struct in_addr *,然后得到数据。
--------------------------------------------------------------------------------
客户-服务器背景知识这里是个客户--服务器的世界。在网络上的所有东西都是在处理客户进 程和服务器进程的交谈。举个telnet 的例子。当你用 telnet (客户)通过23 号端口登陆到主机,主机上运行的一个程序(一般叫 telnetd,服务器)激活。 它处理这个连接,显示登陆界面,等等。图2:客户机和服务器的关系图 2 说明了客户和服务器之间的信息交换。注 意,客户--服务器之间可以使用SOCK_STREAM、SOCK_DGRAM 或者其它(只要它们采用相同的)。一些很好的客户--服务器的例子有 telnet/telnetd、 ftp/ftpd 和 bootp/bootpd。每次你使用 ftp 的时候,在远 端都有一个 ftpd 为你服务。一般,在服务端只有一个服务器,它采用 fork() 来处理多个客户的连 接。基本的程序是:服务器等待一个连接,接受 (accept()) 连接,然后 fork() 一个子进程处理它。这是下一章我们的例子中会讲到的。
--------------------------------------------------------------------------------
简单的服务器这个服务器所做的全部工作是在流式连接上发送字符串 "Hello, World!\n"。你要测试这个程序的话,可以在一台机器上运行该程序,然后 在另外一机器上登陆:$ telnet remotehostname 3490remotehostname 是该程序运行的机器的名字。服务器代码:C++代码
1. #include <stdio