Presentation is loading. Please wait.

Presentation is loading. Please wait.

第2章 套接字网络编程基础 2.1 UNIX套接字网络编程接口的 产生与发展 2.2 套接字编程的基本概念 2.3 面向连接的套接字编程

Similar presentations


Presentation on theme: "第2章 套接字网络编程基础 2.1 UNIX套接字网络编程接口的 产生与发展 2.2 套接字编程的基本概念 2.3 面向连接的套接字编程"— Presentation transcript:

1 第2章 套接字网络编程基础 2.1 UNIX套接字网络编程接口的 产生与发展 2.2 套接字编程的基本概念 2.3 面向连接的套接字编程
第2章 套接字网络编程基础 2.1 UNIX套接字网络编程接口的 产生与发展 2.2 套接字编程的基本概念 2.3 面向连接的套接字编程 2.4 无连接的套接字编程 2.5 原始套接字 1

2 从应用程序实现的角度,应用程序如何方便地使用协议栈软件进行通信呢?
2.1 UNIX套接字网络编程接口的产生与发展 问题的提出 (P24) 从应用程序实现的角度,应用程序如何方便地使用协议栈软件进行通信呢? 如果能在应用程序与协议栈软件之间提供一个软件接口,就可以方便客户机与服务器软件的编程。UNIX系统的开发者提出了套接字应用程序编程接口。 套接字应用程序编程接口是网络应用程序通过网络协议栈进行通信时所使用的接口,即应用程序与协议栈软件之间的接口,简称套接字编程接口(Socket API)。

3 问题的提出 P24 套接字编程接口定义了应用程序与协议栈软件进行交互时可以使用的一组操作,决定了应用程序使用协议栈的方式、应用程序所能实现的功能、以及开发具有这些功能的程序的方法。 具体地说,套接字编程接口给出了应用程序能够调用的一组函数,每个函数完成一个与协议栈交互的基本操作。

4 2.1.2 套接字编程接口起源于UNIX系统 (P25) 2.1 UNIX套接字网络编程接口的产生与发展
加州大学伯克利(Berkley)分校开发并推广了一个包括TCP/IP互联协议的UNIX,称为BSD UNIX(Berkeley Software Distribution UNIX)操作系统,套接字编程接口是这个操作系统的一部分。 后来的许多操作系统并没有另外搞一套其它的编程接口,而是选择了对于套接字编程接口的支持。包括各种UNIX的派生版,后来出现的Windows,及各种Linux版本。

5 2.1.2 套接字编程接口起源于UNIX系统 (P25) 2.1 UNIX套接字网络编程接口的产生与发展
套接字规范规定了一系列有关的C函数,为在UNIX环境使用TCP/IP进行网络通信提供了一套应用程序编程接口,得以实现并广泛流传。 套接字编程接口广泛应用在各种网络编程中,成为事实上的工业标准。 由于这个套接字规范最早是由Berkeley大学开发的,一般将它称为Berkeley Sockets规范。

6 2.1.3 套接字编程接口的继承和发展 (P25) 2) Linux系统 2.1 UNIX套接字网络编程接口的产生与发展
1)Windows系统 微软公司以UNIX操作系统的Berkeley Sockets规范为范例,定义了Windows Socktes规范,全面继承了套接字网络编程接口。详细内容将在第三章介绍。 2) Linux系统 Linux操作系统中的套接字网络编程接口几乎与UNIX操作系统的套接字网络编程接口一样。

7 要想实现套接字编程接口,可以采用两种实现方式:
2.1 UNIX套接字网络编程接口的产生与发展 套接字编程接口的两种实现方式 P25 要想实现套接字编程接口,可以采用两种实现方式: 1. 一种是在操作系统的内核中增加相应的软件来实现,套接字函数是操作系统内核的一部分。 2. 另一种是通过开发操作系统之外的函数库来实现。 具有与UNIX套接字相同的函数名和参数。实现了程序的可移植性,程序源代码不必改动即可移植到另一个操作系统。只是使用时要链接库函数。

8 2.1.5 套接字通信与UNIX操作系统的输入/输出的关系
UNIX操作系统对文件和所有其它的输入/输出设备采用一种统一的的操作模式,就是“打开-读-写-关闭”(open - read - write - close)的I/O模式。 当TCP/IP协议被集成到UNIX内核中的时候,相当于在UNIX系统中引入了一种新型的I/O操作,就是应用程序通过网络协议栈来交换数据。

9 2.1.5 套接字通信与UNIX操作系统的输入/输出的关系
在UNIX系统的实现中,套接字是完全与其他I/O集成在一起的。操作系统和应用程序都将套接字编程接口也看作一种I/O机制。 这体现在三个方面: 1)操作的过程类似。使用套接字也像使用I/O一样“打开--读写--关闭”模式。 2)操作方法类似。操作系统为文件、设备、进程通信、网络通信提供单独的一组描述符,套接字通信同样使用描述符方法。 3)过程名也可以相同。例如read和write。

10 2.1.5 套接字通信与UNIX操作系统的输入/输出的关系
但是,用户进程与网络协议的交互作用实际要比用户进程与传统的I/O设备相互作用要复杂得多。首先,进行网络操作的两个进程是在两台不同的计算机,如何连接;其次,要建立一种通用机制来支持多种网络协议。 还有,使用套接字的应用程序必须说明许多细节。仅仅提供open、read、write、close四个过程远远不够。为避免单个套接字函数参数过多,套接字编程接口的设计者还定义了其它多个函数。

