1、n UNIX套接字网络编程接口的产生与发展过程套接字网络编程接口的产生与发展过程n 套接字与套接字与UNIX操作系统的关系操作系统的关系n 套接字编程的基本概念套接字编程的基本概念n 面向连接的套接字编程面向连接的套接字编程n 无连接的套接字编程无连接的套接字编程n 原始套接字原始套接字n Linux系统的网络编程接口系统的网络编程接口第2章 UNIX中的套接字网络编程接口本章提要本章提要2.1.1 问题的提出问题的提出 站在应用程序实现的角度,应用程序如站在应用程序实现的角度,应用程序如何方便地使用协议栈软件进行通信呢?何方便地使用协议栈软件进行通信呢?如果能在应用程序与协议栈软件之间提如果
2、能在应用程序与协议栈软件之间提供一个软件接口,就可以方便客户与服务器供一个软件接口,就可以方便客户与服务器软件的编程。软件的编程。2.1 UNIX套接字网络编程接口的产生与发展 套接字应用程序编程接口是网络应用程序套接字应用程序编程接口是网络应用程序通过网络协议栈进行通信时所使用的接口,即通过网络协议栈进行通信时所使用的接口,即应用程序与协议栈软件之间的接口,简称应用程序与协议栈软件之间的接口,简称套接套接字编程接口字编程接口(Socket API)。它定义了应用程序与协议栈软件进行交互它定义了应用程序与协议栈软件进行交互时可以使用的一组操作,决定了应用程序使用时可以使用的一组操作,决定了应用
3、程序使用协议栈的方式、应用程序所能实现的功能、以协议栈的方式、应用程序所能实现的功能、以及开发具有这些功能的程序的难度。及开发具有这些功能的程序的难度。2.1 UNIX套接字网络编程接口的产生与发展 加州大学伯克利加州大学伯克利(Berkley)分校开发并推广了一分校开发并推广了一个个包括包括 TCP/IP 互联协议的互联协议的 UNIX,称为称为BSD UNIX(Berkeley Software Distribution UNIX)操作系统,操作系统,套接字编程接口套接字编程接口是是这个操作系统这个操作系统的的一个部分一个部分。后来的许多操作系统并没有另外搞一套其它的编后来的许多操作系统并
4、没有另外搞一套其它的编程接口,而是选择了对于套接字编程接口的支持。程接口,而是选择了对于套接字编程接口的支持。由于这个套接字规范最早是由由于这个套接字规范最早是由Berkeley大学开大学开发的,一般将它称为发的,一般将它称为Berkeley Sockets规范。规范。2.1.2 套接字编程接口的起源与应用套接字编程接口的起源与应用 要想实现套接字编程接口,可以采用两要想实现套接字编程接口,可以采用两种实现方式种实现方式:一种是在操作系统的内核中增加相应的一种是在操作系统的内核中增加相应的软件,网络程序中用软件,网络程序中用系统调用系统调用的方法来实现的方法来实现(Unix/Linux)一种是
5、通过开发操作系统之外的函数库,一种是通过开发操作系统之外的函数库,网络程序中采用网络程序中采用调用库函数调用库函数的方法来实现的方法来实现(Windows)2.1.3 套接字编程接口的两种实现方式套接字编程接口的两种实现方式 UNIX操作系统对文件和所有其它操作系统对文件和所有其它的输入的输入/输出设备输出设备采用一种采用一种统一的的操作模式统一的的操作模式,就是,就是“打开打开-读读-写写-关闭关闭”(open-read-write-close)的)的I/O模式。模式。当当TCP/IP协议被集成到协议被集成到UNIX内核中的时内核中的时候,相当于在候,相当于在 UNIX系统中引入了一种新型的
6、系统中引入了一种新型的I/O 操作,就是应用程序通过网络协议栈来交操作,就是应用程序通过网络协议栈来交换数据。换数据。2.1.4 套接字通信与套接字通信与UNIX操作系统的输入操作系统的输入/输出输出 在在UNIX系统的实现中,套接字是完全系统的实现中,套接字是完全与其他与其他I/O集成在一起的。操作系统和应用集成在一起的。操作系统和应用程序都将程序都将套接字编程接口套接字编程接口也也看作看作一种输入一种输入/输输出机制出机制。但是,用户进程与网络协议的但是,用户进程与网络协议的交互作用交互作用实际要比用户进程与传统的实际要比用户进程与传统的I/O设备相互作设备相互作用要用要复杂得多复杂得多。
7、2.1.4 套接字通信与套接字通信与UNIX操作系统的输入操作系统的输入/输出输出 其次,使用套接字的应用程序其次,使用套接字的应用程序必须说明必须说明许多细节许多细节。仅仅提供。仅仅提供open、read、write、close四个过程四个过程远远不够远远不够。为避免单个套接字。为避免单个套接字函数参数过多,套接字编程接口的设计者函数参数过多,套接字编程接口的设计者定定义了多个函数。义了多个函数。2.1.4 套接字通信与套接字通信与UNIX操作系统的输入操作系统的输入/输出输出2.2 套接字编程的基本概念套接字编程的基本概念 套接口套接口是对网络中不同主机上应用进程之间进行双向通信是对网络中
8、不同主机上应用进程之间进行双向通信的的端点的抽象端点的抽象,一个套接口就是网络上进程通信的一端,提供,一个套接口就是网络上进程通信的一端,提供了应用层进程利用网络协议栈交换数据的机制了应用层进程利用网络协议栈交换数据的机制。图图2.1 电插座与电话插座的作用电插座与电话插座的作用2.2.1 什么是套接字(什么是套接字(SOCKET)2.2.1 什么是套接字什么是套接字(SOCKET)?我们应当从多个层面来理解套接字这个概我们应当从多个层面来理解套接字这个概念的内涵。念的内涵。从套接字所处的地位来讲,套接字上联应从套接字所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网用进程,
9、下联网络协议栈,是应用程序通过网络协议栈进行络协议栈进行通信的接口通信的接口,是应用程序与网络,是应用程序与网络协议栈进行协议栈进行交互的接口交互的接口。图图2.2 应用进程、套接口、网络协议栈及操作系统的关系应用进程、套接口、网络协议栈及操作系统的关系 进程、套接口、协议栈、操作系统的关系 从实现的角度来讲从实现的角度来讲,非常复杂非常复杂。套接字是一个。套接字是一个复杂的软件机构,包含了一定的数据结构,包含许复杂的软件机构,包含了一定的数据结构,包含许多选项,由操作系统内核管理。多选项,由操作系统内核管理。从使用的角度来讲从使用的角度来讲,非常简单非常简单。对于套接字的。对于套接字的操作形
10、成了一种网络应用程序的编程接口(操作形成了一种网络应用程序的编程接口(API)。)。本书把这一套本书把这一套操作套接字的操作套接字的编程接口函数编程接口函数称作称作套接字编程接口套接字编程接口,套接字是它的操作对象。,套接字是它的操作对象。总之,套接字是网络通信的基石。总之,套接字是网络通信的基石。套接字编程接口套接字编程接口2.2.2 套接字的特点套接字的特点1 1通信域通信域 套接字存在于通信域中,通信域是为了处理套接字存在于通信域中,通信域是为了处理一一般的进程般的进程通过套接字通信而引入的一种抽象概念,通过套接字通信而引入的一种抽象概念,套接字套接字通常通常只和只和同一域同一域中的中的
11、套接字套接字交换数据交换数据。如果如果数据交换要数据交换要穿越穿越域域的的边界边界,就一定要执行,就一定要执行某种某种解释解释程序。程序。本课程中,仅仅针对本课程中,仅仅针对Internet域域,并且使用,并且使用Internet协议族协议族(即即TCP/IP协议族协议族)来通信。来通信。2套接字具有三种类型套接字具有三种类型 每一个正被使用的套接字都有它确定的每一个正被使用的套接字都有它确定的类型类型,只有只有相同类型相同类型的套接字的套接字才能相互通信才能相互通信。(1)数据报套接字数据报套接字 (Datagram SOCKET)数据报套接字提供数据报套接字提供无连接的、不保证可靠的无连接
12、的、不保证可靠的独立的数据报传输服务独立的数据报传输服务。在。在Internet通信域中,通信域中,数据报套接字使用数据报套接字使用UDP数据报协议形成的进程间数据报协议形成的进程间通路,具有通路,具有UDP协议为上层所提供的服务的所有协议为上层所提供的服务的所有特点。特点。数据报套接字基于数据报套接字基于UDP协议协议 图图2.3 在在Internet通信域中,数据报套接字基于通信域中,数据报套接字基于UDP协议协议(2)流套接字流套接字(Stream SOCKET)流套接字流套接字提供提供双向的、有序的、无重复的、无记双向的、有序的、无重复的、无记录边界的、可靠的数据流传输服务录边界的、可
13、靠的数据流传输服务。在。在Internet通通信域中,流套接字使用信域中,流套接字使用TCP协议形成的进程间通路,协议形成的进程间通路,具有具有TCP协议为上层所提供的服务的所有特点,在协议为上层所提供的服务的所有特点,在使用流套接字使用流套接字传输数据之前传输数据之前,必须必须在数据的发送端和在数据的发送端和接收端之间接收端之间先建立连接先建立连接,如下页图,如下页图2.4所示。所示。流式套接字基于流式套接字基于TCP协议协议图图2.4 在在Internet通信域中,流式套接字基于通信域中,流式套接字基于TCP协议协议(3)原始套接字)原始套接字(RAW SOCKET)原始套接字原始套接字允
14、许对较低层次的协议,如允许对较低层次的协议,如IP、ICMP直接访问,或用于检验新的协议的实现。直接访问,或用于检验新的协议的实现。TCPUDP IP/ICMP Ethernet应 用 程 序流套接字数据报套接字原始套接字原始套接字3 3套接字由应用层通信进程创建,并为其服务套接字由应用层通信进程创建,并为其服务 套接字由应用层的通信进程创建,并套接字由应用层的通信进程创建,并为其服务,这为其服务,这就是说,就是说,每一个套接字每一个套接字都有都有一个相关的应用进程一个相关的应用进程,操作该套接字的代,操作该套接字的代码是该进程的组成部分。码是该进程的组成部分。4 4使用确定的使用确定的IPI
15、P地址和传输层端口号地址和传输层端口号 套接字编程时,往往在生成套接字的描述套接字编程时,往往在生成套接字的描述符后,要将符后,要将套接字套接字与与计算机上的特定的计算机上的特定的IP地址地址和传输层和传输层端口号端口号相关联相关联,这个过程称为,这个过程称为绑定绑定。一个套接口要使用一个确定的一个套接口要使用一个确定的三元组三元组网络网络地址信息,才能使它在网络中地址信息,才能使它在网络中唯一唯一地被地被标识标识。(1)不管是采用对等模式或者客户机)不管是采用对等模式或者客户机/服服务器模式,务器模式,通信双方通信双方的的应用程序应用程序都都需要开发需要开发。(2)双方所交换)双方所交换数据
16、数据的的结构结构和交换数据的和交换数据的顺序顺序有有特定的要求特定的要求,不符合现在成熟的应用层,不符合现在成熟的应用层协议的要求时。甚至,有时需要自己去开发应协议的要求时。甚至,有时需要自己去开发应用层协议,自己设计最适合的数据结构和信息用层协议,自己设计最适合的数据结构和信息交换规程。交换规程。2.2.3 套接字的应用场合套接字的应用场合2.2.4 套接字使用的数据类型和相关的问套接字使用的数据类型和相关的问题题1三种三种表示表示套接字地址套接字地址的的结构结构 在套接字编程接口中,专门定义了在套接字编程接口中,专门定义了三三种种结构型的数据类型结构型的数据类型,用来存储协议相关,用来存储
17、协议相关的的网络地址网络地址,在套接字编程接口的函数调,在套接字编程接口的函数调用中要用到它们。用中要用到它们。三种表示套接字地址的结构三种表示套接字地址的结构 (1)sockaddr 结构结构,针对,针对各种各种通信域通信域的的套接字,存储它们的地址信息:套接字,存储它们的地址信息:struct sockaddr unsigned short sa_family;/地址家族地址家族 char sa_data14;/协议地址协议地址 (2)sockaddr_in 结构结构,专门针对专门针对Internet通通信域信域,存储套接字相关的网络地址信息,例如,存储套接字相关的网络地址信息,例如IP地
18、址地址、端口号端口号等信息。等信息。struct sockaddr_in short int sin_family;/地址家族地址家族 unsigned short int sin_port;/2字节字节端口号端口号 struct in_addr sin_addr;/4字节字节IP 地址地址 unsigned char sin_zero8;/8字节全字节全0 /最后包含一个最后包含一个8字节的全字节的全0的域,以使该结构在大小上与的域,以使该结构在大小上与sockaddr相同相同三种表示套接字地址的结构三种表示套接字地址的结构 (3)in_addr 结构结构,专门专门用来存储用来存储 IP地址
19、地址:struct in_addr unsigned long s_addr;/4字节字节IP 三种表示套接字地址的结构三种表示套接字地址的结构 (4)这些数据结构组合使用的一般用法:)这些数据结构组合使用的一般用法:首先,首先,定义定义一个一个sockaddr_in的的结构实例结构实例,并将它并将它清零清零。比如:比如:struct sockaddr_in myad;memset(&myad,0,sizeof(struct sockaddr_in);一般,三种结构组合起来使用:一般,三种结构组合起来使用:然后,为这个然后,为这个myad结构结构赋值,比如:赋值,比如:myad.sin_fam
20、ily=AF_INET;myad.sin_port=htons(8080);myad.sin_addr.s_addr=htonl(INADDR_ANY);注注1:地址家族有:地址家族有AF_INET、AF_DECnet、等字符常量可等字符常量可 选,在选,在Internet域内进行通信时,应赋值域内进行通信时,应赋值AF_INET。注注2:INADDR_ANY字符常量表示该主机的任何一个字符常量表示该主机的任何一个IP地址。地址。一般,三种结构组合起来使用:一般,三种结构组合起来使用:第三步:在函数调用中使用时,将这个第三步:在函数调用中使用时,将这个结构结构强制转换为强制转换为sockadd
21、r类型类型。如:如:accept(listenfd,(sockaddr*)(&myad),&addrlen);一般,三种结构组合起来使用:一般,三种结构组合起来使用:2 2主机字节顺序和网络字节顺序主机字节顺序和网络字节顺序 多字节数据的多字节数据的字节顺序字节顺序 大端模式大端模式 (big endian):高字节放到低地址上高字节放到低地址上 小端模式小端模式 (little endian):高字节放到高地址上高字节放到高地址上 在在具体计算机中具体计算机中多字节数据的多字节数据的存储顺序存储顺序称为称为主机字节顺序主机字节顺序。不同的机器内存中主机字节顺序不相同,与不同的机器内存中主机字
22、节顺序不相同,与CPU设计有关:设计有关:Motorola 68000、PowerPC系列系列 -大端模式大端模式(big endian)Intel x86系列系列 -小端模式小端模式(little endian)多字节数据在多字节数据在网络协议报头中网络协议报头中的的 顺序顺序,称为,称为 网络字节顺序网络字节顺序。网络通信时网络通信时一律一律使用统一的使用统一的 大端模式大端模式网络字节顺序,避免主网络字节顺序,避免主 机间通信时识别兼容问题。机间通信时识别兼容问题。5612347834785612 低址低址:高址高址:表示表示 0 x12345678时时:Motorola Intel2
23、2主机字节顺序和网络字节顺序主机字节顺序和网络字节顺序 网络应用程序要在网络应用程序要在不同的不同的计算机中计算机中运行,运行,多字节数据多字节数据在各在各机器内存中存放的机器内存中存放的主机字节顺序主机字节顺序可能可能不同不同,可能是大端模式,可能是大端模式,也可能是小端模式;但是,在网络上传输时,在信包协议报头也可能是小端模式;但是,在网络上传输时,在信包协议报头字段中的字段中的网络字节顺序网络字节顺序是是一定一定的的,都是都是大端模式大端模式。所以,应用程序在编程的时候,在把主机内存变量中的所以,应用程序在编程的时候,在把主机内存变量中的IPIP地地址址和和端口号端口号装入装入网络通信套
24、接字网络通信套接字的时候,应当把它们从的时候,应当把它们从主机字主机字节顺序节顺序转换为转换为网络字节顺序网络字节顺序;相反,在;相反,在主机输出显示主机输出显示时,应从时,应从收到的收到的网络字节顺序网络字节顺序转换为转换为主机字节顺序主机字节顺序。n 在不同机器上运行网络程序,解决兼容性问题的途径:在不同机器上运行网络程序,解决兼容性问题的途径:往网络上往网络上发送前发送前:将:将IPIP地址、端口号地址、端口号转换成转换成网络字节序网络字节序;从网络上从网络上接收后接收后:将:将IPIP地址、端口号地址、端口号转换成转换成主机字节序主机字节序。四个转换函数四个转换函数 套接字编程接口特为
25、解决这个问题设置套接字编程接口特为解决这个问题设置了四个函数:了四个函数:htons():():短整数短整数主机字节顺序主机字节顺序转换为转换为 网络字节顺序网络字节顺序,用于端口号。用于端口号。htonl():():长整数长整数主机字节顺序主机字节顺序转换为转换为 网络字节顺序网络字节顺序,用于用于IP地址。地址。n 说明说明 h 代表代表host,n 代表代表 network;s 代表代表short,l 代表代表 long ntohs()():短整数短整数网络字节顺序网络字节顺序转换为转换为 主机字节顺序主机字节顺序,用于端口号。,用于端口号。ntohl()():长整数长整数网络字节顺序网
26、络字节顺序转换为转换为 主机字节顺序主机字节顺序,用于,用于IP地址。地址。这四个函数将被转换的数值作为函数的这四个函数将被转换的数值作为函数的入口参数,函数返回值是转换后的结果。入口参数,函数返回值是转换后的结果。四个转换函数四个转换函数n 说明说明 h 代表代表host,n 代表代表 network;s 代表代表short,l 代表代表 long 3两个两个IP地址转换地址转换函数函数 在因特网中,在因特网中,IP地址常常用地址常常用点分十进点分十进制制的表示方法,但在套接字中,的表示方法,但在套接字中,IP地址是地址是无符号长整型数无符号长整型数,套接字编程接口设置了,套接字编程接口设置
27、了两个函数,专门用于两种形式的两个函数,专门用于两种形式的IP地址的地址的转换。转换。(1)inet-addr 函数函数unsigned long inet_addr(const char*cp)入口参数入口参数cp:点分十进制形式点分十进制形式IP地址字符串;地址字符串;返回值返回值:无符号长整型的网络字节顺序无符号长整型的网络字节顺序 的的IP地址。地址。例:例:0 x80 00 00 01u_long“128.0.0.1”char*(2)inet_ntoa函数函数char*inet_ntoa(struct in_addr in)入口参数入口参数in:包含无符号长整型:包含无符号长整型IP
28、地址的地址的 in_addr结构变量;结构变量;返回值返回值:指向点分十进制指向点分十进制IP地址字符串地址字符串 的指针。的指针。例:例:0 x80 00 00 01u_longstruct in_addr“128.0.0.1”char*通常,我们使用域名来标识站点,可以将文字通常,我们使用域名来标识站点,可以将文字型的主机域名直接转换成型的主机域名直接转换成IP地址:地址:struct hostent*gethostbyname(const char*name);入口参数入口参数name:是站点的主机域名字符串是站点的主机域名字符串 返回值:返回值:是指向是指向hostent 结构的指针结
29、构的指针 hostent结构包含主机名,主机别名数组,返结构包含主机名,主机别名数组,返回地址的类型(一般是回地址的类型(一般是AF_INET),地址长度字),地址长度字节数,已符合网络字节顺序的节数,已符合网络字节顺序的IP地址等。地址等。4 4域名解析函数域名解析函数上述上述gethostbyname()域名解析函数的返回结构域名解析函数的返回结构hostent为为 struct hostent char*h_name;/主机名主机名 char*h_aliases;/主机别名列表主机别名列表 short h_addrtype;/返回地址的类型返回地址的类型 short h_length;/
30、地址的长度地址的长度 char*h_addr_list;/主机主机IP地址列表地址列表(主机可能多个主机可能多个IP)#define h_addr h_addr_list0;/主机主机IP地址列表中的第一个地址地址列表中的第一个地址 ;比如,可以用下面的程序,将主机域名解析出比如,可以用下面的程序,将主机域名解析出IP地址:地址:h=gethostbyname(某域名某域名);printf(“host name:%s”,hh_name);printf(“IP address is:%s”,inet_ntoa(*(struct in_addr*)hh_addr);4 4域名解析函数域名解析函数2
31、.3 面向连接的套接字编程面向连接的套接字编程2.3.1 套接字的工作过程套接字的工作过程2.3.2 UNIX套接字编程接口的系统调用套接字编程接口的系统调用1创建套接字创建套接字SOCKET()SOCKET过程创建一个套接字并返回一个整型过程创建一个套接字并返回一个整型描述符:描述符:int socket(int proto_family,int type,int protocol);2 BIND()绑定套接字到指定的地址绑定套接字到指定的地址int bind(int sockfd,struct sockaddr*my_addr,int addrlen);3Listen()启动监听启动监听i
32、nt listen(int sockfd,int queue_size);举例:举例:listen(sockfd,10);图图2.6 监听套接字使用缓冲区接纳多个客户端的连接请求监听套接字使用缓冲区接纳多个客户端的连接请求 4ACCEPT()接受连接请求接受连接请求int accept(int sockfd,struct sockaddr*addr,int*addrlen);举例:举例:int clientfd;/定义响应套接字描述符变量定义响应套接字描述符变量int addrlen=sizeof(sockaddr);/获得套接字地址结构长度获得套接字地址结构长度struct sockaddr
33、_in cltsockaddr;/定义用于返回客户端地址的结构定义用于返回客户端地址的结构clientfd=accept(listenfd,(sockaddr*)(&cltsockaddr),&addrlen);/接受客户连接请求接受客户连接请求 5CONNECT()请求建立连接请求建立连接int connect(int sockfd,struct sockaddr*service_addr,int addrlen);举例:举例:if(connect(sockfd,(struct sockaddr*)(&serv_addr),sizeof(struct sockaddr)0)连接服务器有错时的
34、报错处理,并退出连接服务器有错时的报错处理,并退出 6 READ()和和WRITE()读读/写套接字写套接字 int read(int sockfd,void*buffer,int len);int write(int sockfd,void*buffer,int len)7 SEND()和和RECV()向套接字发送向套接字发送/接收接收int send(int sockfd,char*buf,int len,int flags);int recv(int sockfd,char*buf,int len,int flags);8CLOSE()关闭套接字关闭套接字int close(int so
35、ckfd);2.3.3 面向连接的套接字编程实例面向连接的套接字编程实例1 1实例的功能实例的功能 服务器对来访的客户计数,并向客户报告这个计服务器对来访的客户计数,并向客户报告这个计数值。数值。客户建立与服务器的一个连接并等待它的输出。客户建立与服务器的一个连接并等待它的输出。每当连接请求到达时,服务器生成一个可打印的每当连接请求到达时,服务器生成一个可打印的ASCII串信息,将它在连接上发回,然后关闭连接。串信息,将它在连接上发回,然后关闭连接。客户显示收到的信息,然后退出。客户显示收到的信息,然后退出。1 1实例的功能实例的功能 例如,对于服务器接收的第例如,对于服务器接收的第10次客户
36、次客户连接请求,该客户将收到并打印如下信息:连接请求,该客户将收到并打印如下信息:This server has been contacted 10 times.2 2实例程序的命令行参数实例程序的命令行参数 实例是实例是UNIX环境下的环境下的C程序,客户和服务程序,客户和服务器程序在编译后,均以器程序在编译后,均以命令行方式命令行方式执行。执行。服务器程序服务器程序执行时可以带执行时可以带一个命令行参数一个命令行参数,是用来表示接受客户端请求时的是用来表示接受客户端请求时的服务器监听套服务器监听套接字接字的协议的协议端口号端口号。这个参数是可选的。如果不指定端口号,这个参数是可选的。如果不
37、指定端口号,代码将使用程序内定的代码将使用程序内定的缺省缺省端口号端口号5188。2 2实例程序的命令行参数实例程序的命令行参数 客户程序客户程序执行时可以带执行时可以带两个命令行参数两个命令行参数:一个是:一个是服务器服务器所在计算机的所在计算机的主机名主机名,另一个是,另一个是服务器监听的服务器监听的协议端口号协议端口号。这两个参数都是可选的。这两个参数都是可选的。如果没有指定协议端口号,客户使用程序内定的如果没有指定协议端口号,客户使用程序内定的缺省端口缺省端口5188。如果一个参数也没有,客户使用如果一个参数也没有,客户使用缺省端口缺省端口5188和和缺省主机名缺省主机名localho
38、st,localhost是映射到客户所在计算是映射到客户所在计算机的一个别名。机的一个别名。允许客户与本地机上的服务器通信,允许客户与本地机上的服务器通信,对调试对调试是是很很有用有用的。的。3 3客户程序代码客户程序代码/*-*程序程序:client.c*目的目的:创建一个套接字,通过网络连接一个服务器,并创建一个套接字,通过网络连接一个服务器,并打印来自服务器的信息打印来自服务器的信息*语法语法:client host port *host-运行服务器的计算机的名字运行服务器的计算机的名字*port-服务器监听套接字所用协议端口号服务器监听套接字所用协议端口号*注意:两个参数都是可选的。如
39、果未指定主机名,客户注意:两个参数都是可选的。如果未指定主机名,客户*使用缺省主机名使用缺省主机名localhost;如果未指定端口号,客户;如果未指定端口号,客户*将使用将使用PROTOPORT中给定的缺省协议端口号。中给定的缺省协议端口号。*-*/3 3客户程序代码客户程序代码#include#include /*UNIX下,套接字的相关包含文件。*/#include#include#include#include#include#define PROTOPORT 5188 /*缺省协议端口号*/extern int errno;char localhost=“localhost”;/*缺
40、省主机名*/3 3客户程序代码客户程序代码int main(int argc,char*argv)struct hostent *ptrh;/*指向主机列表中一个条目的指针*/struct sockaddr_in servaddr;/*存放服务器端网络地址的结构*/int sockfd;/*客户端的套接字描述符*/int port;/*服务器端套接字协议端口号*/char*host;/*服务器主机名指针*/int n;/*读取的字符数*/char buf1000;/*缓冲区,接收服务器发来的数据*/关于关于argc、argv的说明:的说明:例:例:命令行若键入#client hp1 8080,
41、则参数个数 argc=3,参数字符串数组元素 argv0=“client”,argv1=“hp1”,argv2=“8080”,在程序中可引用。memset(char*)&servaddr,0,sizeof(servaddr);servaddr.sin_family=AF_INET;/*因特网协议族*/*检查命令行参数,若有则取端口号,否则使用内定缺省值*/if(argc2)port=atoi(argv2)/*若指定协议端口则转换成整数*/else port=PROTOPORT;/*否则,使用缺省端口号*/if(port0)/*若端口号合法值则将它装入网络地址结构*/servaddr.sin_p
42、ort=htons(u_short)port)else /*否则,打印错误信息并退出*/fprintf(stderr,”bad port number%sn”,argv2);exit(1);3 3客户程序代码客户程序代码/*检查主机参数并指定主机名*/if(argc1)host=argv1;/*如果指定了主机名参数,就使用它*/else host=localhost;/*否则,使用缺省值*/3 3客户程序代码客户程序代码/*将主机名转换成相应的IP地址并复制到servaddr 结构中*/ptrh=gethostbyname(host);/*从服务器主机名得到相应IP地址*/If(char*)p
43、trh=null)/*检查主机名的有效性,无效则退出*/fprintf(stderr,”invalid host:%sn”,host);exit(1);memcpy(&servaddr.sin_addr,ptrh-h_addr,ptrh-h_length);3 3客户程序代码客户程序代码/*创建一个套接字*/sockfd=socket(AF_INET,SOCK_STREAM,0);if(sockfd 0)fprintf(stderr,”socket creation failedn”);exit(1);/*请求连接到服务器*/if(connect(sockfd,(struct sockaddr
44、*)&servaddr,sizeof(servaddr)0)write(1,buf,n);/*1stdout设备设备(即显示器即显示器),0stdin设备 n=recv(sockfd,buf,sizeof(buf),0);/*关闭套接字*/closesocket(sockfd);/*终止客户程序*/exit(0);3 3客户程序代码客户程序代码4 4服务器实例代码服务器实例代码/*-*程序程序:server.c*目的目的:分配一个套接字,然后反复执行如下几步:*(1)等待客户的下一个连接*(2)发送一个短消息给客户*(3)关闭与客户的连接*(4)转向(1)步*命令行语法命令行语法:server
45、 port*port 服务器端监听套接字使用的协议端口号*注意注意:端口号可选。如果未指定端口号,服务器使用PROTOPORT中*指定的缺省端口号*-*/4 4服务器实例代码服务器实例代码#include#include#include#include#include#include#define PROTOPORT 5188 /*监听套接字的缺省协议端口号*/#define QLEN 6 /*监听套接字的请求队列大小*/int visits=0;/*对于客户连接的计数*/int main(argc,argv)int argc;char*argv;struct hostent *ptrh;/*
46、指向主机列表中一个条目的指针*/struct sockaddr_in servaddr;/*存放服务器网络地址的结构*/struct sockaddr_in clientaddr;/*存放客户网络地址的结构*/int listenfd;/*监听套接字描述符*/int clientfd;/*响应套接字描述符*/int port;/*协议端口号*/int alen;/*地址长度*/char buf1000;/*供服务器发送字符串所用的缓冲区*/4 4服务器实例代码服务器实例代码memset(char*)&servaddr,0,sizeof(servaddr);/*清空sockaddr结构*/ser
47、vaddr.sin_family=AF_INET;/*设置为因特网协议族*/servaddr.sin_addr.s_addr=htonl(INADDR_ANY);/*设置本地IP地址*/*检查命令行参数,若指定了,就用该端口号,否则使用缺省端口号*/if(argc 1)port=atoi(argv1);/*如果指定了端口号,就将它转换成整数*/else port=PROTOPORT;/*否则,使用缺省端口号*/4 4服务器实例代码服务器实例代码if (port 0)/*测试端口号是否合法*/servaddr.sin_port =htons(u_short)port);else /*打印错误信息
48、并退出*/fprintf(stderr,”bad portnumber%sn”,argv1);exit(1);/*创建一个用于监听的流式套接字用于监听的流式套接字 */listenfd=SOCKET(AF_INET,SOCK_STREAM,0);if (listenfd 0)fprintf(stderr,“socket creation failedn”);exit(1);4 4服务器实例代码服务器实例代码/*将本地地址绑定到监听套接字监听套接字*/if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)0)fprintf(st
49、derr,”bind failedn”);exit(1);/*开始监听,并指定监听套接字监听套接字请求队列的长度*/if (listen(listenfd,QLEN)0)fprintf(stderr,”listen filedn”);exit(1);4 4服务器实例代码服务器实例代码 while(1)/*服务器主循环-接受和处理来自客户端的连接请求*/alen=sizeof(clientaddr);/*接受客户端连接请求,并生成响应套接字响应套接字*/if(clientfd=accept(listenfd,(struct sockaddr*)&clientaddr,&alen)0)fprint
50、f(stderr,“accept failedn”);exit(1);visits+;/*累加访问的客户数*/sprintf(buf,“This server has been contacted%d time.n”,visits);send(clientfd,buf,strlen(buf),0);/*向客户端发送信息*/closesocket(clientfd);/*关闭响应套接字响应套接字*/closesocket(listenfd);/*关闭监听套接字监听套接字*/4 4服务器实例代码服务器实例代码关于阻塞的问题关于阻塞的问题 图图2.7 服务器进程因调用服务器进程因调用ACCEPT()