全套课件·《Linux/UNIX网络编程》1_第1页
全套课件·《Linux/UNIX网络编程》1_第2页
全套课件·《Linux/UNIX网络编程》1_第3页
全套课件·《Linux/UNIX网络编程》1_第4页
全套课件·《Linux/UNIX网络编程》1_第5页
已阅读5页,还剩375页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、Linux/UNIX网络编程先修课程C语言TCP/IP协议操作系统原理Linux的基本使用Linux系统基础软件开发环境最常用编辑工具:vi编译器:gcc,g+ gcc编译c程序 g+编译c+程序调试器:gdb基本的C/S服务模型 简单的客户/服务器模型CS1、数据请求2、数据响应无连接3、数据请求4、数据响应1、连接请求2、连接响应5、关闭请求6、关闭响应CS复杂的客户/服务器模型CSCCCCWEB服务器和客户端之间的数据传输过程 OSI 与 TCP/IP应用层表示层会话层传输层网络层数据链路层物理层应用层IPv4, IPv6网络介质层TCPUDPOSI模型TCP/IP模型网络编程接口通过路

2、由器连接的两个网络FTP客户TCPIP以太网驱动程序FTP服务器TCPIP令牌环驱动程序令牌环以太网驱动程序令牌环驱动程序IPFTP协议TCP协议IP协议IP协议以太网客户服务器数据进入协议栈时的封装用户数据用户数据App头部用户数据TCP头部用户数据TCP头部IP头部用户数据TCP头部IP头部以太网头部以太网尾部以太网帧401500字节FTP客户TCPIP以太网驱动程序TCP段IP分组以太网数据帧分用过程以太网帧ARPRARPIPICMPIGMPTCPUDP应用程序应用程序应用程序应用程序进入的帧根据以太网首部中的帧类型进行分用根据IP首部中的协议类型进行分用根据TCP或UDP首部中的端口号

3、进行分用UDP:用户数据报协议UDP提供无连接服务UDP缺乏可靠性支持,应用程序必须实现:确认、超时、重传、流控等UDP面向记录服务UDP数据报格式源端口目的端口长度校验和数据01531#ifdef _FAVOR_BSDstruct udphdr u_int16_t uh_sport; /* source port */ u_int16_t uh_dport; /* destination port */ u_int16_t uh_ulen; /* udp length */ u_int16_t uh_sum; /* udp checksum */;#elsestruct udphdr u_i

4、nt16_t source; u_int16_t dest; u_int16_t len; u_int16_t check;#endifTCP:传输控制协议TCP是面向连接的。TCP提供可靠性,实现了丢失重传。RTT的估算。TCP通过给所发送数据的每一个段管理一个序列号进行排序。TCP提供流量控制和拥塞控制:通告窗口、拥塞窗口。TCP的连接是全双工的。TCP协议数据段格式HLEN窗口序列号确认号源端口目的端口保留码位校验和紧急指针选项填充字节数据URGACKPSHRSTSYNFIN015317首部数据区TCP协议数据段格式(续)struct tcphdr WORD SourPort;WORD

5、DestPort;DWORD SeqNo;DWORD AckNo;BYTE HLen;BYTE Flag;WORD Window;WORD ChkSum;WORD UrgPtr;/* Put options here. */; TCP协议数据段格式(续)几个需要说明的字段HLEN:首部长度,以4字节(32位)为单位。tcp数据段首部包括固定和变长两部分;窗口:为通告窗口;URG位:如果使用紧急数据指针,则将这一位设为1ACK位:如果确认序列号有效,则设为1;PSH位:表示”推”数据,如果这一位设置成1,表示希望接收方在接收到这个数据段之后,将它立即传送给高层应用程序,而不是缓存起来。TCP协议

6、数据段格式(续)RST位:表示请求重置连接。当TCP协议接收到一个不能处理的数据段时,向对方TCP协议发送这种数据段,表示这个数据段所标识的连接出现了某种错误,请求对方TCP协议将这个连接清除。有3种情况可能导致TCP协议发送RST数据段(1)SYN数据段指定的目的端口处没有接收进程等待;(2)TCP协议想放弃一个已经存在的连接;(3)TCP接收到一个数据段,但是这个数据段所标识的连接不存在。接收到RST数据段的TCP协议立即将这条连接非正常断开,并向应用程序报告;TCP协议数据段格式(续)SYN位:请求建立连接。tcp用这种数据段向对方tcp协议请求建立连接,在这个数据段中,tcp协议将它选