11 2.2 套接字编程的基本概念 2.2.1 什么是套接字(SOCKET) (P27) 图2.1 电气插座与电话插座的作用
2.2 套接字编程的基本概念 什么是套接字(SOCKET) (P27) 图2.1 电气插座与电话插座的作用 套接口是对网络中不同主机上应用进程之间进行双向通信的端点的抽象,一个套接口就是网络上进程通信的一端,提供了应用层进程利用网络协议栈交换数据的机制。

12 什么是套接字 我们应当从多个层面来理解套接字这个概念。 1)从套接字所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议栈进行通信的接口,是应用程序与网络协议栈进行交互的接口。 图2.2 应用进程、套接口、网络协议栈及操作系统的关系

13 什么是套接字 2)从实现的角度来讲,非常复杂。套接字是一个复杂的软件机构,包含了一定的数据结构,包含许多选项,由操作系统内核管理。 3)从使用的角度来讲,非常简单。调用相应的过程后,生成的套接字就是套接字描述符,用一个整数来代表。对于套接字的操作形成了一种网络应用程序的编程接口(API)。 这里把这一套操作套接字的编程接口函数称作套接字编程接口,套接字是它的操作对象。 总之,套接字是网络通信的基石。

14 套接字的特点 (P28) 1.通信域 套接字存在于通信域中,通信域是为了处理一般的进程通过套接字通信而引入的一种抽象概念,套接字通常只和同一域中的套接字交换数据。 套接字实际是通过网络协议栈来通信,通信双方要使用相同的通信协议。 在Internet通信域中,所有计算机都使用Internet协议族(即TCP/IP协议族)来通信。

15 套接字的特点 2.套接字具有三种类型 P29 每一个正被使用的套接字都有它确定的类型,只有相同类型的套接字才能相互通信。 (1)数据报套接字(Datagram SOCKET) 数据报套接字提供无连接的不保证可靠的独立的数据报传输服务。在Internet通信域中,数据报套接字使用UDP数据报协议形成的进程间通路,具有UDP协议为上层所提供的服务的所有特点。

16 套接字的特点 图2.3 在Internet通信域中,数据报套接字基于UDP协议

17 套接字的特点 (2) 流式套接字(Stream SOCKET) 流式套接字提供双向的、有序的、无重复的、无记录边界的可靠的数据流传输服务。在Internet通信域中,流式套接字使用TCP协议形成的进程间通路,具有TCP协议为上层所提供的服务的所有特点,在使用流式套接字传输数据之前,必须在数据的发送端和接收端之间建立连接,如图2.4所示。

18 图2.4 在Internet通信域中,流式套接字基于TCP协议
套接字的特点 图2.4 在Internet通信域中,流式套接字基于TCP协议

19 (3) 原始式套接字(RAW SOCKET) 2.2.2 套接字的特点
套接字的特点 (3) 原始式套接字(RAW SOCKET) 原始式套接字允许对较低层次的协议,如IP、ICMP直接访问,用于检验新的协议的实现。

20 套接字的特点 3.套接字由应用层的通信进程创建,并为其服务 就是说,每一个套接字都有一个相关的应用进程,操作该套接字的代码是该进程的组成部分。

21 套接字的特点 4.使用确定的IP地址和传输层端口号 往往在生成套接字的描述符后,要将套接字与计算机上的特定的IP地址和传输层端口号相关联,这个过程称为绑定。 一个套接字要使用一个确定的三元组网络地址信息,才能使它在网络中唯一地被标识。

22 2.2 套接字编程的基本概念 2.2.3 套接字的应用场合 (P30)
2.2 套接字编程的基本概念 套接字的应用场合 (P30) (1)不管是采用对等模式或者客户机/服务器模式,通信双方的应用程序都需要开发。 (2)双方所交换数据的结构和交换数据的顺序有特定的要求,不符合现在成熟的应用层协议,甚至需要自己去开发应用层协议,自己设计最适合的数据结构和信息交换规程。 在这种情况下,套接字很有用,因为套接字直接与传输层连接,提供了网络应用进程之间交换数据的方法,程序员编程有很大的自由度。

23 2.2 套接字编程的基本概念 2.2.4 套接字使用的数据类型和相关的问题 P30 1.三种表示套接字地址的结构
2.2 套接字编程的基本概念 套接字使用的数据类型和相关的问题 P30 1.三种表示套接字地址的结构 在套接字编程接口中,专门定义了三种结构的数据类型,用来存储协议相关的网络地址,在套接字编程接口的函数调用中要用到它们。

