1、Socket 编程知识必学来源: ChinaUnix 博客 日期: 2008.06.29 23:42 ( 共有条评论) 我要评论/* Author: cnscn* HOME: http:/scn.org* 整理自Linux 网络编程*/端口号常识:端口号被从1 开始分配。通常端口号超出255 的部分被本地主机保留为私有用途。1到255 之间的号码被用于远程应用程序所请求的进程和网络服务。每个网络通信循环地进出主计算机的 TCP 应用层。它被两个所连接的号码唯一地识别。这两个号码合起来叫做套接字.组成套接字的这两个号码就是机器的 IP 地址和 TCP 软件所使用的端口号。套接字的三种类型流式套接
2、字(SOCK_STREAM ) ,数据报套接字(SOCK_DGRAM)及原始套接字(RAW)。流式套接字(SOCK_STREAM)流式的套接字可以提供可靠的、面向连接的通讯流。如果你通过流式套接字发送了顺序的数据:“1”、 “”。那么数据到达远程时候的顺序也是“”、 “”。Telnet 应用程序、 BBS 服务、以及系统的远程登陆都是通过 Telnet 协议连接的。Telnet 就是一个流式连接。你是否希望你在 Telnet 应用程序上输入的字符(或汉字)在到达远程应用程序的时候是以你输入的顺序到达的?答案应该是肯定的吧。还有 WWW 浏览器,它使用的 HTTP 协议也是通过流式套接字来获取网
3、页的。事实上,如果你 Telnet 到一个 Web Site 的80 端口上,然后输入 “GET 网页路径名” 然后按两下回车(或者是两下 Ctrl+回车)然后你就得到了“网页路径名 ”所代表的网页!数据报套接字(SOCK_DGRAM )数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠,无差错。如果你发送了一个数据报,它可能不会到达。它可能会以不同的顺序到达。如果它到达了,它包含的数据中可能存在错误。数据报套接字也使用 IP,但是它不使用 TCP,它使用使用者数据报协议UDP(User Datagram Protocol 可以参考 RFC 768)为
4、什么说它们是“无连接”的呢?因为它(UDP )不像流式套接字那样维护一个打开的连接,你只需要把数据打成一个包,把远程的 IP 贴上去,然后把这个包发送出去。这个过程是不需要建立连接的。UDP 的应用例子有: tftp, bootp 等。那么,数据包既然会丢失,怎样能保证程序能够正常工作呢?事实上,每个使用 UDP 的程序都要有自己的对数据进行确认的协议。比如, TFTP 协议定义了对于每一个发送出去的数据包,远程在接受到之后都要回送一个数据包告诉本地程序:“我已经拿到了!”(一个 “ACK” 包) 。如果数据包发的送者在5 秒内没有的得到回应,它就会重新发送这个数据包直到数据包接受者回送了 “
5、ACK” 信号。这些知识对编写一个使用 UDP 协议的程序员来说是非常必要的。无连接服务器一般都是面向事务处理的,一个请求一个应答就完成了客户程序与服务程序之间的相互作用。面向连接服务器处理的请求往往比较复杂,不是一来一去的请求应答所能解决的,而且往往是并发服务器套接字工作过程如下:服务器首先启动通过调用 socket()建立一个套接字,然后调用 bind()将该套接字和本地网络地址联系在一起,再调用 listen()使套接字做好侦听的准备,并规定它的请求队列的长度,之后就调用 accept()来接收连接。客户在建立套接字然后就可调用 connect()和服务器建立连接。客户机和服务器之间就可
6、以通过调用 read()和 write()来发送和接收数据。最后,待数据传送结束后,双方调用 close()关闭套接字。对流式套接字你所需要做的只是调用 send() 函数来发送数据。而对于数据报套接字,你需要自己加个信息头,然后调用 sendto() 函数把数据发送出去原始套接字原始套接字主要用于一些协议的开发,可以进行比较底层的操作。它功能强大,但是没有上面介绍的两种套接字使用方便,一般的程序也涉及不到原始套接字套接字结构struct sockaddr这个结构用来存储套接字地址。数据定义:struct sockaddrunsigned short sa_family; /* address
7、 族, AF_xxx */char sa_data14; /* 14 bytes 的协议地址 */;sa_family 一般来说,都是 “AF_INET”。sa_data 包含了一些远程电脑的地址、端口和套接字的数目,它里面的数据是杂溶在一切的。为了处理 struct sockaddr, 程序员建立了另外一个相似的结构 struct sockaddr_in (“in” 代表 “Internet”):struct sockaddr_inshort int sin_family; /* Internet 地址族 */unsigned short int sin_port; /* 端口号 */str
8、uct in_addr sin_addr; /* Internet 地址 */unsigned char sin_zero8; /* 添0(和 struct sockaddr 一样大小)*/;注意:1)这个结构提供了方便的手段来访问 socket address(struct sockaddr)结构中的每一个元素。2)sin_zero8 是为了是两个结构在内存中具有相同的尺寸要把 sin_zero 全部设成零值(使用 bzero()或 memset()函数) 。3)一个指向 struct sockaddr_in 的指针可以声明指向一个 sturct sockaddr 的结构。所以虽然 sock
9、et() 函数需要一个 structaddr * ,你也可以给他一个 sockaddr_in * 。4)在 struct sockaddr_in 中,sin_family 相当于 在 struct sockaddr 中的 sa_family,需要设成 “AF_INET”。5)一定要保证 sin_port 和 sin_addr 必须是网络字节顺序(见下节)!2struct in_addr ( 因特网地址 (a structure for historical reasons) )struct in_addrunsigned long s_addr;如果你声明了一个 “ina“ 作为一个 stru
10、ct sockaddr_in 的结构, 那么“ina.sin_addr.s_addr”就是4 个字节的 IP 地址(按网络字节顺序排放) 。需要注意的是,即使你的系统仍然使用联合而不是结构来表示 struct in_addr,你仍然可以用上面的方法得到4 个字节的 IP 地址(一些 #defines 帮了你的忙)网络字节顺序因为每一个机器内部对变量的字节存储顺序不同(有的系统是高位在前,底位在后,而有的系统是底位在前,高位在后) ,而网络传输的数据大家是一定要统一顺序的。所以对与内部字节表示顺序和网络字节顺序不同的机器,就一定要对数据进行转换(比如 IP 地址的表示,端口号的表示) 。但是内部
11、字节顺序和网络字节顺序相同的机器该怎么办呢?是这样的:它们也要调用转换函数,但是真正转换还是不转换是由系统函数自己来决定的。有关的转化函数我们通常使用的有两种数据类型:短型(两个字节)和长型(四个字节) 。下面介绍的这些转换函数对于这两类的无符号整型变量都可以进行正确的转换。如果你想将一个短型数据从主机字节顺序转换到网络字节顺序的话,有这样一个函数 htons:它是以“h”开头的(代表“主机”) ;紧跟着它的是“to”,代表“ 转换到”;然后是“n”代表“网络”;最后是“s”,代表“短型数据”。H-to-n-s,就是 htons() 函数(可以使用 Hostto Network Short 来
12、助记)你可以使用 “n”, “h”, “to”, “s”, “l”的任意组合.当然,你要在可能的情况下进行组合。比如,系统是没有 stolh() 函数的(Short to Long Host?) 。下面给出套接字字节转换程序的列表:htons()“Host to Network Short” 主机字节顺序转换为网络字节顺序(对无符号短型进行操作4 bytes)htonl()“Host to Network Long” 主机字节顺序转换为网络字节顺序(对无符号长型进行操作8 bytes)ntohs()“Network to Host Short “ 网络字节顺序转换为主机字节顺序(对无符号短型进
13、行操作4 bytes)ntohl()“Network to Host Long “ 网络字节顺序转换为主机字节顺序(对无符号长型进行操作8 bytes)在 struct sockaddr_in 中的 sin_addr 和 sin_port 他们的字节顺序都是网络字节顺序,而 sin_family 却不是网络字节顺序的。为什么呢?这个是因为 sin_addr 和 sin_port 是从 IP 和 UDP 协议层取出来的数据,而在 IP 和 UDP 协议层,是直接和网络相关的,所以,它们必须使用网络字节顺序。然而, sin_family 域只是内核用来判断 struct sockaddr_in 是
14、存储的什么类型的数据,并且, sin_family 永远也不会被发送到网络上,所以可以使用主机字节顺序来存储socket() 函数/* 取得套接字描述符!(记得我们以前说过的吗?它其实就是一个文件描述符)* domain 需要被设置为 “AF_INET”,就像上面的 struct sockaddr_in。* type 参数告诉内核这个 socket 是什么类型, “SOCK_STREAM”或是“SOCK_DGRAM”。* protocol 通常为0* return 如果发生错误,socket()函数返回 1 。全局变量 errno 将被设置为错误代码。*/#include#includeint
15、 socket( int domain , int type , int protocol)示例:if(sockfd = socket(AF_INET, SOCK_STREAM, 0) = -1)perror(“create sock“);return -1;else/ printf(“socket created.n“);bind() 函数/* 为套接字绑定一个端口号* 当你需要进行端口监听 listen()操作,等待接受一个连入请求的时候,* 一般都需要经过这一步。比如网络泥巴(MUD),Telnet a.b.c.d 4000* 如果你只是想进行连接一台服务器,也就是进行 connect(
16、) 操作的时候,这一步并不是必须的。* sockfd 是由 socket()函数返回的套接字描述符* my_addr 是一个指向 struct sockaddr 的指针,包含有关你的地址的信息:名称、端口和 IP 地址。* addrlen 可以设置为 sizeof(struct sockaddr)* return 调用错误的时候,返回 -1 作为错误发生的标志。errno 的值为错误代码。*/#include#includeint bind (int sockfd , struct sockaddr *my_addr , int addrlen) ;示例:#include#include#in
17、clude#define MYPORT 4000main()int sockfd ;struct sockaddr_in my_addr ;sockfd = socket(AF_INET, SOCK_STREAM, 0); /* 在你自己的程序中要进行错误检查!! */my_addr.sin_family = AF_INET ; /* 主机字节顺序 */my_addr.sin_port = htons(MYPORT); /* 网络字节顺序,短整型 */my_addr.sin_addr.s_addr = inet_addr(“166.111.69.52“) ;bzero( /* 将整个结构剩余部
18、分数据设为0 */bind (sockfd, (struct sockaddr *) /* 不要忘记在你自己的程序中加入判断 bind 错误的代码! */注意:my_addr.sin_port 是网络字节顺序, 短整型my_addr.sin_addr.s_addr 也是网络字节顺序。最后,bind()可以在程序中自动获取你自己的 IP 地址和端口。my_addr.sin_port = 0 ; /* 随机选择一个端口 */my_addr.sin_addr.s_addr = INADDR_ANY ; /* 使用自己的地址 */如上,通过设置 my_addr.sin_port 为0,bind()可以
19、知道你要它帮你选择合适的端口;通过设置 my_addr.sin_addr.s_addr 为 INADDR_ANY,bind()知道你要它将 s_addr 填充为运行这个进程的机器的 IP。这一切都可以要求 bind()来自动的帮助你完成。如果你注意到了一些细节的话,你可能会发现我并没有将 INADDR_ANY 转换为网络字节顺序!是这样的,INADDR_ANY 的值为0,0 就是0 ,无论用什么顺序排列位的顺序,它都是不变的。有读者会想了,因为我用的 INADDR_ANY 是一个#define,那么如果将我的程序移植到另外一个系统,假如那里的 INADDR_ANY 是这样定义的:#define
20、 INADDR_ANY 100,那么我的程序不是就会不运行了吗?那么下面这段代码就 OK 了:my_addr.sin_port = htons(0); /* 随机选择一个未用的端口 */my_addr.sin_addr.s_addr = htonl(INADDR_ANY) ; /* 使用自己的 IP 地址 */现在我们已经是这么的严谨,对于任何数值的 INADDR_ANY 调用 bind 的时候就都不会有麻烦了。另外一件必须指出的事情是:当你调用 bind()的时候,不要把端口数设置的过小!小于1024 的所有端口都是保留下来作为系统使用端口的,没有 root 权利无法使用。你可以使用1024
21、 以上的任何端口,一直到65535 :你所可能使用的最大的端口号(当然,你还要保证你所希望使用的端口没有被其他程序所使用) 。最后注意有关 bind()的是:有时候你并不一定要调用 bind()来建立网络连接。比如你只是想连接到一个远程主机上面进行通讯,你并不在乎你究竟是用的自己机器上的哪个端口进行通讯(比如 Telnet) ,那么你可以简单的直接调用 connect()函数,connect()将自动寻找出本地机器上的一个未使用的端口,然后调用 bind()来将其 socket 绑定到那个端口上。connect() 函数/* sockfd 套接字文件描述符,由 socket()函数返回的* s
22、erv_addr 是一个存储远程计算机的 IP 地址和端口信息的结构* addrlen 应该是 sizeof(struct sockaddr)* return 如果发生了错误(比如无法连接到远程主机,或是远程主机的指定端口无法进行连接等)它将会返回错误值 -1* 全局变量 errno 将会存储错误代码*/#include#includeint connect (int sockfd, struct sockaddr *serv_addr, int addrlen);示例:#include#include#include#define DEST_IP “166.111.69.52”#define
23、 DEST_PORT 23main()int sockfd ;/* 将用来存储远程信息 */struct sockaddr_in dest_addr ;/* 注意在你自己的程序中进行错误检查! */sockfd = socket(AF_INET, SOCK_STREAM, 0);/* 主机字节顺序 */dest_addr.sin_family = AF_INET ;/* 网络字节顺序,短整型 */dest_addr.sin_port = htons(DEST_PORT(;dest_addr.sin_addr.s_addr = inet_addr(DEST_IP);/* 将剩下的结构中的空间置
24、0 */bzero(/* 不要忘记在你的代码中对 connect()进行错误检查! */connect(sockfd, (struct sockaddr *)注意我们没有调用 bind()函数。基本上,我们并不在乎我们本地用什么端口来通讯,是不是?我们在乎的是我们连到哪台主机上的哪个端口上。Linux 内核自动为我们选择了一个没有被使用的本地端口。listen() 函数/* 等待别人连接,进行系统侦听请求* 当有人连接你的时候,你有两步需要做:* 通过 listen()函数等待连接请求* 然后使用 accept()函数来处理* 那么我们需要指定本地端口了,因为我们是等待别人的连接。所以,在 l
25、isten()函数调用之前,我们需要使用 bind() 函数来指定使用本地的哪一个端口数值* 如果你想在一个端口上接受外来的连接请求的话,那么函数的调用顺序为:* socket();* bind();* listen();* sockfd 是一个套接字描述符,由 socket()系统调用获得* backlog 是未经过处理的连接请求队列可以容纳的最大数目(每一个连入请求都要进入一个连入请求队列,等待 listen 的程序调用 accept()函数来接受这个连接。当系统还没有调用 accept()函数的时候,如果有很多连接,那么本地能够等待的最大数目就是 backlog 的数值。你可以将其设成5
26、 到10 之间的数值(推荐)* return 错误返回-1, 并设置全局错误代码变量 errno*/#includeint listen(int sockfd, int backlog);accept()函数/* 当调用它的时候,大致过程是下面这样的:* 有人从很远很远的地方尝试调用 connect()来连接你的机器上的某个端口(当然是你已经在 listen()的)* 他的连接将被 listen 加入等待队列等待 accept()函数的调用* 你调用 accept()函数,告诉他你准备连接* sockfd 是正在 listen() 的一个套接字描述符* addr 一般是一个指向 struct
27、sockaddr_in 结构的指针;里面存储着远程连接过来的计算机的信息(比如远程计算机的 IP 地址和端口)* addrlen 是一个本地的整型数值,在它的地址传给 accept() 前它的值应该是 sizeof(struct sockaddr_in);accept()不会在 addr 中存储多余 addrlen bytes 大小的数据。如果 accept()函数在 addr 中存储的数据量不足 addrlen,则 accept()函数会改变 addrlen 的值来反应这个情况。* return accept()函数将回返回一个新的套接字描述符,这个描述符就代表了这个连接* 这时候你有了两个
28、套接字描述符:* 返回给你的那个就是和远程计算机的连接, 这时候你所得到的那个新的套接字描述符就可以进行 send()操作和 recv()操作了。* 而第一个套接字描述符仍然在你的机器上原来的那个端口上 listen()* -1 来表明调用失败,同时全局变量 errno 将会存储错误代码*/#includeint accept(int sockfd, void *addr, int *addrlen);示例:#include#include#include/* 用户连接的端口号 */#define MYPORT 4000/* 等待队列中可以存储多少个未经过 accept()处理的连接 */#d
29、efine BACKLOG 10main()/* 用来监听网络连接的套接字 sock_fd,用户连入的套接字使用 new_fd */int sockfd, new_fd ;/* 本地的地址信息 */struct sockaddr_in my_addr ;/* 连接者的地址信息 */struct sockaddr_in their_addr ;int sin_size;/* 记得在自己的程序中这部分要进行错误检查! */sockfd = socket(AF_INET, SOCK_STREAM, 0) ;/* 主机字节顺序 */my_addr.sin_family = AF_INET ;/* 网络
30、字节顺序,短整型 */my_addr.sin_port = htons(MYPORT) ;/* 自动赋值为自己的 IP */my_addr.sin_addr.s_addr = INADDR_ANY ;/* 将结构中未使用部分全部清零 */bzero(/* 不要忘记在你自己的程序中下面的程序调用需要进行错误检测!*/bind(sockfd, (struct sockaddr *)listen(sockfd, BACKLOG);sin_size = sizeof(struct sockaddr_in);new_fd = accept(sockfd, send()、recv()函数/* 这两个函数是
31、最基本的,通过连接的套接字流进行通讯的函数* 如果你想使用无连接的使用者数据报的话,请参 sendto() 和 recvfrom() 函数。* sockfd 是代表你与远程程序连接的套接字描述符。* msg 是一个指针,指向你想发送的信息的地址。* len 是你想发送信息的长度* flags 发送标记。一般都设为0(你可以查看 send 的 man pages 来获得其他的参数值并且明白各个参数所代表的含义)* * return 函数在调用后会返回它真正发送数据的长度* -1 如果发生错误,错误代码存储在全局变量 errno*/#include#includeint send(int sock
32、fd, const void *msg, int len, int flags);示例:char *msg = “Hello! World! “;int len, bytes_sent;len = strlen(msg);bytes_sent = send(sockfd, msg, len, 0);注意:send() 所发送的数据可能少于你给它的参数所指定的长度!因为如果你给 send()的参数中包含的数据的长度远远大于 send()所能一次发送的数据,则 send()函数只发送它所能发送的最大数据长度,然后它相信你会把剩下的数据再次调用它来进行第二次发送。所以,记住如果 send()函数的返
33、回值小于 len 的话,则你需要再次发送剩下的数据。幸运的是,如果包足够小(小于1K ) ,那么 send()一般都会一次发送光的。recv()函数/* sockfd 是你要读取数据的套接字描述符* buf 是一个指针,指向你能存储数据的内存缓存区域* len 是缓存区的最大尺寸* flags 是 recv() 函数的一个标志,一般都为 0 (具体的其他数值和含义请参考 recv()的 man pages)* 返回 它所真正收到的数据的长度(也就是存到 buf 中数据的长度)* -1 则代表发生了错误(比如网络以外中断、对方关闭了套接字连接等) ,全局变量 errno 里面存储了错误代码*/#
34、include#includeint recv(int sockfd, void *buf, int len, unsigned int flags);sendto() 和 recvfrom() 函数/* 这两个函数是进行无连接的 UDP 通讯时使用的。* 使用这两个函数,则数据会在没有建立过任何连接的网络上传输。因为数据报套接字无法对远程主机进行连接* sockfd 是代表你与远程程序连接的套接字描述符* msg 是一个指针,指向你想发送的信息的地址* len 是你想发送信息的长度* flags 发送标记。一般都设为0 (你可以查看 send 的 man pages 来获得其他的参数值并且明
35、白各个参数所代表的含义)* to 是一个指向 struct sockaddr 结构的指针,里面包含了远程主机的 IP 地址和端口数据* tolen 只是指出了 struct sockaddr 在内存中的大小 sizeof(struct sockaddr)* return sendto()返回它所真正发送的字节数(当然也和 send()一样,它所真正发送的字节数可能小于你所给它的数据的字节数)* -1 表示出错 同时全局变量 errno 存储了错误代码*/#include#includeint sendto(int sockfd, const void *msg, int len, unsign
36、ed int flags, const struct sockaddr *to, int tolen);recvfrom()函数/* sockfd 是你要读取数据的套接字描述符* buf 是一个指针,指向你能存储数据的内存缓存区域* len 是缓存区的最大尺寸* flags 是 recv() 函数的一个标志,一般都为0 (具体的其他数值和含义请参考 recv()的man pages)* from 是一个本地指针,指向一个 struct sockaddr 的结构(里面存有源 IP 地址和端口数)* fromlen 是一个指向一个 int 型数据的指针,它的大小应该是 sizeof (struct
37、 sockaddr)当函数返回的时候,formlen 指向的数据是 form 指向的 struct sockaddr 的实际大小* 如果一个信息大得缓冲区都放不下,那么附加信息将被砍掉。该调用可以立即返回,也可以永久的等待。这取决于你把 flags 设置成什么类型。你甚至可以设置超时(timeout)值。* return 返回它接收到的字节数,如果发生了错误,它就返回1 ,全局变量 errno 存储了错误代码*/#include#includeint recvfrom(int sockfd, void *buf, int len, unsigned int flags, struct sock
38、addr *from, int *fromlen);注意:如果你使用 cnnect()连接到了一个数据报套接字的服务器程序上,那么你就可以使用send() 和 recv() 函数来传输你的数据不要以为你在使用一个流式的套接字,你所使用的仍然是一个使用者数据报的套接字,只不过套接字界面在 send() 和 recv()的时候自动帮助你加上了目标地址,目标端口的信息close()和 shutdown()函数/* 程序进行网络传输完毕后,你需要关闭这个套接字描述符所表示的连接。实现这个非常简单,只需要使用标准的关闭文件的函数:close()。* 执行 close()之后,套接字将不会在允许进行读操作
39、和写操作。任何有关对套接字描述符进行读和写的操作都会接收到一个错误。*/close(sockfd);/* 如果你想对网络套接字的关闭进行进一步的操作的话,你可以使用函数 shutdown()* 它允许你进行单向的关闭操作,或是全部禁止掉。* 如果你在一个未连接的数据报套接字上使用 shutdown() 函数,它将什么也不做* sockfd 是一个你所想关闭的套接字描述符* how 可以取下面的值。* 0 表示不允许以后数据的接收操;* 1 表示不允许以后数据的发送操作;* 2 表示和 close()一样,不允许以后的任何操作(包括接收,发送数据)* return 0 执行成功* -1 执行失败
40、, 全局变量 errno 中存储了错误代码* */#includeint shutdown(int sockfd, int how);setsockopt() 和 getsockopt() 函数Linux 所提供的 socket 库含有一个错误(bug) 。此错误表现为你不能为一个套接字重新启用同一个端口号,即使在你正常关闭该套接字以后。例如,比方说,你编写一个服务器在一个套接字上等待的程序服务器打开套接字并在其上侦听是没有问题的。无论如何,总有一些原因(不管是正常还是非正常的结束程序)使你的程序需要重新启动。然而重启动后你就不能把它绑定在原来那个端口上了。从bind()系统调用返回的错误代码
41、总是报告说你试图连接的端口已经被别的进程所绑定。问题就是 Linux 内核在一个绑定套接字的进程结束后从不把端口标记为未用。在大多数Linux/UNIX 系统中,端口可以被一个进程重复使用,甚至可以被其它进程使用。在 Linux 中绕开这个问题的办法是,当套接字已经打开但尚未有连接的时候用setsockopt()系统调用在其上设定选项(options) 。setsockopt() 调用设置选项而 getsockopt()从给定的套接字取得选项。这里是这些调用的语法:/* sockfd 必须是一个已打开的套接字* level 是函数所使用的协议标准(protocol level) (TCP/IP
42、 协议使用 IPPROTO_TCP,套接字标准的选项实用 SOL_SOCKET)* name 选项在套接字说明书中(man page)有详细说明* value 指向为 getsockopt()函数所获取的值, setsockopt()函数所设置的值的地址* optlen 指针指向一个整数,该整数包含参数以字节计算的长度*/#include#includeint getsockopt(int sockfd, int level, int name, char *value, int *optlen);int setsockopt(int sockfd, int level, int name,
43、char *value, int *optlen);当你打开一个套接字时必须同时用下面的代码段来调用 setsockopt()函数:/* 设定参数数值 */opt = 1;len = sizeof(opt);/* 设置套接字属性 */setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,getpeername()函数/* 这个函数可以取得一个已经连接上的套接字的远程信息(比如 IP 地址和端口) ,告诉你在远程和你连接的究竟是谁* 当你拥有了远程连接用户的 IP 地址,你就可以使用 inet_ntoa() 或 gethostbyaddr()来输出信息或是做进一步的
44、处理* sockfd 是你想取得远程信息的那个套接字描述符* addr 是一个指向 struct sockaddr (或是 struct sockaddr_in)的指针* addrlen 是一个指向 int 的指针,应该赋于 sizeof(struct sockaddr)的大小* return 错误,函数将返回 1 ,并且错误代码储存在全局变量 errno 中*/#includeint getpeername(int sockfd, struct sockaddr *addr, int *addrlen);gethostname()函数/* 可以取得本地主机的信息,它返回正在执行它的计算机的名
45、字* 返回的这个名字可以被 gethostbyname()函数使用,由此可以得到本地主机的 IP 地址* hostname 是一个指向字符数组的指针,当函数返回的时候,它里面的数据就是本地的主机的名字* size 是 hostname 指向的数组的长度* return 成功执行,它返回0* 错误,则返回1,全局变量 errno 中存储着错误代码*/#includeint gethostname(char *hostname, size_t size);gethostbyname()函数/* 网络地址是以网络字节顺序存储的* return 成功则返回指向结构 struct hostent 的指针
46、*#define h_addr h_addr_list0 /h_addr 是 h_addr_list 数组的第一个成员struct hostentchar *h_name; /是这个主机的正式名称char *h_aliases; /是一个以 NULL(空字符)结尾的数组,里面存储了主机的备用名称int h_addrtype; /是返回地址的类型,一般来说是“AF_INET”int h_length; /是地址的字节长度char *h_addr_list; /是一个以0 结尾的数组,存储了主机的网络地址;* 如果发生错误,它将会返回 NULL(但是 errno 并不代表错误代码,h_errno
47、中存储的才识错误代码。参考下面的 herror()函数*/struct hostent *gethostbyname(const char *name);五种 I/O 模式-在 Linux/UNIX 下,有下面这五种 I/O 操作方式:阻塞 I/O非阻塞 I/OI/O 多路复用信号驱动 I/O(SIGIO)异步 I/O程序进行输入操作有两步:等待有数据可以读将数据从系统内核中拷贝到程序的数据区。对于一个对套接字的输入操作:第一步一般来说是,等待数据从网络上传到本地,当数据包到达的时候,数据将会从网络层拷贝到内核的缓存中;第二步是从内核中把数据拷贝到程序的数据区中.阻塞 I/O 模式简单的说,阻
48、塞就是睡眠的同义词如你运行上面的 listener 的时候,它只不过是简单的在那里等待接收数据。它调用 recvfrom()函数,但是那个时候( listener 调用 recvfrom()函数的时候) ,它并没有数据可以接收所以 recvfrom()函数阻塞在那里(也就是程序停在 recvfrom()函数处睡大觉)直到有数据传过来阻塞你应该明白它的意思。阻塞 I/O 模式是最普遍使用的 I/O 模式。大部分程序使用的都是阻塞模式的 I/O 。缺省的,一个套接字建立后所处于的模式就是阻塞 I/O 模式。对于一个 UDP 套接字来说,数据就绪的标志比较简单:已经收到了一整个数据报没有收到。而 TCP 这个概念就比较复杂,需要附加一些其他的变量一个进程调用 recvfrom ,然后系统调用并不返回知道有数据报到达本地系统,然后系统将数据拷贝到进程的缓存中。(如果系统调用收到一个中断信号,则它的调用会被中断)我们称这个进程在调用 recvfrom 一直到从 recvfrom 返回这段时间是阻塞的。当 recvfrom 正常返回时,我们的进程继续它的操作。.非阻塞模式 I/O当我们将一个套接字设