《简明Socket编程指南.pdf》由会员分享,可在线阅读,更多相关《简明Socket编程指南.pdf(26页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、中国科学技术大学多媒体技术与网络通信实验室(copyright 1999)附录一简明 Socket 编程指南在本说明文档中,主要讲述了一些网络SOCKET 编程的基本概念和有关函数说明,并给出了部分示例程序的源代码。在完成“TCP 和 IP 通信程序设计实验”、“实时声音传输实验”和“HTTP 代理实现实验”时,可以参考本文档的内容。一、SOCKET 基本概念1 Linux/Unix:Socket 函数库Linux Socket函数库是从Berkeley 大学开发的BSD UNIX系统中移植过来的。BSD Socket 接口是在众多Unix 系统中被广泛支持的TCP/IP 通信接口,Linux
2、 下的 Socket 程序设计,除了微小的差别之外,也适用于大多数其它Unix 系统。Socket 的使用,和文件操作比较类似。如同文件的读、写、打开、关闭等操作一样,TCP/IP 网络通信同样也有这些操作,不过它使用的接口不是文件描述符或者FILE*,而是一个称做Socket 的描述符。类似于文件操作,对于Socket,也通过读、写、打开、关闭操作来进行网络数据传送。同时,还有一些辅助的函数,如域名/IP 地址查询、Socket 功能设置等。2.DOS:WATTCP函数库关于 WATTCP 函数库,也就是DOS 下 TCP/IP 协议驱动程序库是由加拿大Waterloo 大学的 Erick
3、Engelke 提供的。NCSA 的 telnet、ftp 等程序,都是利用Erick Engelke 的 TCP/IP程序库WATTCP 开发出来的。WATTCP 是一个很容易使用的C 语言函数库,相对Unix 和其它环境下广泛使用的BSD Socket 接口而言,它在DOS 下提供了一个更为简单和直观的TCP/IP 编程接口。WATTCP 编程接口,相对于Unix Socket 而言,作了相当的简化。Unix 下,网络操作和文件系统几乎融为一体,但 DOS 下不存在如Unix 一样强大的网络文件系统功能。因此,在DOS 下没有 Unix 中那种直接用文件系统调用来操作Socket 的便利。
4、DOS 下的 TCP/IP 通信和 DOS 系统几乎是完全独立的。WATTCP 支持 DOS TCP/IP 环境下的基本Socket 接口,大致和Unix Socket兼容,包括面向连接的TCP(SOCK_STREAM)和非连接的UDP(SOCK_DGRAM)型 Socket。另外 WATTCP 提供一些专用的SOCKET 接口函数。3.Windows Sockets规范 Windows Sockets 规范以 U.C.Berkeley 大学 BSD UNIX中流行的Socket 接口为范例定义了一套Micosoft Windows下网络编程接口。它不仅包含了人们所熟悉的Berkeley So
5、cket风格的库函数;也包含了一组针对Windows的扩展库函数,以使程序员能充分地利用Windows 消息驱动机制进行编程。这一套Windows Sockets API能够在所有3.0 以上版本的Windows 和所有Windows 如果您在阅读过程中发现疏漏和错误,请您尽快和编者取得联系 中国科学技术大学多媒体技术与网络通信实验室(copyright 1999)Scokets 实现上使用,所以它不仅为Windwos Sockets 实现和 Windows Sockets 应用程序提供了 16 位操作环境,而且也提供了32 位操作环境。Windows Sockets 也支持多线程的Windo
6、ws 进程。一个进程包含了一个或多个同时执行的线程。在Windows 3.1 非多线程版本中,一个任务对应了一个仅具有单个线程的进程。而我们在本书中所提到的线程均是指在多线程Windows 环境中的真正意义的线程。在非多线程环境中(例如Windows 3.0)这个术语是指Windows Sockets 进程。Windows Sockets规范中的针对Windows 的扩展部分为应用程序开发者提供了开发具有Windows 应用软件的功能。它有利于使程序员写出更加稳定并且更加高效的程序,也有助于在非占先Windows 版本中使多个应用程序在多任务情况下更好地运作。除了WSAStartup()和 W
7、SACleanup()两个函数除外,其他的Windows 扩展函数的使用不是强制性的。4.套接口基本概念通讯的基石是套接口,一个套接口是通讯的一端。在这一端上你可以找到与其对应的一个名字。一个正在被使用的套接口都有它的类型和与其相关的进程。套接口存在于通讯域中。通讯域是为了处理一般的线程通过套接口通讯而引进的一种抽象概念。套接口通常和同一个域中的套接口交换数据(数据交换也可能穿越域的界限,但这时一定要执行某种解释程序)。Windows Sockets规范支持单一的通讯域,即Internet 域。各种进程使用这个域互相之间用Internet 协议族来进行通讯(Windows Sockets 1.
8、1 以上的版本支持其他的域,例如Windows Sockets 2)。套接口可以根据通讯性质分类;这种性质对于用户是可见的。应用程序一般仅在同一类的套接口间通讯。不过只要底层的通讯协议允许,不同类型的套接口间也照样可以通讯。用户目前可以使用两种套接口,即流套接口和数据报套接口。流套接口提供了双向的,有序的,无重复并且无记录边界的数据流服务。数据报套接口支持双向的数据流,但并不保证是可靠,有序,无重复的。也就是说,一个从数据报套接口接收信息的进程有可能发现信息重复了,或者和发出时的顺序不同。数据报套接口的一个重要特点是它保留了记录边界。对于这一特点,数据报套接口采用了与现在许多包交换网络(例如以
9、太网)非常类似的模型。二、SOCKET 编程原理1.套接口网络编程原理套接口有三种类型:流式套接口,数据报套接口及原始套接口。流式套接口定义了一种可靠的面向连接的服务,实现了无差错无重复的顺序数据传输。数据报套接口定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠,无差错。原始套接口允许对低层协议如IP或ICMP 直接访问,主要用于新的网络协议实现的测试等。无连接服务器一般都是面向事务处理的,一个请求一个应答就完成了客户程序与服务程序之间的相互作用。若使用无连接的套接口编程,程序的流程可以用图3-1表示。如果您在阅读过程中发现疏漏和错误,请您尽快和编者取得联系 中
10、国科学技术大学多媒体技术与网络通信实验室(copyright 1999)服务器 socket()bind()readfrom()sendto()阻塞,等待客户数据处理服务请求 socket()bi nd()sendto()readfrom()客户机服务请求服务应答图 3-1 无连接套接口应用程序时序图close()close()面向连接服务器处理的请求往往比较复杂,不是一来一去的请求应答所能解决的,而且往往是并发服务器。使用面向连接的套接口编程,可以通过图3-1 来表示:其时序。如果您在阅读过程中发现疏漏和错误,请您尽快和编者取得联系 中国科学技术大学多媒体技术与网络通信实验室(copyrig
11、ht 1999)服务器 socket()bind()l isten()阻塞,等待客户数据处理服务请求 socket()connect()w rite()客户机请求数据应答数据图 3-2 面向连接套接口应用程序时序图read()read()accept()write()建立连接 cl ose()cl ose()套接口工作过程如下:服务器首先启动,通过调用socket()建立一个套接口,然后调用bind()将该套接口和本地网络地址联系在一起,再调用listen()使套接口做好侦听的准备,并规定它的请求队列的长度,之后就调用accept()来接收连接。客户在建立套接口后就可调用connect()和服
12、务器建立连接。连接一旦建立,客户机和服务器之间就可以通过调用read()和write()来发送和接收数据。最后,待数据传送结束后,双方调用close()关闭套接口。2.Windows Sockets 编程原理由于 Windows 的基于消息的特点,WINSOCK 和BSD 套接口相比,有如下一些新的扩充:1.异步选择机制异步选择函数WSAAsyncSelect()允许应用程序提名一个或多个感兴趣的网络事件,如FD_READ,FD_WRITE,FD_CONNECT,FD_ACCEPT 等等代表的网络事件。当被提名的网络事件发生时,Windows 应用程序的窗口函数将收到一个消息。这样就可以实现事
13、件驱动了。如果您在阅读过程中发现疏漏和错误,请您尽快和编者取得联系 中国科学技术大学多媒体技术与网络通信实验室(copyright 1999)2.异步请求函数异步请求函数允许应用程序用异步方式获得请求的信息,如 WSAAsyncGetXByY()类函数。这些函数是对BSD 标准函数的扩充。函数WSACancelAsyncRequest()允许用户中止一个正在执行的异步请求。3.阻塞处理方法 WINSOCK提供了 钩子函数 负责处理Windows 消息,使Windows 的消息循环能够继续。WINSOCK 提供了两个函数(WSASetBlockingHook()和 WSAUnhookBlocki
14、ngHook()让应用程序设置或取消自己的钩子函数。函数WSAIsBlocking()可以检测是否阻塞,函数WSACancelBlockingCall()可以取消一个阻塞的调用。4.错误处理 WINSOCK提供了两个WSAGetLastError()和 WSASetLastError()来获取和设置最近错误号。5.启动和终止由于 Windows Sockets 的服务是以动态连接库WINSOCK.DLL形式实现的,所以必须要先调用 WSAStartup()函数对 Windows Sockets DLL 进行初始化,协商 WINSOCK 的版本支持,并分配必要的资源。在应用程序关闭套接口后,还应
15、调用WSACleanup()终止对Windows Sockets DLL 的使用,并释放资源,以备下一次使用。在这些函数中,实现Windows 网络实时通信的关键是异步选择函数WSAAsyncSelect()的使用。三、SOCKET 函数库介绍1.DOS:WATTCP函数库兼容型 Socket 函数WATTCP 支持 DOS TCP/IP 环境下的基本Socket 接口,大致和Unix Socket 兼容,包括面向连接的TCP(SOCK_STREAM)和非连接的UDP(SOCK_DGRAM)型 Socket。其主要的函数有:WATTCP 初始化:sock_init():初始化TCP/IP 驱动
16、程序,建立和pkt driver 的调用关系;sock_exit():清除 TCP/IP 驱动程序和pkt driver 的联系;由于 DOS 环境下,TCP/IP 驱动程序是完全独立的,为了正常使用,必须首先初始化,使用完毕后,必须清除现场,以免影响其它程序的运行。Socket 创建和释放:socket()、n_close()如果您在阅读过程中发现疏漏和错误,请您尽快和编者取得联系 中国科学技术大学多媒体技术与网络通信实验室(copyright 1999)连接建立和撤销:bind()、listen()、accept()、connect()Socket 发送和接收:n_read()、n_wri
17、te()、select()数据转换函数gethostbyname()、inet_addr()、inet_ntoa()这些函数中,n_read()、n_write()、n_close()分别对应于Unix Socket 接口中的read()、write()、close()函数,因为需要和DOS 系统下的文件操作函数区分开,用了另外的名字。以上这些函数,其含义和用法和标准的Unix Socket 接口基本一致,是实验中应该掌握并学会使用的部分。专用 Socket 接口函数另外,WATTCP 函数库是相当开放的,上面的 Socket 接口是层次比较高的接口,对 Socket的控制功能稍微弱一些,如,
18、函数n_read()和 n_write()都不是 non_blocking 型的,没有提供non_blocking 选项。如果需要对TCP Socket 进行更强的控制,可以完全弃之不用,而改用较为低级的 TCP/UDP 接口函数,但是,这要求直接使用WATTCP 的内部数据结构Tcp_Socket,对 WATTCP 的内部实现机制有比较清楚的了解,并在程序中间进行必要的协调。具体的函数有:TCP/IP 接口初始化:sock_init():初始化TCP/IP 驱动程序,建立和pkt driver 的调用关系;sock_exit():清除 TCP/IP 驱动程序和pkt driver 的联系;S
19、ocket 操作函数:tcp_listen()、tcp_accept():被动等待建立TCP 连接;tcp_open()、udp_open():主动建立TCP/UDP 连接;sock_close():关闭 Socket;Socket 读写函数:sock_read()、sock_write()非 non_blocking 的 Socket 读写函数;sock_fastread()、sock_fastwrite()sock_read()和 sock_write()的 non_blocking 版本;sock_flush()完全发送缓冲区中的数据;如果您在阅读过程中发现疏漏和错误,请您尽快和编者取得
20、联系 中国科学技术大学多媒体技术与网络通信实验室(copyright 1999)Socket 等待型操作的容错处理:sock_wait_xxx 宏:sock_wait_established tcp_open 之后,等待连接建立的完成;sock_wait_input 连接建立之后,等待接收网络数据;sock_wait_closed sock_close之后,等待连接撤销的完成;关于这些函数的使用,请参见相应的示例。2.Linux/Unix:Socket 函数库Socket 操作:Socket():分配 Socket#include#include int socket(int domain,i
21、nt type,int protocol);socket()函数分配一个Socket 句柄,用于指定特定网络下、使用特定的协议和数据传送方式进行通信。Socket 接口是不仅仅局限于TCP/IP 的,但是由于TCP/IP 的广泛使用,它们几乎被完全等同起来了。Socket 句柄分配以后,如果要开始TCP 通信,还需要建立连接。根据需要,可以主动地建立连接(通过connect())和被动地等待对方建立连接(通过listen()),在连接建立后才能使用读写操作通过网络连接进行数据交换。参数说明:domain domain 参数选择通信中使用的协议族,也就是网络的类型,可以是以下之一:AF_UNIX
22、 (UNIX内部协议)AF_INET (ARPA Internet 协议,也就是TCP/IP 协议族,亦即我们实验中所使用的)AF_ISO (ISO协议)AF_NS (Xerox Network Systems协议)AF_IMPLINK(IMP host at IMP link layer)type 数据传送的方式,可以是以下之一:SOCK_STREAM:保证顺序的、可靠传送的双向字节数据流,最为常用,也是TCP 连接所使用的方式。SOCK_DGRAM:无连接的、不保证可靠的、固定长度(通常很小)的消息传送。SOCK_SEQPACKET:顺序的、可靠的双向固定长度的数据包传送,只用于AF_NS
23、 类型的网络中。SOCK_RAW:原始的数据传送,适用于系统内部专用的网络协议和接口,和 SOCK_RDM一样,只能由超级用户使用。SOCK_RDM:可靠的数据报传送,未实现。protocol protocol 参数指定通信中使用的协议。在给定Socket 的协议族和传送类型之后,一般情况下所使用的协议也就固定下来,如下表所示,此时protocol 参数可使用缺省值0;但如果还有多个协议供选择,则必须使用protocol 参数来标识。协议族(仅考虑IP传送类型protocol 参数常量协议类型如果您在阅读过程中发现疏漏和错误,请您尽快和编者取得联系 中国科学技术大学多媒体技术与网络通信实验室(
24、copyright 1999)协议族)(/usr/include/linux/in.h)SOCK_STREAM IPPROTO_TCP TCP SOCK_DGRAM IPPROTO_UDP UDP SOCK_RAW IPPROTO_ICMP ICMP AF_INET SOCK_RAW IPRROTO_RAW(raw)返回值:正常执行时,返回Socket 描述符;否则,返回-1,错误状态在全局变量errno 中。close():关闭 Socket#include int close(int fd);Socket 和文件描述符的关闭操作都是使用这个函数。参数说明:fd Socket 描述符。返回值
25、:正常时返回0,-1 表示出错。bind():给 Socket 指定本地地址#include#include int bind(int sockfd,struct sockaddr*my_addr,int addrlen);bind 函数给已经打开的Socket 指定本地地址。这个函数的使用有以下两种情况:如果此Socket 是面向连接的,而且此Socket 在连接建立过程中处于被动的地位,即,己方程序使用listen 函数等待对方建立连接,对方用connect 函数来向此Socket 建立连接,这种情况下,必须用 bind 给此 Socket 设定本地地址。在己方使用listen 函数时,除
26、指定 Socket描述符之外,该Socket 必须已经用bind 函数设定好了本地地址(包括IP 地址和端口号),这样,系统在收到建立连接的网络请求时,才能根据请求的目的地址,识别是通向哪个Socket的连接,从而己方才能用此Socket 接收到发给此Socket 地址的数据包。不指定Socket 的本地地址,就无法将此Socket 用于连接建立和数据接收。如果此 Socket 用于无连接的情形,同样也要求给该Socket 设定本地地址,这样,以后系统从网络中接收到数据后,才知道该送给哪个Socket 及其相对应的进程。参数说明:sockfd Socket 描述符。addrlen my_add
27、r 结构的长度。my_addr 用于侦听连接请求的本地地址。struct sockaddr 是一个通用型的结构,不仅包含TCP/IP 协议的情况,同时也是为了适合于其它网络,如AF_NS。由于它的这种通用性,它只是定义了一个一般意义上的存储空间,如/usr/include/linux/socket.h中所示:struct sockaddr unsigned short sa_family;/*address family,AF_xxx*/char sa_data14;/*14 bytes of protocol address*/;当使用TCP/IP 协议(即:Internet 协议)时,可用
28、如下的struct 等价地代替struct sockaddr(/usr/include/linux/in.h):如果您在阅读过程中发现疏漏和错误,请您尽快和编者取得联系 中国科学技术大学多媒体技术与网络通信实验室(copyright 1999)#define _SOCK_SIZE_ 16 /*sizeof(struct sockaddr)*/struct sockaddr_in short int sin_family;/*Address family*/unsigned short int sin_port;/*Port number*/struct in_addr sin_addr;/*I
29、nternet address*/*Pad to size of struct sockaddr.*/unsigned char _pad_SOCK_SIZE_-sizeof(short int)-sizeof(unsigned short int)-sizeof(struct in_addr);在 Socket 程序中,等待建立连接一方的准备过程请参见编程实例,以及listen()、accept()的说明。返回值:正常时返回0,否则返回-1,同时 errno 是系统错误码。listen():准备接受连接请求。#include int listen(int s,int backlog);在用
30、bind()给一个Socket设定本地地址之后,就可以将这个Socket用于接受连接请求,即listen()。调用listen()之后,系统将给此Socket配备一个连接请求的队列,暂存系统接收到的、申请向此 Socket建立连接的请求,等待用户程序用accept()正式接受该请求。队列长度,就由backlog参数指定。如下面的简图所示:通信己方Me 通信对方 Socket_Me Peer Sockets 0-连接建立请求1-connect()Socket_peer_1 1-连接建立请求2-connect()Socket_peer_2 连接请求暂存队列backlog-1-连接建立请求n-con
31、nect()Socket_peer_n 如果短时间内向己方建立连接的请求过多,己方来不及处理,那么排在backlog 之后的请求将被系统拒绝。因此,backlog 参数实际上规定了己方程序能够容许的连接建立处理速度。至于己方程序使用此Socket(及其指定的本地地址)实际建立连接的个数,由己方程序调用accept()的次数来决定,参见accept()的说明。参数说明:sSocket 描述符。backlog连接请求暂存队列长度。返回值:正常时返回0;否则返回-1,同时 errno 是系统错误码。accept:接受指定Socket 上的连接请求#include#include int accept
32、(int s,struct sockaddr*addr,int*addrlen);在调用 listen()之后,系统就在Socket 的连接请求暂存队列里存放每一个向该Socket(及其本地地址)建立的连接请求。accept()函数的作用就是,从该暂存队列中取出一个连接请求,用该Socket 的数据,创建一个新的Socket:Socket_New,并为它分配一个文件描述符。如果您在阅读过程中发现疏漏和错误,请您尽快和编者取得联系 中国科学技术大学多媒体技术与网络通信实验室(copyright 1999)Socket_New 即标识了此次建立的连接,可被己方用来向连接的另一方发送和接收数据(wr
33、ite/read,send/recv)。同时,原Socket 仍然保持打开状态不变,继续用于等待网络连接请求。如果该Socket 的暂存队列中没有待处理的连接请求,根据Socket 的特征选项(是否non_blocking),accept()函数将选择两种方式:如果该 Socket 不是 non_blocking 型的,accept()将一直等待,直到收到一个连接请求后才返回;如果该Socket 是 non_blocking 型的,那么accept()将立即返回,但如果没有连接请求,只返回错误信息,不创建新的Socket_New。accept()返回后,如果创建了新的Socket_New 来标
34、识新建立的连接,那么参数addr 指定的结构里面将会有对方的地址信息,addrlen 是地址信息的长度。关于 accept()的进一步信息,如:如何检测某Socket 有无待处理的连接请求、如何在使用 accept()接受连接请求之前先获取连接对方的地址、如何根据获取的对方地址信息拒绝该连接请求等,请参阅Linux manual,此处不再累述。参数说明:s Socket 描述符。addr accept()接受连接后,在 addr 指向的结构中存放对方的地址信息。如果是 AF_INET Socket,该地址信息就是对方的IP 地址和端口号。addrlen 在调用 accept()之前,*addr
35、len 必须被设置为addr 数据结构的合法长度。在 accept()返回之后,*addrlen 中是对方地址信息的长度。返回值:如果正常创建了一个新的连接,那么返回非负的整数:即新连接的Socket 描述符(注意,用于等待连接请求的原Socket 保持打开状态不变,可用于接收新的连接请求。);否则,返回-1,errno 是系统错误码。connect:建立连接#include#include int connect(int sockfd,struct sockaddr*serv_addr,int addrlen);前面提到的函数,如bind、listen、connect 等,都是用于被动地等待
36、对方建立连接时需要使用的,而connect()函数,则是主动地向对方建立连接时使用的。connect()使用一个事先打开的 Socket,和目的方(即通信对方,或称服务器一方)地址信息,向对方发出连接建立请求。一个完整的Socket 通信发起过程可简单地图示为:主动发起方(客户方)被动接受方(服务方)listen(sock_w);等待连接建立请求connect(sock_s)发送连接建立请求 sock_d=accept();接受连接建立请求发送应答收到连接建立应答 此时,在sock_s 和 sock_d 之间,一个连接就建立完毕。如果是SOCK_STREAM型的Socket,通常只用conne
37、ct()建立一个正常的连接。但如果是如果您在阅读过程中发现疏漏和错误,请您尽快和编者取得联系 中国科学技术大学多媒体技术与网络通信实验室(copyright 1999)SOCK_DGRAM型的 Socket,connect()函数并不象上图中那样向目的方发出连接建立请求,而只是简单地用给出的地址设置该Socket 的目的地址,以后该Socket 的无连接数据报就发往该目的地址。因此,对于SOCK_DGRAM型的 Socket,可以多次调用connect()来改变该Socket 的目的地址。SOCK_DGRAM型的 Socket 与本实验关系不大,故不再详述。参数说明:sockfd Socket
38、 描述符。serv_addr 通信目的方的地址。其格式参见bind()的说明。addrlen 目的地址长度。返回值:连接正常建立时返回0;否则,返回-1,系统错误码在errno 中。send/recv:用 Socket 发送和接收数据#include#include int send(int s,const void*msg,int len,unsigned int flags);int sendto(int s,const void*msg,int len,unsigned int flags,const struct sockaddr*to,int tolen);int recv(int
39、s,void*buf,int len,unsigned int flags);int recvfrom(int s,void*buf,int len,unsigned int flags,struct sockaddr*from,int*fromlen);在连接建立完成后,通信双方就可以使用以上这些函数来进行数据的发送和接收操作。其中,send和 recv 用于连接建立以后的发送和接收;sendto和 recvfrom 用于非连接的协议。对于非non_blocking 型的 Socket,send 将等待数据发送完后才返回;对于non_blocking 型的 Socket,send将立即返回,
40、用户程序需要用select()函数决定网络发送是否结束。类似地,对于非 non_blocking 型的 Socket,若系统没有收到任何数据,recv 将等待接收数据到达后才返回;对于 non_blocking 型的 Socket,recv 将立即返回,并返回错误信息或者接收到的数据字节数。sendto和 recvfrom 因为是非连接型的发送和接收,必须在参数中给出目的地址或者存放源地址的空间。参数说明:s Socket 描述符;msg,buf 存放接收或者发送数据的存储空间;len 接收或者发送数据的字节数;to,from sendto 和 recvfrom 所使用的,目的方地址和存放源地
41、址的空间;tolen,fromlen 目的地址和源地址空间大小。flag 通常设为0,详细说明请参见Linux Manual。返回值:send/sendto 返回实际发送的数据字节数,或者-1,表示出错;recv/recvfrom 返回实际接收到的数据字节数,或者-1,表示出错。read/write:用系统文件操作进行Socket 通信#include ssize_t read(int fd,void*buf,size_t count);ssize_t write(int fd,const void*buf,size_t count);如果您在阅读过程中发现疏漏和错误,请您尽快和编者取得联系
42、中国科学技术大学多媒体技术与网络通信实验室(copyright 1999)在连接建立完成后,对于连接建立过程中被动的一方,在accept()正常返回后,它返回一个新的Socket,并且为该Socket 分配了一个文件描述符;对于连接请求发起方,connect()正常返回后,相应的Socket 中也包含有已分配的文件描述符。因此,可以使用标准的Unix文件读写函数read()/write()来进行 Socket 通信。要注意的是,由于网络数据和磁盘文件不一样,不是已经准备好的,因此,每次读写操作不一定能传送完指定长度的数据,需要由程序反复进行剩余部分的传送。另外,文件描述符是较底层的文件操作参数
43、,不同于C 语言中常用的FILE*。FILE*是使用 fread/fwrite 函数来进行读写操作的。参数说明:fd 文件或者Socket 描述符。buf 数据缓冲区。count 数据字节数。返回值:正常时,返回所读写的字节数(注意,可能小于count 参数指定的数目);否则,返回-1,errno 是系统错误码。getsockopt/setsockopt:获取、设置Socket 特征选项。#include#include int getsockopt(int s,int level,int optname,void*optval,int*optlen);int setsockopt(int s
44、,int level,int optname,const void*optval,int optlen);由于在普通的Socket 编程中很少涉及这些选项,在这里不作介绍。non_blocking 特性:由于前面多处提到non_blocking 特性,这里介绍一下如何设置这种特性。在这里,我们又可以看到Socket 和文件描述符在Unix 系统中的相似性。实际上 non_blocking 特性也是通过 Unix 文件操作函数来设置的:#include#include fcntl(socket,F_SETFL,O_NONBLOCK);数据格式转换#include unsigned long in
45、t htonl(unsigned long int hostlong);unsigned short int htons(unsigned short int hostshort);unsigned long int ntohl(unsigned long int netlong);unsigned short int ntohs(unsigned short int netshort);数据格式转换函数提供和硬件平台无关的、主机数据字节顺序和网络字节顺序之间的转换。由于各种平台CPU 结构的不同,在不同的硬件平台下,主机的字节顺序有两种情况:Intel 80 x86 和 SUN Sparc
46、CPU 的低位在前格式和Motorola CPU(68000、PowerPC)等的高位在前格式。网络数据交换要求网络中所有的int 型数据都有统一的字节顺序:高位在前格式,因此在Socket 函数库中提供了以上统一的字节顺序转换函数。在 Socket 程序中使用的地址数据,如端口号等,都必须遵循这样统一的字节顺序。因如果您在阅读过程中发现疏漏和错误,请您尽快和编者取得联系 中国科学技术大学多媒体技术与网络通信实验室(copyright 1999)此,在本实验的例子程序中,在bind()函数、connect()函数等涉及struct sockaddr_in 地址数据的场合,都可以看到以上转换函数
47、的使用,以加强源程序的可移植性。主机名字/地址数据查询为配合 DNS 的使用,尽量方便Internet 主机名字的记忆,避免使用烦琐的数字式IP 地址,Socket 函数库提供了方便的主机名字查询函数。struct hostent struct hostent 是一个关于主机地址信息的数据结构,其中包含从DNS 服务器得到的比较全面的主机信息。gethostbyname()和 gethostbyaddr()都返回这样的数据结构。实际使用时,可用此结构中的地址信息来设置bind()和 connect()函数参数中的struct sockaddr_in中的地址,以支持DNS 名字的使用。参见本实验
48、的具体示例。#include struct hostent char*h_name;/*official name of host*/char*h_aliases;/*alias list*/int h_addrtype;/*host address type*/int h_length;/*length of address*/char*h_addr_list;/*list of addresses from name server*/;#define h_addr h_addr_list0 /*address,for backward compatibility*/这其中最常用的是h_ad
49、dr,即主机的缺省地址(因为该主机名字可能对应多个地址)。gethostbyname#include struct hostent*gethostbyname(char*name);根据 DNS 名字,查找主机地址信息。name 可以是DNS 名字,如”,也可以是 IP 地址串:”202.38.64.2”。gethostbyaddr#include struct hostent*gethostbyaddr(char*addr;int len,int type);根据 IP地址查找主机地址信息。addr是整数格式的IP地址指针,如:unsigned char addr4202,38,64,2,在 Internet 协议中,len 必须为 4,type 必须为 AF_INET。要注意的是:如果只知道主机的IP 地址,而且DNS服务器中没有登记该主机,用gethostbyname 总能得到适当的主机地址信息,它只需要简单地将ASCII 形式的 IP 地址转换为二进制格式。但如果使用gethostbyaddr,却得不到所需要的地址信息,因为此函数完全依靠 DNS 服务器进行IP 到 DNS 名字的转换,不作其它的处理。关于 Socket 编程接口,就介绍以上这些。作为普通的TCP/IP 应用,它们已经足够了。在本实验的示例中,可以看到它们的具体应用。关于Socket 接口的进一步资料,请
限制150内