24 unsigned short sa_family; // 地址家族 char sa_data[14]; // 14字节协议地址 }
套接字使用的数据类型和相关的问题 (1)sockaddr结构,针对各种通信域的套接字,存储它们的地址信息。 struct sockaddr { unsigned short sa_family; // 地址家族 char sa_data[14]; // 14字节协议地址 }

25 short int sin_family; // 协议簇 unsigned short int sin_port; // 端口号
套接字使用的数据类型和相关的问题 (2)sockaddr_in结构,专门针对Internet通信域,存储套接字相关的网络地址信息,例如IP地址,传输层端口号等信息。 struct sockaddr_in { short int sin_family; // 协议簇 unsigned short int sin_port; // 端口号 struct in_addr sin_addr; // IP 地址 unsigned char sin_zero[8]; // 全为0 }

26 unsigned long s_addrl; }
套接字使用的数据类型和相关的问题 (3)in_addr结构,专门用来存储 IP地址。 struct in_addr { unsigned long s_addrl; }

27 struct sockaddr_in myad; memset(&myad,0,sizeof(struct sockaddr_in));
套接字使用的数据类型和相关的问题 (4) 这些数据结构的一般用法: 第1步,定义一个sockaddr_in的结构实例,并将它清零。 比如: struct sockaddr_in myad; memset(&myad,0,sizeof(struct sockaddr_in));

28 myad.sin_family=AF_INET; myad.sin_port=htons(8080);
套接字使用的数据类型和相关的问题 第2步,为这个结构赋值,比如: myad.sin_family=AF_INET; myad.sin_port=htons(8080); myad.sin_addr.s_addr=htonl(INADDR-ANY); 第3步,在函数调用中使用时,将这个结构强制转换为sockaddr类型。如: accept(listenfd,(sockaddr*)(&myad),&addrlen);

29 2.本机字节顺序和网络字节顺序 P31 2.2.4 套接字使用的数据类型和相关的问题
套接字使用的数据类型和相关的问题 2.本机字节顺序和网络字节顺序 P31 不同的计算机中存放多字节的顺序可能不同,有的先低后高,有的先高后低。在具体计算机中的多字节数据的存储顺序,称为本机字节顺序。 多字节数据在网络协议报头中的存储顺序,称为网络字节顺序。

30 1.网络字节顺序格式 在网络传输过程中,IP地址被保存为32位二进制数。TCP/IP协议规定,在低位存储地址中保存数据的高位字节,这种存储顺序格式被称为网络字节顺序。数据按照32位二进制数为一组进行传输,因为采用网络字节顺序,所以数据的传输顺序是由高位至低位进行的。 存储地址 2000 高位字节1 2001 高位字节2 2002 低位字节1 2003 低位字节2 2004 .

31 2.主机字节顺序格式 不同的主机在对IP地址进行存储时使用的格式也不同。有些操作系统的IP地址存储顺序与网络字节顺序格式相同,而Intel x86系列主机的主机字节顺序格式则与网络字节顺序格式正好相反。 存储地址 2000 低位字节2 2001 低位字节1 2002 高位字节2 2003 高位字节1 2004 .

32 套接字使用的数据类型和相关的问题 网络应用程序要在不同的计算机中运行,本机字节顺序是不同的,但是,网络字节顺序是一定的。 所以,应用程序在编程的时候,在把IP地址和端口号装入套接字的时候,应当把它们从本机字节顺序转换为网络字节顺序;相反,在本机输出时,应将它们从网络字节顺序转换为本机字节顺序。

33 字节序转换的方法

34 套接字使用的数据类型和相关的问题 套接字编程接口特为解决这个问题设置了四个函数: htons():短整数本机顺序转换为网络顺序,用于端口号。 htonl():长整数本机顺序转换为网络顺序,用于IP地址。 ntohs():短整数网络顺序转换为本机顺序,用于端口号。 ntohl():长整数网络顺序转化为本机顺序,用于IP地址。 这四个函数将被转换的数值作为函数的参数,函数返回值是转换后的结果。

35 3.点分十进制的IP地址的转换 P31 2.2.4 套接字使用的数据类型和相关的问题
套接字使用的数据类型和相关的问题 3.点分十进制的IP地址的转换 P31 在因特网中,IP地址常常用点分十进制的表示方法,但在套接字中,IP地址是无符号的长整型数,是网络字节顺序的地址。 套接字编程接口设置了两个函数,专门用于两种形式的IP地址的转换。

36 unsigned long inet_addr( const char* cp)
套接字使用的数据类型和相关的问题 1)inet_addr函数 点分十进制形式---->网络字节顺序 unsigned long inet_addr( const char* cp) 入口参数cp:点分十进制形式的IP地址。 返回值: 网络字节顺序的IP地址,是无符号的长整数。

37 网络字节顺序---->点分十进制形式 char* inet_ntoa(struct in_addr in)
套接字使用的数据类型和相关的问题 2)inet_ntoa函数 网络字节顺序---->点分十进制形式 char* inet_ntoa(struct in_addr in) 入口参数in:包含长整型IP地址的 in_addr 结构变量。 返回值: 指向点分十进制IP地址的字符串的指针。

38 4.域名服务 P32 2.2.4 套接字使用的数据类型和相关的问题 通常,我们使用域名来标识站点,可以将文字型的主机域名直接转换成IP地址:
套接字使用的数据类型和相关的问题 4.域名服务 P32 通常,我们使用域名来标识站点,可以将文字型的主机域名直接转换成IP地址: struct hostent* gethostbyname( const char* name); 入口参数:是站点的主机域名字符串, 返回值:是指向hostent 结构的指针, hostent结构包含主机名,主机别名数组,返回地址的类型(一般是AF-INET),地址长度的字节数,已符合网络字节顺序的主机网络地址等。

39 2.3 面向连接的套接字编程 2.3.1 可靠的传输控制协议 (P32)
2.3 面向连接的套接字编程 可靠的传输控制协议 (P32) 传输控制协议(TCP)是TCP/IP协议簇中主要的传输层协议,负责为应用层提供可靠的传输服务。 TCP建立在网络层的IP之上,为应用层进程提供一个面向连接的、端到端的、完全可靠的(无差错、无丢失、无重复和无失序)全双工的流传输服务,允许网络中的两个应用程序建立一个虚拟连接,并在任何一个方向上发送数据,把数据当作一个双向字节流进行交换,然后终止连接。每一TCP连接可靠地建立,从容地终止,在终止发生之前的所有数据都会被可靠地传递。

40 2.3 面向连接的套接字编程 套接字的工作过程 P33