7、择的初始序列号通知对方,并且与对方协议协商最大数据段的大小。FIN位:请求关闭连接。当协议收到对这个数据段的确认后,成功关闭写方向的连接,因为tcp连接是全双工的,在发送了FIN数据段之后,它仍能接收数据,直至对方也发送FIN数据段。紧急指针:如果设置了URG位,这个字段指出紧急数据相对于这个数据段的开始序列号的偏移量。tcp协议只提供一字节的紧急数据,但由于历史原因,紧急指针指向紧急数据的下一个位置。TCP三路握手客户服务器SocketConnect(阻塞)(主动打开)Socket,bind,listen(被动打开)SYN xSYN y, ack x+1ack y+1Accept返回Read

8、阻塞Connect返回TCP连接的建立TCP连接的过程:服务器必须准备好接受外来的连接。通过调用socket, bind, listen函数完成。称为被动打开。客户通过调用connect进行主动打开。这引起客户TCP发送一个SYN分节,告诉服务器客户将在连接中发送的数据的初始序列号。服务器必须确认客户的SYN,同时自己也得发送一个SYN分节。服务器以单个分节向客户发送SYN和对客户的SYN的ACK。客户必须确认服务器的SYN。TCP连接关闭客户服务器Close主动关闭被动关闭Read返回0FIN Mack N+1Closeack M+1FIN NTCP连接终止TCP一般用四个分节终止一个连接:

9、某个进程首先调用close, 这一端的TCP于是发送一个FIN分节,表示数据发送完毕。主动关闭。另一端称为被动关闭。TCP对接收的FIN分节进行确认,并以文件结束标志传递给应用程序。一段时间后,接收到文件结束标志的应用程序调用close,这也导致向对方发送一个FIN分节。接收到这个FIN分节的原发送方TCP对它进行确认。 还有一种关闭称为“半关闭”(half-close)CLOSELISTENSYN_RCVDSYN_SENTESTABLISHEDCLOSE_WAITLAST_ACKFIN_WAIT1FIN_WAIT2TIME_WAITCLOSING被动关闭主动关闭应用:close或超时接收:A

10、CK发送:应用:close发送:FIN应用:主动打开发送:SYN起始点应用:被动打开发送:接收:SYN;发送:SYN,ACK接收:RST接收:SYN发送:SYN,ACK,同时打开接收:ACK发送:应用:close发送FIN同时关闭接收:ACK发送:接收:FIN,ACK发送:ACK接收:FIN发送:ACK,接收:ACK发送:2MSL超时?接收:SYN,ACK发送:ACK接收:FIN发送:ACK接收:FIN发送:ACK,表示客户的状态转换表示服务器的状态转换socket,bind,listenLISTEN(被动打开)accept阻塞SocketConnect(阻塞)(主动打开)SYN_SENTSY

11、N_RCVDESTABLISHEDESTABLISHEDaccept返回read阻塞read返回客户构造请求WriteRead(阻塞)WriteRead阻塞Read(返回)CLOSE_WAIT(被动关闭)Read返回0CloseLAST_ACKClose(主动关闭)FIN_WAIT1FIN_WAIT2TIME_WAITCLOSED2MSLSYN J, mss=1460SYN K, ACK J+1, mss=1024ACK k+1数据(请求)数据(应答),请求ack应答ackFIN MACK M+1FIN NACK N+1TCP连接中的分组交换TCP 的TIME_WAIT状态难点:执行主动关闭的

12、那端进入这种状态。这个端点在该状态的持续时间是2MSL(最长分节生命周期)。存在TIME_WAIT状态的两个理由?网络中的几类地址物理地址:即MAC地址逻辑地址:即IP地址端口地址:应用程序端口号域名地址:取代IP地址记忆物理地址48位:24位OUI,24位由厂商分配平面地址,无结构全球唯一局部范围寻址存在于数据链路层IP地址IP地址标识着网络一个主机的位置。每个IP地址都是由32位(或128位)组成,分成两部分:网络号、主机号。全球唯一,寻址容易两种表示形式:二进制(计算机内部)、点分十进制(便于记忆)IP地址分类0NetID10110NetID1110Multicast AddressHo

13、stIDNetIDHostIDHostIDABCD8 bits8 bits8 bits8 bits0126128191192223224239基本套接字编程主要内容套接字基础套接字地址结构套接字基本函数 套接字基础网络编程接口有两个发展方向:Socket,TLI套接字是一种网络API,程序员可以用之开发网络程序。进程1进程2网络编程接口(socket)网络通信协议服务接口(TCP/IP)底层通信协议和网络介质套接字类型套接字支持多种通信协议:Unix:Unix系统内部协议INET:IP版本4INET6:IP版本6Linux支持多种套接字类型,即应用程序希望的通信服务类型SOCKET_STREA

