《linux环境SOCKET编程.doc》由会员分享,可在线阅读,更多相关《linux环境SOCKET编程.doc(25页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、计算机网络互连课程设计报告题 目: linux环境SOCKET编程实现多机通信 学生姓名: 何琼 学 号: 151021069 专业班级: 计算机科学与技术 02104班 同组姓名: 王春喜 陈立志 指导教师: 熊齐 设计时间: 2005-11-18 指导老师意见:评定成绩: 签名: 日期:摘 要套接字Socket接口最早是在BSD UNIX上实现的,是应用最广泛的一套应用程序接口。在UNIX系统中,网络应用编程界面有两类:UNIXBSD的套接字(socket)和UNIXSystemV的TLI。由于Sun公司采用了支持TCP/IP的UNIXBSD操作系统,使TCP/IP的应用有更大的发展,其网
2、络应用编程界面套接字(socket)在网络软件中被广泛应用,至今已引进微机操作系统DOS和Windows系统中,成为开发网络应用软件的强有力工具。本设计是面向客户服务器模型设计,针对客户和服务器提供不同的套接字操作。客户随机申请一个套接字号,服务器拥有全局公认的套接字号,任何客户可以向它发出连接请求和信息请求。关键字:套接字 客户服务器模型 TCP/IP UNIXBSD Linux1 课程设计目的和意义Linux以其源代码公开闻名于世,并以其稳定性和可靠性雄霸操作系统领域,在网络应用技术方面使用得更加广泛。很久以来它就是Windows的重要对手之一。随着网络时代的来临,Linux的这种优势已变
3、得更加突出。随着网络技术的发展,网络结构已从过去的主机/终端型、对等型发展到现在广为使用的客户机/服务器型。客户机/服务器模型应用十分广泛,在Internet上WWW,E-mail,FTP等都是基于这种模型的。在面向连接的通信模式下,服务器打开监听端口,监听网络上其它客户机向该服务器发出的连接请求,当收到一个请求信号时与该客户机建立一个连接,之后两者进行交互式的通信在网络上,很多应用程序都使用TCP协议或UDP协议与其他机器上的应用程序进行通信。但是,在一台机器上,可能同时有多个应用程序在运行,那么,当目的机在收到数据后,怎么知道这些数据是发送给该机上的哪个应用程序的呢?为此,TCP/IP协议
4、中引进了端口(port)的概念,利用端口来区分应用程序,每个端口与一个应用程序相联系。每个端口有一个16位(二进制)的标识符,称为端口号。由于每个主机上的端口号是独立分配的,所以它不可能全局唯一。端口号与主机的IP地址合起来,就构成了套接字Socket,它能在全网范围内唯一标识某个主机的某个端口,也即套接字Socket能够唯一标识某个应用程序的位置。UNIX系统的I/O命令集,是从Maltics和早期系统中的命令演变出来的,其模式为打开一读/写一关闭(open-write-read-close)。在一个用户进程进行I/O操作时,它首先调用“打开”获得对指定文件或设备的使用权,并返回称为文件描述
5、符的整型数,以描述用户在打开的文件或设备上进行I/O操作的进程。然后这个用户进程多次调用“读/写”以传输数据。当所有的传输操作完成后,用户进程关闭调用,通知操作系统已经完成了对某对象的使用。在UNIX系统中,任何对I/O的操作都是通过读或写一个文件描述符来实现的。一个文件描述符只是一个简单的整形数值,它代表一个被打开的文件(这里的文件指广义的UNIX文件)。在TCP/IP网络应用中,通信的两个进程间相互作用的主要模式是客户/服务器模式(Client/Servermodel),即客户向服务器发出服务请求,服务器接收到请求后,提供相应的服务。客户/服务器模式的建立基于以下两点:首先,建立网络的起因
6、是网络中软硬件资源、运算能力和信息不均等,需要共享,从而造就拥有众多资源的主机提供服务,资源较少的客户请求服务这一非对等作用。其次,网间进程通信完全是异步的,相互通信的进程间既不存在父子关系,又不共享内存缓冲区,因此需要一种机制为希望通信的进程间建立联系,为二者的数据交换提供同步,这就是基与客户/服务器模式的TCP/IP。 客户/服务器模式过程中采取的是主动请求方式: 首先服务器方要先启动,并根据请求提供相应服务: 1.打开一通信通道并告知本地主机,它愿意在某一公认地址上(周知口,如FTP为21)接收客户请求; 2.等待客户请求到达该端口; 3.接收到重复服务请求,处理该请求并发送应答信号。接
7、收到并发服务请求,要激活一新进程来处理这个客户请求(如UNIX系统中用fork、exec)。新进程处理此客户请求,并不需要对其它请求作出应答。服务完成后,关闭此新进程与客户的通信链路,并终止。 4.返回第二步,等待另一客户请求。 5.关闭服务器 客户方: 1.打开一通信通道,并连接到服务器所在主机的特定端口; 2.向服务器发服务请求报文,等待并接收应答;继续提出请求. 3.请求结束后关闭通信通道并终止。 TCP/IP协议被集成到UNIX内核中时,相当于在UNIX系统引入了一种新型的I/O操作。UNIX用户进程与网络协议的交互作用比用户进程与传统的I/O设备相互作用复杂得多。首先,进行网络操作的
8、两个进程客户/服务器同机器上,如何建立它们之间的联系?其次,网络协议存在多种,如何建立一种通用机制以支持多种协议?这些都是网络应用编程界面所要解决的问题。在TCP/IP协议中,套接字对应的端口号由于是用16位两进制来表示的,所以它的范围从0到65535。在实际应用中,小于1024的端口号常被系统保留,用于一些预定义的服务,如端口21用于FTP协议,端口25用于EMAIL,Web服务缺省的端口号为80。 TCP/IP协议也采用了Client/Server模型,使用TCP/IP协议的网络应用程序分为两类:一类应用程序为其他主机提供服务,这类应用程序就是Client/Server模型中的服务程序。另
9、一类应用程序使用服务程序提供的服务,它们主动向服务程序发送连接请求,这类程序就是客户程序。客户程序可以任意选择其进行通信的端口的端口号,而服务程序(特别是一些众所周知的服务)往往使用较固定的端口号。服务程序运行后,就在各自的端口上等待。客户程序如要使用某一台主机的相应的服务,只要往该服务对应的套接字上发送数据即可。2 需求分析 2.1网间进程通信 进程通信的概念最初来源于单机系统。由于每个进程都在自己的地址范围内运行,为保证两个相互通信的进程之间既互不干扰又协调一致工作,操作系统为进程通信提供了相应设施,如UNIXBSD中的管道(pipe)、命名管道(namedpipe)和软中断信号(sign
10、al),UNIXsystemV的消息(message)、共享存储区(sharedmemory)和信号量(semaphore)等,但都仅限于用在本机进程之间通信。网间进程通信要解决的是不同主机进程间的相互通信问题(可把同机进程通信看作是其中的特例)。为此,首先要解决的是网间进程标识问题。同一主机上,不同进程可用进程号(processID)唯一标识。但在网络环境下,各主机独立分配的进程号不能唯一标识该进程。 其次,操作系统支持的网络协议众多,不同协议的工作方式不同,地址格式也不同。因此,网间进程通信还要解决多重协议的识别问题。图1不同的BSP版以及重要的TCP/IP特征为了解决上述问题,TCP/I
11、P协议引入了下列几个概念。 端口 网络中可以被命名和寻址的通信端口,是操作系统可分配的一种资源。 按照OSI七层协议的描述,传输层与网络层在功能上的最大区别是传输层提供进程通信能力。从这个意义上讲,网络通信的最终地址就不仅仅是主机地址了,还包括可以描述进程的某种标识符。为此,TCP/IP协议提出了协议端口(protocolport,简称端口)的概念,用于标识通信的进程。 端口是一种抽象的软件结构(包括一些数据结构和I/O缓冲区)。应用程序(即进程)通过系统调用与某端口建立连接(binding)后,传输层传给该端口的数据都被相应进程所接收,相应进程发给传输层的数据都通过该端口输出。在TCP/IP
12、协议的实现中,类似于一般的I/O操作,进程获取一个端口,相当于获取本地唯一的I/O文件,可以用一般的读写原语访问之。 类似于文件描述符,每个端口都拥有一个叫端口号(portnumber)的整数型标识符,用于区别不同端口。由于TCP/IP传输层的两个协议TCP和UDP是完全独立的两个软件模块,此各自的端口号也相互独立,如TCP有一个255号端口,UDP也可以有一个255号端口,二者并不冲突。 端口号的分配是一个重要问题。有两种基本分配方式:第一种叫全局分配,这是一种集中控制方式,由一个公认的中央机构根据用户需要进行统一分配,并将结果公布于众。第二种是本地分配,又称动态连接,即进程需要访问传输层服
13、务时,向本地操作系统提出申请,操作系统返回一个本地唯一的端口号,进程再通过合适的系统调用将自己与该端口号联系起来(绑扎)。TCP/IP端口号的分配中综合了上述两种方式。TCP/IP将端口号分为两部分,少量的作为保留端口,以全局方式分配给服务进程。因此,每一个标准服务器都拥有一个全局公认的端口(即周知口,well-knownport),即使客户/服务器不同机器上,其端口号也相同。剩余的为自由端口,以本地方式进行分配。TCP和UDP均规定,小于256的端口号才能作保留端口地址 网络通信中通信的两个进程分别在客户/服务器的机器上。在互连网络中,两台机器可能位于客户/服务器的网络,这些网络通过网络互连
14、设备(网关,网桥,路由器等)连接。因此需要三级寻址: 1.某一主机可与多个网络相连,必须指定一特定网络地址; 2.网络上每一台主机应有其唯一的地址; 3.每一主机上的每一进程应有在该主机上的唯一标识符。 通常主机地址由网络ID和主机ID组成,在TCP/IP协议中用32位整数值表示;TCP和UDP均使用16位端口号标识用户进程。 网络字节顺序 不同的计算机存放多字节值的顺序不同,有的机器在起始地址存放低位字节(低价先存),有的存高位字节(高价先存)。为保证数据的正确性,在网络协议中须指定网络字节顺序。TCP/IP协议使用16位整数和32位整数的高价先存格式,它们均含在协议头文件中。 连接 两个进
15、程间的通信链路称为连接。连接在客户/服务器表现为一些缓冲区和一组协议机制,在外部表现出比无连接高的可靠性。 半相关 网络中用一个三元组可以在全局唯一标志一个进程: (协议,本地地址,本地端口号) 这样一个三元组,叫做一个半相关,它指定连接的每半部分。 全相关 一个完整的网间进程通信需要由两个进程组成,并且只能使用同一种高层协议。也就是说,不可能通信的一端用TCP协议,而另一端用UDP协议。因此一个完整的网间通信需要一个五元组来标识: (协议,本地地址,本地端口号,远地地址,远地端口号) 这样一个五元组,叫做一个相关(association),即两个协议相同的半相关才能组合成一个合适的相关,或完
16、全指定组成一连接。 2.2服务方式 在网络分层结构中,各层之间是严格单向依赖的,各层次的分工和协作集中体现在客户/服务器之间的界面上。“服务”是描述客户/服务器之间关系的抽象概念,即网络中各层向紧邻上层提供的一组操作。下层是服务提供者,上层是请求服务的用户。服务的表现形式是原语(primitive),如系统调用或库函数。系统调用是操作系统内核向网络应用程序或高层协议提供的服务原语。网络中的n层总要向n+1层提供比n-1层更完备的服务,否则n层就没有存在的价值。 服务器socket ( )服务响应服务请求建立连接处理服务请求bind ( )listen ( )accept ( )阻塞并等待客户的
17、连接请求read ( )write ( )客户socket ( )connect ( )write ( )read ( )在OSI的术语中,网络层及其以下各层又称为通信子网,只提供点到点通信,没有程序或进程的概念。而传输层实现的是“端到端”通信,引进网间进程通信概念,同时也要解决差错控制,流量控制,数据排序(报文排序),连接管理等问题,为此提供不同的服务方式: 面向连接(虚电路)或无连接 面向连接服务是电话系统服务模式的抽象,即每一次完整的数据传输都要经过建立连接,使用连接,终止连接的过程。在数据传输过程中,各数据分组不携带目的地址,而使用连接号(connectID)。本质上,连接是一个管道,
18、收发数据不但顺序一致,而且内容相同。TCP协议提供面向连接的虚电路。 图2面向连接的客户/服务器模式无连接服务是邮政系统服务的抽象,每个分组都携带完整的目的地址,各分组在系统中独立传送。无连接服务不能保证分组的先后顺序,不进行分组出错的恢复与重传,不保证传输的可靠性。UDP协议提供无连接的数据报服务。服务器服务响应服务请求bind()recvfrom()阻塞并等待客户数据处理服务请求sendto()socket()客户recvfrom()sendto()bind()socket()图3无连接的客户/服务器模式2.3 TCP/IP Socket的基本原理。TCP/IP对外提供的只是编程接口而非用
19、户服务,真正的用户服务还得靠编写相应的服务程序来实现。TCP/IP的Socket API编程接口构成了使用协议的网络应用程序视图。服务程序客户程序Socket APITCP/IP协议物理介质 图4网络应用程序、Socket API和TCP/IP的关系Socket API在BSD UNIX中首次提出,其目的是为了解决网络间程序通讯的问题。就其原理而言,面向连接的Socket类似于电话系统,无连接的Socket类似于电报系统。Socket实质上是为网络程序提供了通讯的端点号。对于每个网络程序的一个Socket,它首先有一个半相关的端点号的描述:协议,本地地址,本地端口,如果它是与另一个Socket
20、连接了的,则有一个相关的端点描述:协议,本地地址,本地端口,远程地址,远程端口。每个Socket有一个本地唯一的由操作系统分配的编号。2.4 服务器程序可分为两类:并发服务器(Concurrent Server)和串行服务器(Iterative Server)。前者主要针对实时性的客户/服务器模式,后者主要针对服务量小的客户/服务器模式。2.5 TCP串行服务器程序串行服务器程序是这样的:每次它只能为一个连接过来的客户程序提供服务,只有在完全处理了一个客户的请求后,才能响应下一个客户的请求,即按照FIFO的原则响应请求。一般很少使用串行服务器程序,不过诸如时间/日期等服务量小的且实时性要求不高
21、的服务器程序可以使用该方式。从进程控制的角度来讲,该方式的速度是最快的,因为它不进行进程控制,系统开销小。2.6传统的TCP进程并发服务器程序在这种方式下,并发服务器程序在收到客户程序请求后,派生出一个子进程来为该客户程序服务,自己则回到等待状态,准备接收下一个客户程序的请求,子进程在服务完成后退出。其中,作为父进程的并发服务器程序成为主服务器(master),具体处理客户请求的子进程成为从服务器(slave)。响应子进程接管连接客户程序slave服务请求父进程返回生成子进程fork()连接connect()处理请求master接受客户请求accecp()图5传统的TCP进程并发服务器程序框架
22、并发服务器的问题在于派生子进程(fork()操作)时会消耗CPU的很多时间,这对需要响应数目众多的客户进程的服务器进程所在的系统是极为不利的,例如对于Web服务器就是这样。2.7 TCP预先派生子进程并发服务器程序在传统的TCP进程并发服务器程序的基础上,可以对响应方式进行一些改造。传统的TCP进程并发服务器程序的响应方式是即响应即派生子进程。现在将这种方式改变为:服务器程序启动后就先生成若干子进程以备响应,这些子进程构成服务子进程组,而父进程则成了监控进程。fork可用子进程组父进程子进程1子进程2子进程3客户2客户1子进程N图6 TCP预先派生子进程这里需要解决的问题是:怎样保持一定量的可
23、用子进程;服务请求到达时,唤醒子进程的机制应该怎样以及父进程怎样将必要的信息传递给子进程。父进程监视可用子进程的数量,当数量低于某个阈值时就再派生一些子进程,当数量高于某个阈值时就终止一些可用子进程。当然,总的子进程数量也应当有个上限值,以防止系统资源消耗完。这样就使得可用子进程数及总的子进程数保持在一定范围之内了。预先生成得子进程在各自调用accept()后进入睡眠状态。由于这些子进程共用一个socket结构,当一个可户请求到达时,就会造成惊群(thundering herd)唤醒所有的子进程。当然,只有最先被调度的子进程才会获得客户的连接,其他的子进程会再次进入睡眠状态。这种情况会导致系统
24、性能的下降。解决这个问题的方法是给accept上锁,即保证accept操作的原子性。有些UNIX系统在内核已经解决了这一问题,就无须再给accept上锁了。如果子进程只是父进程的副本,基本上就不用额外考虑进程通讯的问题了。如果将父进程改造成类似于inetd的守护进程(启动后先调用fork()生成子进程,再通过exec系统调用执行服务处理程序),就必须解决父进程同子进程之间的通讯问题。UNIX下进程通讯的机制有多种,如管道(pipe),具名管道(named pipe),IPC消息(InterProcess Communication Message)等。这里我们使用IPC消息队列来向子进程传递s
25、ocket描述字。图7 使用IPC消息机制的TCP预先派生子进程方法3 方案设计Socket是面向客户/服务器模式设计的,它针对客户和服务器程序提供了不同的系统调用。同时它还分为面向连接和无连接两种类型。以下列出了Socket API的基本函数3.1 函数socket() 函数socket()用于创建一个socket描述符,其定义如下: #include #include int socket(int domain,int type,int protocol); 参数domain用于指定要创建的套接字使用的协议簇,可设置为AF_INET(TCP/IP协议簇),AF_UNIX(UNIX域协议簇)
26、或AF_ISO(ISO协议簇).参数type指定套接字的类型,可以为SOCK_STREAM(流套接字),SOCK_DGRAM(数据报套接字),SOCK_RAW(原始套接字)。参数protocol一般可设置成0,表示通过指定的协议簇和需要的套接字的类型,就是系统可以确定程序需要使用的具体协议,比如流套接字和数据报套接字分别对应的TCP和UDP协议。以下代码将创建一个TCP的套接字描述符:int sock_fd=socket(AF_INET,SOCK_STREAM,0);if (sock_fd0) perror(socket creating error); exit(1);如果socket调用失
27、败,将返回-1,并将设置errno,以标识错误原因。3.2 函数connect()* 函数定义函数connect()用于向服务器发出连接请求。其定义如下:#include#includeint connect(int sockfd,struct sockaddr* servaddr,int addren);参数sockfd是在socket()函数中返回的套接字描述符。参数servaddr用于指定服务器的地址和服务器端使用的传输端口号,以及地址类型。参数addrlen指定这个套接字地址的长度。* 套接字地址结构struct sockaddr定义了一种通用的套接字地址,其定义如下:struct s
28、ockaddr unsigned short sa_family; /*地址类型*/ char sa_data14; /*14字节的协议地址*/;其中,sa_family为套接字的协议簇地址类型,比如TCP/IP协议簇的地址类型为AF_INET;参数sa_data中存储具体的地址内容。每一种具体的协议簇都将定义自己的协议地址类型,TCP/IP协议簇定义了sockaddr_in 描述自身的协议地址:struct in_addr _u32 s_addr; /*unsigned long 32比特的IP地址 */;struct sockaddr_in short int sin_family; /*
29、地址类型*/ unsigned short int sin_port; /*端口号*/ struct in_addr sin_addr; /*Internet地址*/ unsigned char _pad_SOCK_SIZE-sizeof(short int)-sizeof(unsigned short int)-sizeof(struct in_addr); /*填充比特*/;在编写TCP和UDP的代码时,通常直接使用struct sockaddr_in 结构来赋值,然后将它强制转化成套接字的地址结构。 *网络字序及其相关函数族由于不同计算机内部对变量的字节存放的顺序可能不同。有的系统设计时
30、,将变量的高端放在高字节,有的恰恰相反。所以,如果在将数据发到网络前不进行调整的话,a=0x0001将有可能被理解为a=0x0100。为了消除这种差异,协议规定在Internet上使用的网络字节顺序采用顺序存放。在编写程序时,无论本地字序是否与网络字序相同,都应调用相应的函数进行转化。Linux系统提供了4个库函数来进行字节顺序的转化:#includeunsigned long int htonl(unsigned long int hostlong);unsigned short int htons(unsigned short int hostshort);unsigned long in
31、t ntohl(unsinged long int netlong);unsigned short int ntohs(unsigned short int netshort);htonl代表host to network long对unsinged long int 型的变量转化成网络中使用的字节顺序。htons表示host to network short,对unsigned short int 类型的变量进行转化。ntohs和ntohl的功能和htons、htonl相反。*connect()函数使用示例:struct sockaddr_in serv_addr;int ret,sock_
32、fd;bzero(&serv_addr,sizeof(struct sockaddr_in);serv_addr.sin_family=AF_INET;serv_addr.sin_port=htons(SERVER_PORT);ret=inet_aton(127.0.0.1,&serv_addr.sin_addr);if (ret0) perror(convert IP address error); exit(1);ret=connect(sock_fd,(struct sockaddr*)&serv_addr,sizeof(struct sockaddr_in);if (ret0) per
33、ror(connect to server error); exit(1);和socket()函数相似,如果连接失败,将返回-1,并使用errno还表示错误的原因。3.3 函数band()bind()将一个未名名的套接字一个名字,在函数socket()中,核心只是创建了一个套接字结构,但是这个套接字将工作在哪个传输层端口上,核心并没有指定。如果进程需要使用某一个固定端口,则需要程序来提供端口信息。在server进程中,由于进程通常使用的是一个熟知端口,所以需要调用bind()向系统登记一个固定端口。bind()函数定义如下:#include#includeint bind(int sockfd
34、,struct sockaddr *my_addr,int addrlen);sockfd是由socket()函数返回的套接字描述符。my_addr是一个指向struct sockaddr的指针,包含有关的地址信息:名称、端口和IP地址。addrlen为地址长度,可设置为sizeof(struct sockaddr)。下面几行代码显示了bind()的使用:struct sockaddr_in serv_addr;bzero(&serv_addr,sizeof(struct sockaddr_in);serv_addr.sin_family=AF_INET;serv_addr.sin_port=
35、htons(SERVER_PORT);serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);ret=(bind(sock_fd,(struct sockaddr*)&serv_addr,sizeof(serv_addr);if (ret0) perror(bind to SERVER_PORT error); exit(1);这里的INADDR_ANY的含义是任何网络设备接口。对于一台只有一个IP地址的主机,它就对应于它的IP地址。3.4 函数listen()函数listen()把套接字转化为一个被动倾听套接字,并在套接字指定的端口上开始倾听。函数形式如下:#
36、includeint listen(int sockfd,int backlog);参数sockfd是在bind之后已经命名的套接字,backlog指定连接请求队列的最大长度。如果函数成功返回0,否则返回-1。由函数socket创建的是主动的套接字,也就是说是可以通过调用connect主动请求连接到某个服务器进程的套接字。但是作为服务器进程,通常应当在某个熟知端口上等待,所以需要调用listen向系统申明自己希望在哪个端口倾听,哪个端口上的TCP连接状态将被设置成LISTEN。3.5 函数accept()函数accept()从完全建立连接的队列中接受一个连接。函数的形式如下:#includei
37、nt accept(int sockfd,struct sockaddr *addr,int *addrlen);参数sockfd是被设置为倾听的被动套接字描述符,参数addr是指向套接字地址结构的指针,它将保持连接对端的地址信息。参数addrlen是对端套接字的长度。如果程序对客户进程的地址不感兴趣,则可以将addr和addrlen设置为NULL。当accept()成功返回时,将返回一个新的套接字描述符。这个套接字不同于服务进程用于倾听的被动套接字描述符(listen socket descriptor),被动套接字只能用于接收客户进程的连接请求。而这个新的套接字描述符就象打开一个新文件返回
38、文件的描述符一样,进程可以使用这个新的套接字描述符向客户端写数据或者从对端接受数据。这个新套接字描述符称为连接套接字描述符(connected socket descriptor)。3.6 函数read()和write()read()和write()用于数据的接收和发送。其形式为:int read(int fd, char *buf, int len);int write(int fd, char *buf, int len);参数是由connect返回(客户进程)或者accept(服务器进程)的连接套接字描述符。read中的buf是应用的发送缓冲区,而write中的buf是应用的接收缓冲区。
39、参数len是用于指定希望发送或接收的数据的字节数。如果read函数成功,将返回实际读取的数据的字节数。如果read返回0,则表示对端已经关闭了写管道,相当于收到文件结束符EOF。如果read出错,则返回-1,并设置errno。write和read相似,将返回实际写出的数据的字节数。3.7 函数recv()和send()函数recv()的功能和read()相似,它在read()的功能的基础上,增加了4个参数用来对套接字的读操作进行控制,函数形式如下:#include#includeint recv(int sockfd,void *buf,int len,int flags);函数send()的
40、功能和write()相似,它在write()的功能的基础上,增加了4个参数用来对套接字的写操作进行控制,函数形式如下:#include#includeint send(int sockfd,void *buf,int len,int flags);3.7 函数close()函数close()用于关闭一个套接字描述符。套接字描述符和文件描述符的操作相似。其形式如下:#includeint close(int sockfd);通常close在关闭一个TCP连接时,close将立即返回。进程将不能再使用套接字描述符来访问套接字,但是TCP可能并没有删除套接字结构,因为可能在发送数据缓冲区还有数据没有
41、发送完。TCP将继续发送剩余的数据,并在最后的数据段附加FIN控制信息。4 方案的实施 以下是服务程序和客户程序通过Socket进行通信的流程图:服务器的工作流程:首先调用socket函数创建一个Socket,然后调用bind函数将其与本机地址以及一个本地端口号绑定,然后调用listen在相应的socket上监听,当accpet接收到一个连接服务请求时,将生成一个新的socket。服务器显示该客户机的IP地址,并通过新的socket向客户端发送字符串Hello,you are connected!。最后关闭该socket。 该服务器软件代码如下: #include #include #incl
42、ude #include #include #include #include #include #define SERVPORT 3333 /*服务器监听端口号 */ #define BACKLOG 10 /* 最大同时连接请求数 */ main() int sockfd,client_fd; /*sock_fd:监听socket;client_fd:数据传输socket */ struct sockaddr_in my_addr; /* 本机地址信息 */ struct sockaddr_in remote_addr; /* 客户端地址信息 */ if (sockfd = socket(A
43、F_INET, SOCK_STREAM, 0) = -1) perror(socket创建出错!); exit(1); my_addr.sin_family=AF_INET; my_addr.sin_port=htons(SERVPORT); my_addr.sin_addr.s_addr = INADDR_ANY; bzero(&(my_addr.sin_zero),8); if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr) = -1) perror(bind出错!); exit(1); if (list
44、en(sockfd, BACKLOG) = -1) perror(listen出错!); exit(1); while(1) sin_size = sizeof(struct sockaddr_in); if (client_fd = accept(sockfd, (struct sockaddr *)&remote_addr, &sin_size) = -1) perror(accept出错); continue; printf(received a connection from %sn, inet_ntoa(remote_addr.sin_addr); if (!fork() /* 子进程代码段 */ if (send(client_fd, Hello, you are connected!n, 26, 0) = -1) perror(send出错!); c