41 TCP网络编程流程 1. 创建网络套接字函数 socket( ) 2. 绑定一个地址端口对 bind( )
3. 监听本地端口 listen ( ) 4. 接受一个网络请求 accept( ) 5. 连接目标网络服务器 connect( ) 6. 写入数据函数 write( ) 7. 读取数据函数 read( ) 8. 关闭套接字函数 close( )

42 TCP网络编程流程 TCP网络编程有两种模式: 1.服务器端的程序设计模式 2.客户端的程序设计模式
服务器模式创建一个服务进程,等待客户端用户的连接,接收到用户的连接请求后,根据用户的请求进行处理; 客户端模式则根据目的服务器的IP地址和端口进行连接,向服务器发送请求并对服务器的响应进行数据处理。

43 1. 服务器端的程序设计模式 流程主要分为: 套接字初始化------socket( ) 套接字与端口的绑定------bind( )
设置服务器的侦听连接------listen( ) 接受客户端连接------accept( ) 发送数据------write( ) 接收数据------read( )并进行数据处理 处理完毕的套接字关闭------close( )

44 2. 客户端的程序设计模式 客户端模式分为: 套接字初始化------socket( ) 连接服务器------connect( )
发送数据------write( ) 接收数据------read( )并进行数据处理 最后的套接字关闭------close( )

45 3. 客户端与服务器的交互过程 客户端与服务器在连接、读写数据、关闭过程中有交互过程。

46 套接字的工作过程 UNIX套接字编程接口的系统调用 (P33) 1.socket( )------创建套接字 系统调用socket( )函数建立一个协议族为Protofamily 、协议类型为type、协议编号为protocol的套接字文件描述符。 socket( )函数在socket.h文件中定义 int socket( int Protofamily, int Type, int Protocol);  使用时注意把socket.h包含进来 #include <sys/socket.h> #include <sys/types.h>

47 UNIX套接字编程接口的系统调用 int socket( int Protofamily, int Type, int Protocol);  socket( )函数用来获得文件描述符sockfd。如果函数调用成功,会返回一个表示这个套接字的文件描述符(file descriptor),失败的时候返回–1。 使用举例: sockfd = SOCKET(AF_INET, SOCK_STREAM, 0); Linux系统中,socket( )的定义在 /usr/include/sys/socket.h int socket (int __domain, int __type, int __protocol)

48 UNIX套接字编程接口的系统调用 2. bind( ) 绑定套接字到指定的地址 服务器端在建立套接字文件描述符成功后,需要对套接字进行IP地址和端口的绑定,才能进行数据的接收和发送操作。 bind( )函数将长度为addlen的struct sockadd类型的结构体变量my_addr与sockfd绑定在一起,将sockfd绑定到某IP和端口上。如果是客户端使用connect()函数则没有绑定的必要。绑定的函数原型如下: int bind( int sockfd, struct sockaddr* my_addr, int addrlen);  使用时注意把socket.h包含进来 #include <sys/socket.h> #include <sys/types.h>

49 UNIX套接字编程接口的系统调用 int bind( int sockfd, struct sockaddr* my_addr, int addrlen);  Linux系统中, bind ( )的定义在 /usr/include/sys/socket.h int bind (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len)

50 UNIX套接字编程接口的系统调用 3.listen( ) 启动监听 函数listen( )用来初始化服务器可连接队列,服务器处理客户端连接请求的时候是顺序处理的,同一时间仅能处理一个客户端连接。当多个客户端的连接请求同时到来的时候,服务器并不是同时处理,而是将不能处理的客户端连接请求放到等待队列中,这个队列的长度由listen( )函数来定义。 listen( )函数的原型如下: int listen( int sockfd, int queuesize); 其中的queuesize表示等待队列的长度。 注意把socket.h包含进来 #include <sys/socket.h> #include <sys/types.h>

51 UNIX套接字编程接口的系统调用 int listen( int sockfd, int queuesize); 举例: if (listen(sockfd, qlenth) < 0) { 出错 } Linux系统中, listen ( )的定义在 /usr/include/sys/socket.h int listen (int __fd, int __n)

52 UNIX套接字编程接口的系统调用 举例:listen(sockfd, 10); 图2.6 监听套接字使用缓冲区接纳多个客户端的连接请求

53 UNIX套接字编程接口的系统调用 4.accept ( )------接收连接请求 当一个客户端的连接请求到达服务器主机侦听的端口时,此时客户端的连接会在队列中等待,直到服务器接收请求。 函数accept( )成功执行后,返回一个新的套接字文件描述符来表示客户端的连接,客户端连接信息通过这个新描述符获得。 int accept(int sockfd, struct sockaddr* addr,int* addrlen); int clientfd; //定义响应套接字描述符变量 int addrlen=sizeof(sockaddr);//获得套接字地址结构长度。 struct sockaddr_in cltsockaddr; //定义用于返回客户端地址的结构。

54 UNIX套接字编程接口的系统调用 int accept(int sockfd, struct sockaddr* addr, int* addrlen); 举例: clientfd=accept(sockfd,(sockaddr*)(&cltsockaddr), &addrlen); //接收客户连接请求 Linux系统中, accept( )的定义在 /usr/include/sys/socket.h int accept (int __fd, __SOCKADDR_ARG __addr, socklen_t *__restrict __addr_len);

55 UNIX套接字编程接口的系统调用 5.connect( ) 请求建立连接 客户端在建立套接字之后,不需要进行地址绑定就可以直接连接服务器。 int connect( int sockfd, struct sockaddr* service_addr, int addrlen); 使用时注意包含socket.h #include <sys/types.h> #include <sys/socket.h> 举例: if (connect(sockfd, (struct sockaddr*) (&serv_addr), sizeof(struct sockaddr))<0) { 报错,并退出 }