14、M:双向可靠数据流,对应TCPSOCKET_DGRAM:双向不可靠数据报,对应UDPSOCKET_RAW:是低于传输层的低级协议或物理网络提供的套接字类型,可以访问内部网络接口。例如接收和发送ICMP报套接字地址结构(IPv4)大多数套接字函数需要一个指向套接字地址结构的参数,每个协议族都定义它自己的套接字地址结构,一般以”sockaddr_”开头,并以协议簇为后缀。(netinet/in.h)typedef uint32_t in_addr_t;typedef uint16_t in_port_t;typedef unsigned short sa_family_t;struct in_ad

15、dr in_addr_t s_addr;struct sockaddr_in uint8_t sin_len; sa_family_t sin_family; in_port_t sin_port; struct in_addr sin_addr; char sin_zero8;这些结构在不同的系统上都有所不同struct sockaddr_in serverbzero(&server,sizeof(server)server.sin_family=AF_INET; server.sin_port=htons(PORT); server.sin_addr.s_addr = htonl(INAD

16、DR_ANY).IPv6地址为128位。(netinet/in.h)套接字地址结构(IPv6)typedef uint16_t in_port_t;typedef unsigned short sa_family_t;struct in6_addr uint8_ts6_addr16;struct sockaddr_in6 uint8_t sin6_len; sa_family_t sin6_family; in_port_t sin6_port; uint32_t sin6_flowinfo; struct in6_addr sin6_addr;sin6_flowinfo成员分成三个字段:低2

17、4位是流量标号;下4位是优先级;再下4位保留IPv4与IPv6地址结构比较长度AF_INET616位端口号32位流标签128位IPv6地址sockaddr_in6 长度AF_INET16位端口号32位IP地址未用sockaddr_in 固定长度(16字节)固定长度(24字节)通用套接字地址结构由于套接字函数需接收来自不同协议的地址结构,ANSI的办法是使用通用的指针类型,即(void *).套接字函数方法是定义一个通用的套接字地址结构。struct sockaddr uint8_t sa_len; sa_family_t sa_family; char sa_data14;这就要求调用套接字函

18、数时,需将指向特定于协议的地址结构的指针类型转换成指向通用的地址结构的指针, 如: struct sockaddr_in serv bind(sockfd, (struct sockaddr *)&serv,sizeof(serv);字节排序函数为保证”大端“和”小端“字节序的机器之间能相互通信,需在发送多字节整数时,将主机字节序转换成网络字节序,或反之。高序字节低序字节MSB(最高有效位)16位值 LSB高序字节低序字节内存地址增大方向内存地址增大方向小端字节序大端字节序网络字节序字节排序函数(cont.)#include uint16_t htons(uint16_t hostshort)

19、uint32_t htonl(uint32_t hostlong) 均返回:网络字节序值uint16_t ntohs(uint16_t netshort)uint32_t ntohl(uint32_t netlong) 均返回:主机字节序值h:主机 n:网络 s:短整数 l:长整数字节操纵函数#include void bzero(void *dest, size_t nbytes);void bcopy(const void *src, void *dest, size_t nbytes);int bcmp(const void *src, void *dest, size_t nbytes

20、); /*返回0则相同,非0不相同*/ 上述三个函数源自BSDvoid *memset(void *dest, int c, size_t len);void *memcpy(void *dest, const void *src, size_t nbytes);int memcmp(const void *ptr1, const void *ptr2, size_t nbytes)上述三个函数属于ANSI C地址转换函数#include 将点分十进制数串转换成32位网络字节序二进制值。int inet_aton(const char *cp, struct in_addr *inp); 返回

21、:1-串有效,0-串有错in_addr_t inet_addr(const char *cp); 返回:若成功,返回32位二进制的网络字节序地址,若有错,则返回一个 常值INADDR_NONE(32位均为1). 过时函数inet_aton函数将cp所指的字符串(点分十进制数串,如)转换成32位的网络字节序二进制,并通过指针inp来存储。这个函数需要对字符串所指的地址进行有效性验证。但如果cp为空,函数仍然成功,但不存储任何结果。inet_addr进行相同的转换,但不进行有效性验证,也就是说,所有232种可能的二进制值对inet_addr函数都是有效的。地址转换函数(续)char *inet_n

22、toa(struct in_addr inaddr); 返回:指向点分十进制数串的指针函数inet_ntoa将32位的网络字节序二进制IPv4地址转换成相应的点分十进制数串。但由于返回值所指向的串留在静态内存中,这意味着函数是不可重入的。需要注意的是这个函数是以结构为参数的,而不是指针。上述三个地址转换函数都只能处理IPv4协议,而不能处理IPv6地址。地址转换函数(cont.)#include int inet_pton(int family, const char *strptr, void *addrptr); 返回:1-成功,0输入无效,-1:出错const char *inet_nt

23、op(int family, const void *addrptr, char *strptr, size_t len); 返回:指向结果的指针成功,NULL出错字母P和N分别代表presentation(地址的表达格式)和numeric(数值格式)。family参数可以是AF_INET,也可以是AF_INET6。长度参数len是目标的大小,如果太小无法容纳表达格式结果,则返回一个空指针。另外,目标指针调用前必须先由调用者分配空间。Tcp套接字编程TCP套接字编程(cont.)基本套接字函数socket#include int socket(int family, int type, int

24、 protocol) 返回:非负套接字(sockfd)成功;-1出错。family:协议族;type:套接字类型; protocol:一般为0,除原始套接字外。 family typeAF_INET IPv4协议SOCK_STREAM 字节流套接口AF_INET6IPv6协议SOCK_DGRAM 数据报套接口AF_LOCALunix域协议SOCK_RAW 原始套接口AF_ROUTE 路由套接口AF_KEY 密钥套接口Protocol:指明此socket请求所使用的协议,可以使用如下相关符号常数来表示。IPPROTO_TCP:表示TCP协议IPPROTO_UDP:表示UDP协议基本套接字函数bi