56 UNIX套接字编程接口的系统调用 int connect( int sockfd, struct sockaddr* service_addr, int addrlen); Linux系统中, connect( )的定义在 /usr/include/sys/socket.h int connect (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len);

57 UNIX套接字编程接口的系统调用 6.read( )和 write( ) 读/写套接字 当服务器端在接收到一个客户端的连接后,可以通过套接字描述符进行数据的写入操作。对套接字进行写入的形式和过程与普通文件的操作方式一致,内核会根据文件描述符的值来查找所对应的属性,当为套接字的时候,会调用相对应的内核函数。 int read( int sockfd, void* buffer, int len); int write( int sockfd, void* buffer, int len); 

58 UNIX套接字编程接口的系统调用 7. send( ) 和recv( ) 向套接字发送和从套接字接收 int send( int sockfd,char* buf,int len,int flags ); 在Linux中: send (int __fd, __const void *__buf, size_t __n, int __flags) int recv( int sockfd,char* buf,int len,int flags ); recv (int __fd, void *__buf, size_t __n, int __flags)

59 UNIX套接字编程接口的系统调用 8.close( ) 关闭套接字 可以使用close( )函数关闭socket连接,函数的作用是关闭已经打开的socket连接,内核会释放相关的资源,关闭套接字之后就不能再使用这个套接字文件描述符进行读写操作了。 int close( int sockfd );

60 面向连接的套接字编程实例 P34 1.实例的功能 服务器对来访的客户计数,并向客户报告这个计数值。 客户建立与服务器的一个连接并等待它的输出。 每当连接请求到达时,服务器生成一个可打印的ASCII串信息,将它在连接上发回,然后关闭连接。 客户显示收到的信息,然后退出。

61 This server has been contacted 10 times.
面向连接的套接字编程实例 例如,对于服务器接收的第10次客户连接请求,该客户将收到并打印如下信息: This server has been contacted 10 times.

62 面向连接的套接字编程实例 2.实例程序的命令行参数 实例是UNIX环境下的C程序,客户和服务器程序在编译后,均以命令行的方式执行。 服务器程序执行时可以带一个命令行参数,是用来接受请求的监听套接字的协议端口号。这个参数是可选的。如果不指定端口号,代码将使用程序内定的缺省端口号5188。

63 面向连接的套接字编程实例 客户程序执行时可以带两个命令行参数:一个是服务器所在计算机的主机名,另一个是服务器监听的协议端口号。 这两个参数都是可选的。 如果没有指定协议端口号,客户使用程序内定的缺省值5188。 如果一个参数也没有,客户使用缺省端口和主机名localhost,localhost是映射到客户所运行的计算机的一个别名。允许客户与本地机上的服务器通信,对调试是很有用的。

64 3.客户程序代码 (P35) 面向连接的套接字编程实例------客户程序代码
/* * 程序: client.c * 目的: 创建一个套接字,通过网络连接一个服务器,并打印来自服务器的信息。 * 语法:client [ host [ port ] ] * host - 运行服务器的计算机的名字 * port - 服务器监听套接字所用协议端口号 * 注意:两个参数都是可选的。如果未指定主机名,客户使用localhost;如果未指定端口号,客户将使用PROTOPORT中给定的缺省协议端口号。 * */

65 面向连接的套接字编程实例------客户程序代码
#include <sys/types.h> #include <sys/socket.h> /* UNIX下,套接字的相关包含文件。*/ #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #include <stdio.h> #include <string.h> #define PROTOPORT /*默认协议端口号*/ extern int errno; /*声明errorno*/ char localhost = “localhost”; /*默认主机名*/

66 面向连接的套接字编程实例------客户程序代码
main(int argc,char *argv[]) { /* 指向主机列表中一个条目的指针 */ struct hostent *ptrh; /* 存放服务器端网络地址的结构 */ struct sockaddr_in servaddr; /* 客户端的套接字描述符 */ int sockfd; /* 服务器端套接字协议端口号*/ int port; char* host; /* 服务器主机名指针 */ int n; /* 读取的字符数 */ /* 缓冲区,接收服务器发来的数据 */ char buf[1000] ;

67 面向连接的套接字编程实例------客户程序代码
/* 清空sockaddr结构 */ memset((char*)& servaddr,0,sizeof(servaddr)); /* 设置为因特网协议族 */ servaddr.sin_family = AF_INET; /* 检查命令行参数,如果有,就抽取端口号。否则使用内定的缺省值*/ if (argc>2){ port = atoi(argv[2]); /* 如果指定了协议端口,就转换成整数 */ } else { port = PROTOPORT; /* 否则,使用缺省端口号 */

68 面向连接的套接字编程实例------客户程序代码
if (port>0) /* 如果端口号是合法的数值,就将它装入网络地址结构 */ servaddr.sin_port = htons((u_short)port); else{ /* 否则,打印错误信息并退出*/ fprintf(stderr,”bad port number %s\n”,argv[2]); exit(1); } /* 检查主机参数并指定主机名 */ if(argc>1){ host = argv[1]; /* 如果指定了主机名参数,就使用它 */ }else{ host = localhost; /* 否则,使用默认值 */}

69 面向连接的套接字编程实例------客户程序代码
/* 将主机名转换成相应的IP地址并复制到servaddr 结构中 */ /* 从服务器主机名得到相应的IP地址 */ ptrh = gethostbyname( host ); /* 检查主机名的有效性,无效则退出 */ if ( (char *)ptrh == null ) { fprintf( stderr, ”invalid host: %s\n”, host ); exit(1); } memcpy(&servaddr.sin_addr, ptrh->h_addr, ptrh->h_length );

70 面向连接的套接字编程实例------客户程序代码
/* 创建一个套接字*/ sockfd = SOCKET(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { fprintf(stderr, ”socket creation failed\n” ); exit(1); } /* 请求连接到服务器 */ if (connect( sockfd, (struct sockaddr *)& servaddr, sizeof(servaddr)) < 0) { /* 连接请求被拒绝,报错并退出 */ fprintf( stderr,”connect failed\n” ); exit(1);}

71 面向连接的套接字编程实例------客户程序代码
/* 从套接字反复读数据,并输出到用户屏幕上 */ n = recv(sockfd , buf, sizeof( buf ), 0 ); while ( n > 0) { write(1,buf, n); /*1是标准输出*/ n = recv( sockfd , buf, sizeof( buf ), 0 );} /* 关闭套接字*/ closesocket( sockfd ); /* 终止客户程序*/ exit(0);}

72 面向连接的套接字编程实例------服务器程序代码
4.服务器实例代码 (P37) /* * 程序:server.c * 目的: 分配一个套接字,然后反复执行如下几步: * (1) 等待客户的下一个连接 * (2) 发送一个短消息给客户 * (3) 关闭与客户的连接 * (4) 转向(1)步 * 命令行语法: server [ port ] * port – 服务器端监听套接字使用的协议端口号 * 注意: 端口号可选。如果未指定端口号,服务器使用PROTOPORT中指定的默认端口号 * */

73 面向连接的套接字编程实例------服务器程序代码
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <stdio.h> #include <string.h> #define PROTOPORT /* 监听套接字的缺省协议端口号 */ #define QLEN /* 监听套接字的请求队列大小 */ int visits = 0; /* 对于客户连接的计数*/

74 面向连接的套接字编程实例------服务器程序代码
main(int argc,char* argv[]) { struct hostent *ptrh; /* 指向主机列表中一个条目的指针 */ struct sockaddr_in servaddr; /* 存放服务器网络地址的结构 */ struct sockaddr_in clientaddr; /* 存放客户网络地址的结构 */ int listenfd; /* 监听套接字描述符 */ int clientfd; /* 响应套接字描述符 */ int port; /* 协议端口号 */ int alen; /* 地址长度 */ char buf[1000]; /* 供服务器发送字符串所用的缓冲区 */

75 面向连接的套接字编程实例------服务器程序代码
/* 清空sockaddr结构 */ memset( (char*)& servaddr, 0, sizeof(servaddr) ); /* 设置为因特网协议族 */ servaddr.sin_family = AF_INET; /* 设置本地IP地址 */ servaddr.sin_addr.s_addr = INADDR_ANY; /* 检查命令行参数 */ if (argc > 1){ port = atoi(argv[1]); // 若指定了端口号,将它转换成整数 }else { port = PROTOPORT; // 否则,使用缺省端口号 }

76 面向连接的套接字编程实例------服务器程序代码
if (port > 0) /* 测试端口号是否合法 */ servaddr.sin_port=htons( (u_short)port ); else{ /* 打印错误信息并退出 */ fprintf( stderr, ”bad port number %s\n”, argv[1] ); exit(1); } /* 创建一个用于监听的流式套接字 */ listenfd = SOCKET(AF_INET,SOCK_STREAM,0); if (listenfd <0) { fprintf( stderr, “socket creation failed\n” );

77 面向连接的套接字编程实例------服务器程序代码
/* 将本地地址绑定到监听套接字*/ if ( bind( listenfd, (struct sockaddr *)& servaddr, sizeof(servaddr)) < 0) { fprintf(stderr, ”bind failed\n” ); exit(1);} /* 开始监听,并指定监听套接字请求队列的长度 */ if (listen(listenfd, QLEN) < 0) { fprintf(stderr, ”listen filed\n” );

78 面向连接的套接字编程实例------服务器程序代码
/* 服务器主循环—接受和处理来自客户端的连接请求 */ while(1) { /* 接受客户端连接请求,并生成响应套接字 */ alen = sizeof(clientaddr); if((clientfd = accept( listenfd, (struct sockaddr *)& clientaddr, &alen)) < 0 ) { fprintf( stderr, “accept failed\n”); exit(1); } visits++; /* 累加访问的客户数 */ sprintf( buf, “this server has been contacted %d time \n”, visits ); send(clientfd, buf, strlen(buf), 0 ); /* 向客户端发送信息 */ closesocket( clientfd ); /* 关闭响应套接字 */}

79 操作系统中的阻塞状态 正在执行的进程由于发生某事件而暂时无法继续执行时,便放弃处理机而处于暂停状态,亦即进程的执行受到阻塞,把这种暂停状态称为阻塞状态。致使进程阻塞的典型事件有:请求I/O,申请内存空间等。 通常将这种处于阻塞状态的进程也排成一个队列。有的系统则根据阻塞原因的不同而把处于阻塞状态的进程排成多个队列。

80 图2.6 服务器进程因调用ACCEPT()而被阻塞

81 2.3.4 进程的阻塞问题和对策 2.3.4 进程的阻塞问题和对策 1.什么是阻塞 (P40)
进程的阻塞问题和对策 1.什么是阻塞 (P40) 阻塞是指一个进程执行了一个函数或者系统调用,该函数由于某种原因不能立即完成,因而不能返回调用它的进程,导致进程受控于这个函数而处于等待的状态,进程的这种状态称为阻塞。 在程序中用了一条让用户在键盘上输入字符串的语句,当程序执行到这条语句时就会停下来,等待用户输入字符串并按下回车键,如果用户没有输入,程序就会一直等下去。这时程序的状态就是阻塞。

82 2.3.4 进程的阻塞问题和对策 图2.7 RECV()函数的两种执行方式

83 2.3.4 进程的阻塞问题和对策 2.能引起阻塞的套接字调用 在Berkeley套接字网络编程接口的模型中,套接字的默认行为是阻塞的,具体地说,在一定情况下,有多个操作套接字的系统调用会引起进程阻塞。 (1)ACCEPT() (2)READ()、RECV()和READFORM() (3)WRITE()、SEND()和SENDTO() (4)CONNECT() (5)SELECT() (6)CLOSESOCKET()

84 采用阻塞工作模式的单进程服务器是不能很好地同时为多个客户服务的。图2.9是一个例子。
2.3.4 进程的阻塞问题和对策 3.阻塞工作模式带来的问题 采用阻塞工作模式的单进程服务器是不能很好地同时为多个客户服务的。图2.9是一个例子。 图2.8 采用阻塞工作模式的服务器不能很好地为多个客户服务

85 2.3.4 进程的阻塞问题和对策 4.一种解决方案 利用UNIX操作系统的FORK()系统调用,编制多进程并发执行的服务器程序。可以创建子进程。对于每一个客户端,用一个专门的进程为它服务,通过进程的并发执行,来实现对多个客户的并发服务。基本的编程框架如下。

86 2.3.4 进程的阻塞问题和对策 父进程代码 If ((pid = FORK()) == 0) { ……. 子进程代码 } else if (pid<0) { 报错信息

87 2.3.4 进程的阻塞问题和对策 举例: #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <arpa/inet.h> void main(int argc, char** argv) { int listenfd,clientfd,pid; struct sockaddr_in ssockaddr, csockaddr; char buffer[1024]; int addrlen,n;

88 2.3.4 进程的阻塞问题和对策 /* 创建监听套接字 */ listenfd = socket(AF_INET,SOCK_STREAM,0); if (listenfd < 0) { fprintf(stderr, "socket error!\n"); exit(1); }

89 2.3.4 进程的阻塞问题和对策 /* 为监听套接字绑定网络地址 */ memset(&ssockaddr,0,sizeof(struct sockaddr_in)); ssockaddr.sin_family = AF_INET; ssockaddr.sin_addr.s_addr = htonl(INADDR_ANY); ssockaddr.sin_port = htons(8080); if (bind(listenfd,&ssockaddr,sizeof(struct sockaddr_in)) < 0) { fprintf(stderr, "bind error!\n"); exit(2); }

90 2.3.4 进程的阻塞问题和对策 /* 启动套接字的监听 */ listen(listenfd,5); addrlen = sizeof(sockaddr); /* 服务器进入循环,接受并处理来自不同客户端的连接请求 */ while (1) { clientfd = accept(listenfd,(sockaddr*)(&csockaddr),&addrlen); /* accept调用返回时,表明有客户端请求连接,创建子进程处理连接*/ If ((pid = FORK()) == 0) {

91 2.3.4 进程的阻塞问题和对策 /* 显示客户端的网络地址 */ printf("Client Addr: %s%d\n",inet_ntoa(csockaddr.sin_addr), ntohs(csockaddr.sin_port)); /* 读取客户端发送来的数据,在将它们返回到客户端 */ while ((n = read(clientfd,buffer,1024)) > 0) { buffer[n] = 0; printf("Client Send: %s",buffer); write( clientfd, buffer, n); } if (n < 0) { fprintf( stderr, "read error!\n"); exit(3);

92 2.3.4 进程的阻塞问题和对策 /* 通信完毕,关闭与这个客户连接的套接字 */ printf("clent %s closed!\n", inet_ntoa(csockaddr.sin_addr)); close(clientfd); exit(1); } else if (pid < 0) { printf("fork failed!\n"); } close(listenfd); /* 关闭监听套接字 */

93 2.4 无连接的套接字编程 P43 使用数据报套接字,基于传输层的UDP,不需要建立和释放连接,每个数据包独立传输,但是每个数据包都必须包含发送方和接收方完整的网络地址。

94 2.4.1 高效的用户数据报协议 P43 2.4 无连接的套接字编程
2.4 无连接的套接字编程 高效的用户数据报协议 P43 传输层的用户数据报协议(User Datagram Protocol,UDP)建立在网络层的IP之上,为应用层进程提供无连接的数据报传输服务,这是一种尽力传送的无连接的不保证可靠性的传输服务,是一种保护消息边界的数据传输。 传输前没有建立连接的过程。客户机向服务器发送数据,不管服务器是否准备好接收;服务器收到了客户机的数据,也不会确认。 UDP特别简单,没有差错控制,省去了建立连接的的开销,可以高效传输。但是不保证传输的可靠性。

95 使用数据报套接字开发网络应用程序,既可以采用客户/服务器模式,也可以采用对等模式。
2.4 无连接的套接字编程 无连接的套接字编程的两种模式 P43 使用数据报套接字开发网络应用程序,既可以采用客户/服务器模式,也可以采用对等模式。

96 无连接的套接字编程的两种模式 1.对等模式 图2.9 对等模式的数据报套接字的编程模型

97 无连接的套接字编程的两种模式 2.客户/服务器模式 图2.10 C/S模式的数据报套接字的编程模型

98 两个专用的系统调用 2.4.2 无连接的套接字编程的两种模式 1.发送数据报SENDTO()
无连接的套接字编程的两种模式 两个专用的系统调用 1.发送数据报SENDTO() int SENDTO( int sockfd, const void* msg, int len, unsigned int flags, struct sockaddr* to, int tolen); 2.接收数据报 RECVFROM() int RECVFROM( int sockfd, void* buf, int len, unsigned int flags, struct sockaddr* from, int* fromlen )

99 2.4.3 数据报套接字的对等模式编程实例 P45 聊天程序 #include <sys/types.h>
#include <unistd.h> #include <error.h> #include <sys/socket.h> #include <arpa/inet.h> #include <stdio.h> /* 中断处理过程 */ void int_proc( int signo) { }

100 数据报套接字的对等模式编程实例 void main(int argc, char** argv) { struct sockaddr_in daddr, saddr, cmpaddr; int sockfd; int timer = 3; char buffer[1024]; int addrlen, n; /* 判断用户输入的命令行是否正确,如果有错,提示用法 */ if (argc != 5) { printf("用法:%s 目的IP 目的端口 源IP 源端口\n", argv[0]); exit(0); }

101 数据报套接字的对等模式编程实例 /* 设定中断处理函数,并设置时间限制 */ signal( SIGALRM, int_proc); alarm(timer); /* 建立数据报套接字 */ sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) { fprintf(stderr, "socket error!\n"); exit(1); }

102 数据报套接字的对等模式编程实例 /* 为结构变量daddr的各个字段赋值 */ addrlen = sizeof(struct sockaddr_in); memset(&daddr, 0, addrlen); daddr.sin_family = AF_INET; daddr.sin_port = htons(atoi(argv[2])); if (inet_pton(AF_INET, argv[1], &daddr.sin_addr ) <= 0) { fprintf(stderr, "Invaild dest IP!\n"); exit(0);

103 数据报套接字的对等模式编程实例 /* 绑定地址 */ if (bind(sockfd, &saddr, addrlen) < 0 ) { fprintf(stderr, "bind local addr error!\n"); exit(1);} /* 从标准输入获得字符串,并发送给目标地址 */ if (fgets(buffer, 1024, stdin) == NULL ) exit(0); if ( sendto( sockfd, buffer, strlen(buffer), 0, &daddr, addrlen)) { fprintf(stderr, "sendto error!\n"); exit(2);}

104 数据报套接字的对等模式编程实例 while (1) { /* 接收信息并显示 */ n = recvfrom( sockfd, buffer, 1024, 0, &cmpaddr, &daddrlen ); if (n < 0) { /* 根据errno中的数值是否为常量EWOULDBLOCK,来区别超时错和一般性错。 */ if ( errno == EWOULDBLOCK) fprintf(stderr, "recvfrom timeout error!\n"); else { fprintf(stderr, "recvfrom error!\n"); exit(3); }

105 数据报套接字的对等模式编程实例 } else { /* 比较数据报来源地址与保存的目标地址是否一致 */ /* 不同则返回非0,结束此循环 */ if (memcmp(cmpaddr, daddr,addrlen)) continue; buffer[n] = 0; printf( "Received: %s", buffer); }

106 数据报套接字的对等模式编程实例 /* 从标准输入获得字符串,并发送给目标地址 */ if (fgets(buffer, 1024, stdin) == NULL ) exit(0); if ( sendto( sockfd, buffer, strlen(buffer), 0, &daddr, addrlen)) { fprintf(stderr, "sendto error!\n"); exit(3);} } /* 关闭套接字 */ close(sockfd);

107 2.5 原始套接字(P47) 利用“原始套接字”(Raw Socket),可以访问基层的网络协议,如IP(网际协议)、ICMP(Internet控制消息协议)、IGMP(Internet组管理协议)等。很多网络实用工具,如Tracerout、Ping、网络嗅探器(sniffer)程序等,就是利用原始套接字实现的。

108 2.5 原始套接字 原始套接字的创建 P47 格式一: Int sockRaw = socket(AF_INET,SOCK_RAW, protocol) 格式二: SOCKET sockRaw = WSASocket (AF_INET, SOCK_RAW, protocol, NULL, 0, 0);

109 2.5 原始套接字 原始套接字的使用 P48 1.根据需要设置套接字的选项 2.调用connect和bind函数来绑定对方和本地地址 3.发送数据包 4.接收数据包

110 2.5.3 原始套接字应用实例 P49 2.5 原始套接字 我们经常用ping程序来判断一个特定的主机是否可以通过网络访问到。
2.5 原始套接字 原始套接字应用实例 P49 我们经常用ping程序来判断一个特定的主机是否可以通过网络访问到。 Internet控制报文协议是一种差错报告机制,可以用来向目的主机请求或报告各种网络信息。这些信息包括回送应答,目的地不可达,源站抑制,回送请求,掩码请求和掩码应答等。 Ping程序用的是回送请求与应答报文。

111 原始套接字应用实例 Ping程序采取下列步骤。 (1)创建类型为SOCK_RAW的一个套接字,同时设定协议类型为IPPROTO_ICMP。 (2)创建并初始化ICMP头。 (3)调用sendto或WSASendto,将ICMP请求发给远程主机。 (4)调用recvfrom或WSARecvfrom,以接收任何ICMP响应。


Download ppt "第2章 套接字网络编程基础 2.1 UNIX套接字网络编程接口的 产生与发展 2.2 套接字编程的基本概念 2.3 面向连接的套接字编程"

Similar presentations


Ads by Google