25、nd#include int bind(int sockfd, const struct sockaddr *addr, socklen_len len) 返回:0成功;-1出错并置errno该函数指明套接字将使用本地的哪一个协议端口进行数据传送(IP地址和端口号),注意:协议地址addr是通用地址。Len是该地址结构(第二个参数)的长度。一般而言,服务器调用此函数,而客户则很少调用它。 绑定地址时,可以指定地址和端口号,也可以指定其中之一,甚至一个也不指定。通配地址:INADDR_ANY,其值一般为0,它通知内核选择IP地址。IP地址 端口 结果通配地址 0 内核选择IP地址和端口号通配地址

26、 非0 内核选择IP地址,进程指定端口本地IP 0 进程指定IP地址,内核选择端口本地IP 非0 进程指定IP地址和端口号 若指定端口号为0,调用函数bind时,内核选择一个临时端口(在实际中,端口号都要指定);但若指定一个通配IP地址,则直到套接字已连接(TCP)或数据报已在套接字上发出(UDP),内核才选择一个本地IP地址。bind函数的用法struct sockaddr_in addr;int port = 1234;int opt = SO_REUSEADDR;setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt); bze

27、ro(&server,sizeof(server);addr.sin_family = AF_INET;addr.sin_addr.s_addr = htonl(INADDR_ANY);addr.sin_port = htons(port);if (bind(fd, (struct sockaddr *)&addr, sizeof(addr) = -1)/* 错误处理 */基本套接字函数listen#include int listen(int sockfd, int backlog) 返回:0成功;-1出错并置errno值;函数listen仅被服务器调用,它完成两件事情:函数listen将未

28、连接的套接字转化成被动套接字,指示内核应接受指向此套接字的连接请求;函数的第二个参数规定了内核为此套接字排队的最大连接个数;对于给定的监听套接字,内核要维护两个队列未完成连接队列已完成连接队列两个队列之和不超过backlog;客户服务器Connect调用在未完成队列建立条目SYN JSYN K, ack J+1ack K+1该条目从未完成队列移至已完成队列,accept阻塞Connect返回TCP三路握手和监听套接口的两个队列listen函数(续)三路握手完成两队列之和不能超过backlog已完成连接队列(ESTABLISHED状态)未完成连接队列(SYN_RCVD状态)新到达的SYN分节服务

29、器TCPacceptTCP为监听套接口维护的两个队列listen函数(续)另外几点说明:不同的实现对backlog有不同的解释,如源自Berkeley的实现将backlog增加一个模糊因子,把它乘以1.5,再作为两个队列之和;不要把backlog定义为0,因为有些实现允许1个连接排队,而有些实现不允许有连接排队;当一个客户SYN到达时,若两个队列都是满的,tcp就忽略此分节,且不发送RST。这是因为,这种情况是暂时的,客户tcp将重发SYN,期望不久的将来就能在队列中找到空闲条目。如果发送RST,将会出现?基本套接字函数connect#include int connect(int sockf

30、d, const struct sockaddr *addr, socklen_t addrlen); 返回:0成功;-1出错;函数connect激发TCP的三路握手过程;仅在成功或出错返回;错误有以下几种情况:如果客户没有收到SYN分节的响应(总共75秒,这之间需要可能需要重发若干次SYN),则返回ETIMEDOUT。如果对客户的SYN的响应是RST,则表明该服务器主机在指定的端口上没有进程在等待与之相连。函数返回错误ECONNREFUSED;如果客户发出的SYN在中间路由器上引发一个目的地不可达ICMP错误,客户上的内核保存此消息,并按第一种情况,连续发送SYN,直到规定时间,返回保存的消

31、息(即ICMP错误)作为EHOSTUNREACH或ENETUNREACH错误返回给进程。基本套接字函数accept#include int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen); 返回:非负描述字(connfd)OK;-1出错;accept函数由TCP服务器调用;从已完成连接队列头返回下一个已完成连接;如果该队列空,则进程进入睡眠状态。函数返回的套接字为已连接套接字,应与监听套接字区分开来该函数最多返回三个值:一个既可能是新套接字也可能是错误指示的整数,一个客户进程的协议地址(由cliaddr所指),以

32、及该地址的大小(这后两个参数是值结果参数);也就是说,服务器可以通过参数cliaddr来得到请求连接并获得成功的客户的地址和端口号;基本套接字函数close#include int close(int sockfd); 返回:0OK;-1出错;close函数缺省功能是将套接字做上“已关闭”标记,并立即返回到进程。这个套接字不能再为该进程所用。正常情况下,close将引发向TCP的四分节终止序列,但在终止前将发送已排队的数据;如果套接字描述符访问计数在调用close后大于0(在多个进程共享同一个套接字的情况下),则不会引发TCP终止序列(即不会发送FIN分节);基本套接字函数-shutdown#

33、include int shutdown(int sockfd, int howto); 返回:0OK;-1出错,并置相应的errno的值;该函数立即发送FIN分节(无论其访问计数是否大于0)。shutdown根据参数howto关闭指定方向的数据传输;SHUT_RD:关闭连接的读这一半,不再接收套接字中的数据且现留在接收缓冲区的数据作废;SHUT_WR :关闭连接的写这一半(半关闭),当留在套接字发送缓冲区中的数据都被发送,后跟tcp连接终止序列,不管访问计数是否大于0;此后将不能在执行对套接字的任何写操作;SHUT_RDWR:连接的读、写都关闭,这等效于调用shutdown两次,一次调用是用

34、SHUT_RD,第二次用SHUT_WR。数据数据FIN数据和FIN的确认数据数据FIN数据和FIN的确认writewriteshutdownRead返回大于0Read返回大于0Read返回0writewritecloseRead返回大于0Read返回0Read返回大于0客户服务器调用shutdown关闭一半TCP连接基本套接字函数read#include int read(int fd, char *buf, int len);返回:大于0读写字节大小;-1出错;调用函数read时,有如下几种情况:套接字接收缓冲区接收数据,返回接收到的字节数;tcp协议收到FIN数据,返回0;tcp协议收到R

35、ST数据,返回1,同时errno为ECONNRESET;进程阻塞过程中接收到信号,返回1,同时errno为EINTR。read(connfd,buff,strlen(buff);基本套接字函数write#include int write(int fd, char *buf, int len); 返回:大于0读写字节大小;-1出错;调用函数write,有如下几种情况:套接字发送缓冲区有足够空间,返回发送的字节数;tcp协议接收到RST数据,返回1,同时errno为ECONNRESET; ;进程阻塞过程中接收到信号,返回1,同时errno为EINTR。write(connfd,buff,strl

36、en(buff);数据传输函数send#include #include ssize_t send (int fd, const void *msg, size_t len, int flags); 返回:非0发送成功的数据长度;-1出错;flags 是传输控制标志,其值定义如下:0:常规操作,如同write()函数MSG_OOB,发送带外数据(TCP紧急数据)。MSG_DONTROUTE:忽略底层协议的路由设置,只能将数据发送给与发送机处在同一个网络中的机器上。数据传输函数recv#include #include ssize_t recv(int fd, void *buf ,size_t

37、 len, int flags); 返回:大于0表示成功接收的数据长度;0: 对方已关闭,-1:出错。flags是传输控制标志,其值定义如下:0:常规操作,如同read()函数;MSG_PEEK:只查看数据而不读出数据,后续读操作仍然能读出所查看的该数据;MSG_OOB:忽略常规数据,而只读带外数据;MSG_WAITALL:recv函数只有在将接收缓冲区填满后才返回。TCP套接字编程实现TCP套接字基本步骤分为服务器端和客户端两部分:服务器端创建套接字;绑定套接字;设置套接字为监听模式,进入被动接受连接状态;接受请求,建立连接读写数据终止连接TCP套接字编程(cont.)客户端步骤创建套接字与

38、远程服务器建立连接读/写数据;终止连接TCP服务器模板int main(void)int sockfd,connect_sock;if(sockfd=socket(AF_INET,SOCK_STREAM,0)=-1) perror(“create socket failed.”);exit(-1);/* bind sockfd to some address */* listen */loop if(connect_sock=accept(sockfd,NULL,NULL)=-1) perror(“Accept error.”); exit(-1); /* read and process r

39、equest */close(connect_sock); close(sockfd);TCP客户模板/* include some header files */int main(void)int sockfd;if(sockfd=socket(AF_INET,SOCK_STREAM,0)=-1)perror(“Create socket failed.”);exit(-1);/* connect to server */* send requst and receive response */close(sockfd);TCP套接字例程采用客户/服务器模式,完成下列功能:客户根据用户提供的

40、IP地址,连接相应的服务器;服务器等待客户的连接,一旦连接成功,则显示客户的IP地址,并发欢迎信息给客户;客户接收服务器发送的信息并显示;三种异常情况下面我们考虑在以下三种异常情况发生后,tcp客户服务器程序的反映;服务器主机崩溃服务器主机崩溃后重启服务器主机关机。服务器主机崩溃服务器主机崩溃时,已有的网络连接上发不出任何东西。同时假设应用程序发出数据后,然后阻塞于从套接字读取响应。由于服务器主机崩溃,因此客户tcp会持续重传数据分节,试图从服务器接收一个ACK:源自Berkeley的实现将重传12次。当客户tcp最终放弃时,返回给客户一个错误,此时错误是ETIMEDOUT,或者是因为中间路由

41、器判定服务器主机不可达,且以一个目的地不可达的ICMP消息响应,则错误是EHOSTUNREACH或ENETUNREACH。通过设置套接字选项可以更改tcp持续重传等待的超时时间。服务器主机崩溃后重启在这种情况下,如果客户在主机崩溃重启前不主动发送数据,那么客户是不会知道服务器已崩溃的。在服务器重启后,客户向服务器发送一个数据分节;由于服务器重启后丢失了以前的连接信息(尽管在服务端口上有进程监听,但连接套接字所在的端口无进程等待),因此导致服务器主机的tcp响应RST;当客户tcp收到RST,向客户返回错误,ECONNRESET如果客户对服务器的崩溃情况很关心,即使客户不主动发送数据也这样,就需

42、要其他技术支持(如套接口选项SO_KEEPALIVE或某些客户服务器心跳函数)。服务器主机关机当Linux主机关机时,由init进程给所有运行的进程发信号SIGTERM(我们的服务器程序可以捕获该信号,并在信号处理程序中正常关闭网络连接)。如果服务器程序忽略了SIGTERM信号,则init进程会等待一段固定的时间(通常是5s20s),然后给所有还在运行的程序发信号SIGKILL(该信号不能由服务器程序捕获);服务器将由信号SIGKILL终止,其终止时,所有打开的描述字被关闭,这导致向客户发送FIN分节;客户收到FIN分节后,能推断出服务器将终止服务。UDP套接字编程UDP套接字编程实现UDP套

43、接字基本步骤分为服务器端和客户端两部分:服务器端建立UDP套接字;绑定套接字到特定地址;等待并接收客户端信息;处理客户端请求;发送信息回客户端;关闭套接字;UDP套接字编程(Cont.)客户端步骤建立UDP套接字;发送信息给服务器;接收来自服务器的信息;关闭套接字UDP套接字编程(Cont.)UDP数据传输函数sendto#include #include ssize_t sendto(int sockfd, const void *msg, size_t len, int flags, const struct sockaddr *to, int tolen); 返回:大于0成功发送数据长度

44、;-1出错;UDP套接字使用无连接协议,因此必须使用sendto函数,指明目的地址;flags是传输控制标志,其值定义如下:0:常规操作,如同write()函数;MSG_OOB:发送带外数据;MSG_DONTROUTE:忽略底层路由协议,直接发送。UDP数据传输函数recvfrom#include #include ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *from, int *fromlen); 返回:大于0成功接收数据长度;-1出错;UDP套接字使用无连接协议,因此必须使用

45、recvfrom函数,指明源地址;flags是传输控制标志,其值定义如下:0:常规操作,如同read()函数;MSG_PEEK:只察看数据而不读出数据;MSG_OOB:忽略常规数据,而只读取带外数据;from 和 fromlen 是“值结果”参数。内核长度套接口地址结构用户进程结果值 当函数被调用时,结构大小是一值,当函数返回时,结构大小又是一个结果,这种参数类型叫值-结果参数。参数是一个整型指针。bind(int sockfd, const struct sockaddr *addr, socklen_len len)recvfrom(int sockfd, void *buf, size_

46、t len, int flags, struct sockaddr *from, int *fromlen)值-结果参数说明UDP服务器模板#include #include #inlcude int main(void)int socketfd;if (sockfd = socket(AF_INET, SOCK_DGRAM, 0) = -1) perror(“Create socket failed.”);exit(1);/* Bind socket to address */ loop /* receive and process data from client */ /* send r

47、esuts to client */close(sockfd); UDP客户模板#include #include #inlcude int main(void)int sockfd;if (sockfd = socket(AF_INET, SOCK_DGRAM, 0) = -1) perror(“Create socket failed.”);exit(1);/* send data to the server */* receive data from the server */close(sockfd);UDP套接字例程本例程分为服务器和客户两部分,主要完成如下功能:服务器循环接收客户发

48、来的消息,并显示客户IP地址和相应消息;如果服务器收到”quit“,则退出循环,并关闭套接字;客户向服务器发送消息,并接收服务器响应,显示该消息,并关闭套接字。并发服务器目录服务器分类技术进程与线程多进程服务器多线程服务器并发服务器服务器分类按连接类型分类面向连接的服务器(如tcp)面向无连接的服务器(如udp)按处理方式分类迭代服务器并发服务器迭代服务器 vs. 并发服务器绑定地址监听连接接收连接处理连接断开连接接收请求处理请求返回响应绑定地址监听连接接收连接创建子进程关闭连接套接字处理连接关闭连接套接字终止子进程关闭监听套接字服务器主进程服务器子进程TCP迭代服务器TCP并发服务器“进程”

49、基本概念进程定义了一个计算的基本单元,可以认为是一个程序的一次运行。它是一个动态实体,是独立的任务。它拥有独立的地址空间、执行堆栈、文件描述符等。每个进程拥有独立的地址空间,进程间正常情况下,互不影响,一个进程的崩溃不会造成其他进程的崩溃。当进程间共享某一资源时,需注意两个问题:同步问题和通信问题。创建进程#include #include pid_t fork(void)返回:父进程中返回子进程的进程ID, 子进程返回0, -1出错fork后,子进程和父进程继续执行fork()函数后的指令。子进程是父进程的副本。子进程拥有父进程的数据空间、堆栈的副本。但父、子进程并不共享这些存储空间部分。如

50、果代码段是只读的,则父子进程共享代码段。如果父子进程同时对同一文件描述字操作,而又没有任何形式的同步,则会出现混乱的状况;父进程中调用fork之前打开的所有描述字在函数fork返回之后子进程会得到一个副本。fork后,父子进程均需要将自己不使用的描述字关闭。创建进程(cont.)#include #include pid_t vfork(void);该系统调用基本上与fork相同,在BSD3.0中开始出现,主要为了解决fork昂贵的开销。两者的基本区别在于当使用vfork()创建新进程时,父进程将被暂时阻塞,而子进程则可以借用父进程的地址空间,直到子进程退出,至此父进程才继续执行。终止进程进程

51、的终止存在两个可能:父进程先于子进程终止(init进程领养)子进程先于主进程终止对于后者,系统内核为子进程保留一定的状态信息:进程ID、终止状态、CPU时间等;当父进程调用wait或waitpid函数时,获取这些信息;(什么叫“僵尸进程”?)当子进程正常或异常终止时,系统内核向其父进程发送SIGCHLD信号;缺省情况下,父进程忽略该信号,或者提供一个该信号发生时即被调用的函数。终止进程(续)#include void exit(int status);本函数终止调用进程。关闭所有子进程打开的描述符,向父进程发送SIGCHLD信号,并返回状态。获取子进程终止信息#include #include

52、 pid_t wait(int *stat_loc); 返回:终止子进程的ID成功;-1出错;stat_loc存储子进程的终止状态(一个整数);如果没有终止的子进程,但是有一个或多个正在执行的子进程,则该函数将堵塞,直到有一个子进程终止或者wait被信号中断时,wait返回。当调用该系统调用时,如果有一个子进程已经终止,则该系统调用立即返回,并释放子进程所有资源。获取子进程终止信息使用wait()函数可能会出现一个问题SIGCHLD服务器父进程服务器子进程服务器子进程服务器子进程客户FINFINFINSIGCHLDSIGCHLD由于linux信号不排队,在SIGCHLD信号同时到来后,信号处理

53、程序中调用了wait函数,其只执行一次,这样将留下2个僵尸进程。可以使用waitpid函数解决这个问题。获取子进程终止信息pid_t waitpid(pid_t pid, int *stat_loc, int options); 返回:终止子进程的ID成功;-1出错;stat_loc存储子进程的终止状态;当pid=-1,option=0时,该函数等同于wait,否则由参数pid和option共同决定函数行为,其中pid参数意义如下:-1:要求知道任何一个子进程的返回状态(等待第一个终止的子进程);0:要求知道进程号为pid的子进程的状态;-1:要求知道进程号为pid的绝对值的子进程的终止状态

54、Options最常用的选项是WNOHANG,它通知内核在没有已终止进程时不要堵塞。获取子进程终止信息(cont.)调用wait或waitpid函数时,正常情况下,可能会有以下几种情况:阻塞(如果其所有子进程都还在运行);获得子进程的终止状态并立即返回(如果一个子进程已终止,正等待父进程存取其终止状态);出错立即返回(如果它没有任何子进程)多进程并发服务器状态图服务器客户connect()函数listenfd客户/服务器状态图(调用accept函数时)连接请求多进程并发服务器状态图(cont.)服务器客户connect()函数listenfd客户/服务器状态图(调用accept函数后)connf

55、d连接建立多进程并发服务器状态图(cont.)服务器(父进程)客户connect()函数listenfd客户/服务器状态图(调用fork函数后)connfd连接建立服务器(子进程)listenfdconnfdfork()函数多进程并发服务器状态图(cont.)服务器(父进程)客户connect()函数listenfd客户/服务器状态图(父进程关闭连接套接字,子进程关闭监听套接字)连接建立服务器(子进程)connfd多进程并发服务器实例该实例包括服务器程序和客户程序,具体功能如下:服务器等待接收客户的连接请求,一旦连接成功则显示客户地址,接着接收客户端的名称并显示;然后接收来自该客户的字符串,每

56、当收到一个字符串时,显示该字符串,并将字符串按照恺撒密码的加密方式(K=3)进行加密,再将加密后的字符发回客户端;之后,继续等待接收该客户的信息,直到客户关闭连接。要求服务器具有同时处理多个客户请求的能力。客户首先与相应的服务器建立连接;接着接收用户输入的客户端名称,并将其发送给服务器;然后继续接收用户输入的字符串,再将字符串发送给服务器,同时接收服务器发回的加密后的字符串并显示。之后,继续等待用户输入字符串,直到用户输入Ctrl+D,客户关闭连接并退出。 多进程服务器的问题 虽然多进程并发服务器模式很多年来都使用得很好,但使用fork生成子进程存在一些问题。首先,fork占用大量的资源,内存

57、映像要从父进程拷贝到子进程,所有描述符要在子进程中复制等。虽然当前采用写时拷贝(copy-on-write)技术,将真正的拷贝推迟到子进程有写操作时,但fork仍然需要占用大量资源。其次,fork子进程后,需要用进程间通信(IPC)在父子进程间传递信息。由于子进程从一开始就有父进程数据空间及所有描述符的拷贝,所以fork之前的信息容易传递,但是从子进程返回信息给父进程就需要做很多工作。 “线程”基本概念线程是进程内的独立执行实体和调度单元,又称为“轻量级”进程(lightwight process);创建线程比进程快10100倍。一个进程内的所有线程共享相同的内存空间、全局变量等信息(这种机制

58、又带来了同步问题)。而且它们还共享以下信息: 共享信息 私有信息进程指令 线程ID大多数数据 寄存器集合(包括程序计数器和栈指针)打开的文件描述字栈(用于存放局部变量)信号处理程序和信号处置error当前工作目录信号掩码用户ID和组ID优先级线程调用函数(1)#include int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*func)(void *), void *arg); 返回:成功时为0;出错时为正的Exxx值当一个程序开始运行时,系统会创建一个初始线程或主线程的单个线程。额外线程由上述函数创建

59、;新线程由线程id标识:tid,新线程的属性attr包括:优先级、初始栈大小、是否应该是守护线程等等。线程的执行函数和调用参数分别是:func和arg;由于线程的执行函数的参数和返回值类型均为void *,因此可传递和返回指向任何类型的指针;常见的返回错误值:EAGAIN:超过了系统线程数目的限制。ENOMEN:没有足够的内存产生新的线程。EINVAL:无效的属性attr值。线程函数调用(2)#inlcude int pthread_join(pthread_t tid, void *status); 返回:成功时为0;出错时为正的Exxx值,不设置error该函数类似与waitpid函数,但

60、必须指定等待线程的ID,该函数不能等待任意一个线程结束(如wait);被等待线程必须是当前进程的成员,并且不是分离的线程和守护线程。pthread_t pthread_self(void); 返回:调用线程的线程id;线程函数调用(3)#include int pthread_detach(pthread_t tid) 返回:成功时为0;出错时为正Exxx值;线程或者是可汇合的(joinable)(默认),或者是脱离的(detached)。当可汇合的线程终止时,其线程id和退出状态将保留,直到另外一个线程调用pthread_join。脱离的线程则像守护进程,当它终止时,释放所有资源,我们不能等

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论