《第二部分基本套接口编程-linux网络编程.ppt》由会员分享,可在线阅读,更多相关《第二部分基本套接口编程-linux网络编程.ppt(105页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、基本套接字编程 主要内容主要内容n套接字基础n套接字地址结构n套接字基本函数n简单TCP套接字编程n简单套接字编程 套接字基础套接字基础n网络编程接口有两个发展方向:Socket,TLI进程1进程2网络通信协议服务接口(TCP/IP)底层通信协议和网络介质网络编程接口(socket)应用层通过传输层进行数据通信时,TCP和UDP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要通过同一个TCP协议端口传输数据。为了区别不同的应用程序进程和连接,计算机操作系统为应用程序与TCPIP协议交互提供了称为套接字(Socket)的接口。processTCP with
2、buffers,variablessocket由应用程序由应用程序开发者控制开发者控制由操作系统控制由操作系统控制host orserverprocessTCP withbuffers,variablessocketcontrolled byapplicationdevelopercontrolled byoperatingsystemhost orserverinternet1.套接字是一个主机本地应用程序所创建的套接字是一个主机本地应用程序所创建的,为操作系为操作系统所控制的接口统所控制的接口(“门门”).2.进程通过调用套接字接口来实现相互之间的通信,而进程通过调用套接字接口来实现相互之
3、间的通信,而套接字接口又利用下层的网络通信协议功能和系统调用实套接字接口又利用下层的网络通信协议功能和系统调用实现实际的通信工作现实际的通信工作nSocket原意是“插座”。区分不同应用程序间的网络通信和连接,主要有3个参数:通信的目的IP地址、使用的传输层协议(TCP或UDP)和使用的端口号。通过将这3个参数结合起来,与一个“插座”Socket绑定,应用层就可以和传输层通过套接字接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。n套接字是一种网络API,程序员可以用之开发网络程序。套接字接口本意在于提供一种进程间通信的方法,使得在相同或不同的主机上的进程能以相同的规范进
4、行双向信息传送。套接字套接字(套接口套接口)A首先调用数据发送函数,将要发送的信息写入其Socket中,Socket中的内容通过A的网络管理软件将这段信息由A的网络接口卡发送的B,B的网络接口卡接收到信息并传送给B的网络接口软件,接口软件再将信息送给B的Socket,然后程序B就可以使用这段信息。套接字套接字(套接口套接口)n套接口还可以看成是两个网络应用程序进行通信时,各自通信连接中的一个端点,双方首先必须各自创建一个端点,否则是没有办法建立联系并相互通信的。n其本质是通信过程中所要使用的一些缓冲区及其一些相关的数据结构。套接字类型套接字类型n套接字存在于特定的通信协议(地址族)中,只有类属
5、于同一地址族的套接字才能建立通信,套接字支持多种通信协议:nAF_LOCAL:Unix域协议nAF_INET:IP版本4nAF_INET6:IP版本6nLinux支持多种套接字类型,套接字类型:是指创建套接字的应用程序所希望的通信服务类型。nSOCKET_STREAM:双向可靠数据流,流式套接字,对应TCPnSOCKET_DGRAM:双向不可靠数据报,数据包套接字,对应UDPnSOCKET_RAW:是低于传输层的低级协议或物理网络直接访问,可以访问内部网络接口。原始套接字,例如接收和发送ICMP报。套接字地址结构(IPv4)n大多数套接字函数需要一个指向套接字地址结构的参数,每个协议族都定义它
6、自己的套接字地址结构,一般以”sockaddr_”开头,并以协议簇为后缀。(netinet/in.h)typedef uint32_t in_addr_t;typedef uint16_t in_port_t;typedef unsigned short sa_family_t;struct in_addr in_addr_t s_addr;/32位IP地址,网络字节序;这些结构在不同的系统上都有所不同这些结构在不同的系统上都有所不同nstruct sockaddr_inn unsigned short int sin_len;/*IPv4地址长度*/n short int sin_famil
7、y;/*地址类型*/n unsigned short int sin_port;/*存储端口号*/n struct in_addr sin_addr;/*存储IP地址*/n unsigned char sin_zero8;/*空字节*/n;n在编程中大多数是使用sockaddr_in这个结构来设置/获取地址信息。sin_familysin_family指代协议族,在指代协议族,在指代协议族,在指代协议族,在TCPTCP套接字编程中只能是套接字编程中只能是套接字编程中只能是套接字编程中只能是AF_INET;AF_INET;sin_portsin_port存储端口号(使用网络字节顺序),数据类型是
8、一个存储端口号(使用网络字节顺序),数据类型是一个存储端口号(使用网络字节顺序),数据类型是一个存储端口号(使用网络字节顺序),数据类型是一个1616位的无符号整数类型;位的无符号整数类型;位的无符号整数类型;位的无符号整数类型;sin_addrsin_addr存储存储IPIP地址,地址,IPIP地址使用地址使用in_addrin_addr这个数据结构:这个数据结构:structstruct in_addrin_addr unsigned long unsigned long s_addrs_addr;这个数据结构是由于历史原因保留下来,主要用作与以前的格式兼容。这里这个数据结构是由于历史原因
9、保留下来,主要用作与以前的格式兼容。这里的的s_addrs_addr按照网络字节顺序存储按照网络字节顺序存储IPIP地址。地址。sin_zerosin_zero是为了让是为了让是为了让是为了让sockaddrsockaddr与与与与sockaddr_insockaddr_in两个数据结构保持两个数据结构保持两个数据结构保持两个数据结构保持大小相同而保留的空字节。大小相同而保留的空字节。大小相同而保留的空字节。大小相同而保留的空字节。struct sockaddr_in serverbzero(&server,sizeof(server)/置0server.sin_family=AF_INET;
10、server.sin_port=htons(PORT);server.sin_addr.s_addr =htonl(INADDR_ANY).设置地址信息的实例(设置地址信息的实例(设置地址信息的实例(设置地址信息的实例(IPv4IPv4)structstruct sockaddr_insockaddr_in mysockmysock;/*/*设置设置设置设置sockaddr_insockaddr_in的结构体变量的结构体变量的结构体变量的结构体变量mysockmysock*/*/mysock.sin_familymysock.sin_family=AF_INET;=AF_INET;/*/*地址
11、族地址族地址族地址族*/mysock.sin_portmysock.sin_port=htons(3490);=htons(3490);/*/*short,NBOshort,NBO*/*/mysock.sin_addr.s_addrmysock.sin_addr.s_addr=inet_addr(“192.1=inet_addr(“192.168.1.221”);68.1.221”);/*/*设置地址为设置地址为设置地址为设置地址为192.168.1.221*/192.168.1.221*/bzero(&(mysock.sin_zero),8);bzero(&(mysock.sin_zero)
12、,8);/*/*设置设置设置设置sin_zerosin_zero为为为为8 8位保留字节位保留字节位保留字节位保留字节*/注意:注意:注意:注意:如果如果如果如果mysock.sin_addr.s_addrmysock.sin_addr.s_addr=INADDR_ANY,=INADDR_ANY,则不指定则不指定则不指定则不指定IPIP地址(用于地址(用于地址(用于地址(用于serverserver程序)。程序)。程序)。程序)。nIPv6地址为128位。(netinet/in.h)套接字地址结构(IPv6)typedef uint16_t in_port_t;typedef unsigned
13、 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成员分成三个字段:n低24位是流量标号;n下4位是优先级;n再下4位保留sa_family_tsa_family_t 地址族类型,在地址族类型,在地址族类型,在地址族类型,在IPV6IPV6中是中是中是中是AF
14、_INET6AF_INET6sin6_portsin6_port,端口号,以网络字节顺序存储,端口号,以网络字节顺序存储,端口号,以网络字节顺序存储,端口号,以网络字节顺序存储sin6_addrsin6_addr:6_addr6_addr成员,存储网络字节序的成员,存储网络字节序的成员,存储网络字节序的成员,存储网络字节序的IPv6IPv6地址地址地址地址sin6_flowinfosin6_flowinfo:优先级和流量标识,网络自己顺序:优先级和流量标识,网络自己顺序:优先级和流量标识,网络自己顺序:优先级和流量标识,网络自己顺序IPv4与IPv6地址结构比较长度AF_INET616位端口号
15、32位流标签128位IPv6地址sockaddr_in6 长度AF_INET16位端口号32位IP地址未用sockaddr_in 固定长度(固定长度(16字节)字节)固定长度(固定长度(24字节)字节)n套接口地址结构仅在给定主机上使用,虽然有些成员用在不同主机间通信,但结构本身并不参与通信。通用套接字地址结构通用套接字地址结构n由于套接字函数需接收来自不同协议的地址结构,ANSI的办法是使用通用的指针类型,即(void*).套接字函数方法是定义一个通用的套接字地址结构。struct sockaddr uint8_t sa_len;sa_family_t sa_family;char sa_d
16、ata14;这就要求调用套接字函数时,需将指向特定于协议的地址结构的指针类型转换成指向通用的地址结构的指针,如:struct sockaddr_in serv bind(sockfd,(struct sockaddr*)&serv,sizeof(serv);字节排序函数n网络中存在多种类型的机器,这些不同类型的机器表示数据的字节顺序是不同的。n以16进制数308A,在内存地址0 x1000的存储为例30308A8A30308A8A0 x10000 x10000 x10000 x10000 x10010 x10010 x10010 x1001小端字节序小端字节序小端字节序小端字节序大端字节序大端
17、字节序大端字节序大端字节序网络协议中的数据采用统一的网络字节顺序,因为只有采用统一的字节网络协议中的数据采用统一的网络字节顺序,因为只有采用统一的字节网络协议中的数据采用统一的网络字节顺序,因为只有采用统一的字节网络协议中的数据采用统一的网络字节顺序,因为只有采用统一的字节顺序,才能在不同类型的硬件设备之间正确的发送和接收数据。顺序,才能在不同类型的硬件设备之间正确的发送和接收数据。顺序,才能在不同类型的硬件设备之间正确的发送和接收数据。顺序,才能在不同类型的硬件设备之间正确的发送和接收数据。广域网广域网广域网广域网规定的网络字节顺序采用大端字节顺序方式。规定的网络字节顺序采用大端字节顺序方式
18、。规定的网络字节顺序采用大端字节顺序方式。规定的网络字节顺序采用大端字节顺序方式。字节排序函数(cont.)n系统提供4个函数来进行字节顺序转换:#include“netinet/in.h”unsigned short int htons(unsigned short int hostshort);unsigned long int htonl(unsigned long int hostlong);unsigned short int ntons(unsigned short int netshort);unsigned long int ntonl(unsigned long int ne
19、tlong);h:主机 n:网络 s:短整数 l:长整数其中。前两个函数将主机字节顺序转换成网络字节顺序;后两个函数将其中。前两个函数将主机字节顺序转换成网络字节顺序;后两个函数将网络字节顺序转换成主机字节顺序。网络字节顺序转换成主机字节顺序。在使用这些函数时,我们不关心主机或网络顺序的真实值到底是大端还在使用这些函数时,我们不关心主机或网络顺序的真实值到底是大端还是小端,只需要调用适当的函数来对给定值(函数的整型参数)进是小端,只需要调用适当的函数来对给定值(函数的整型参数)进行主机字节顺序和网络字节顺序的转换,它们的返回值就是经过转行主机字节顺序和网络字节顺序的转换,它们的返回值就是经过转
20、换以后的结果。换以后的结果。字节操纵函数n系统提供两组函数来处理多字节数据,一组函数是以b(byte)开头,和BSD系统兼容的函数;另一组是以mem开头,ANSI C所提供的函数。n#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);/*返回0则相同,非0不相同*/上述三个函数源自BSDvoid*memset(void*dest,int c,size_t le
21、n);void*memcpy(void*dest,const void*src,size_t nbytes);int memcmp(const void*ptr1,const void*ptr2,size_t nbytes)上述三个函数属于ANSI Cbzerobzero函数将目标中指定数目的字节置为函数将目标中指定数目的字节置为函数将目标中指定数目的字节置为函数将目标中指定数目的字节置为0 0,这个函数经常用来,这个函数经常用来,这个函数经常用来,这个函数经常用来把套接字地址结构初始化为把套接字地址结构初始化为把套接字地址结构初始化为把套接字地址结构初始化为0 0,如:,如:,如:,如:bz
22、ero(&servaddr,sizeof(servaddrbzero(&servaddr,sizeof(servaddr););bcopybcopy将指定数目的字节从源将指定数目的字节从源将指定数目的字节从源将指定数目的字节从源srcsrc移动到目标移动到目标移动到目标移动到目标destdest指定的内存区指定的内存区指定的内存区指定的内存区域;域;域;域;bcmpbcmp比较任意两个内存区域,即比较任意两个内存区域,即比较任意两个内存区域,即比较任意两个内存区域,即s1s1指定的内存区域与指定的内存区域与指定的内存区域与指定的内存区域与s2s2指定指定指定指定的内存区域的前的内存区域的前的内
23、存区域的前的内存区域的前n n个字节,若相同则返回值为个字节,若相同则返回值为个字节,若相同则返回值为个字节,若相同则返回值为0 0,否则返回值为,否则返回值为,否则返回值为,否则返回值为非非非非0 0;memsetmemset函数将参数函数将参数函数将参数函数将参数s s指定的内存区域的前指定的内存区域的前指定的内存区域的前指定的内存区域的前n n个字节设置为参个字节设置为参个字节设置为参个字节设置为参数数数数c c的内容;的内容;的内容;的内容;memcpymemcpy函数等同于函数等同于函数等同于函数等同于bcopybcopy,差别是,差别是,差别是,差别是bcopybcopy可以处理源
24、可以处理源可以处理源可以处理源srcsrc和和和和目标目标目标目标destdest相重叠的情况,而相重叠的情况,而相重叠的情况,而相重叠的情况,而memcpymemcpy则对这种情况没有定义。则对这种情况没有定义。则对这种情况没有定义。则对这种情况没有定义。#include#include main()char*s1=Hello,Programmers!;char*s2=Hello,programmers!;int r;r=memcmp(s1,s2,strlen(s1);if(!r)printf(s1 and s2 are identical);else if(r0)printf(s1 les
25、s than s2);else printf(s1 greater than s2);Memset函数#include#include int main()char buffer=This is a test of the memset function;printf(Before:%sn,buffer);memset(buffer,*,4);printf(After:%sn,buffer);Before:This is a test of the memset function After:*is a test of the memset function 地址转换函数n地址转换函数负责在A
26、SCII字符串和网络字节顺序的二进制值之间进行地址转换。ninet_aton,inet_addr和inet_ntoa函数#include int inet_aton(const char*strptr,struct in_addr*addrptr);in_addr_t inet_addr(const char*strptr);inet_aton函数将函数将strptr所指向的字符串转换成所指向的字符串转换成32位的网络字节位的网络字节序二进制值,并存储在指针序二进制值,并存储在指针addrptr指向的指向的in_addr结构体中,结构体中,若成功,返回若成功,返回1。inet_addr函数,其
27、转换结果作为返回值返回函数,其转换结果作为返回值返回32位二进制网络位二进制网络字节序地址,若转换错,则返回字节序地址,若转换错,则返回INADDR_NONE。地址转换函数ninet_addr进行相同的转换,但不进行有效性验证,当IP地址是255.255.255.255时,会认为这是个无效的IP地址,但对于目前大部分的路由器上,这个IP都是有效的。ninet_aton函数将tcp所指的字符串(点分十进制数串,如192.168.0.1)转换成32位的网络字节序二进制,并通过指针addrptr来存储。这个函数需要对字符串所指的地址进行有效性验证。但如果strptr为空,函数仍然成功,但不存储任何结
28、果。地址转换函数(续)地址转换函数(续)char*inet_ntoa(struct in_addr inaddr);返回:指向点分十进制数串的指针n函数inet_ntoa将32位的网络字节序二进制IPv4地址转换成相应的点分十进制数串。但由于返回值所指向的串留在静态内存中,这意味着函数是不可重入的。n需要注意的是这个函数是以结构为参数的,而不是指针。n上述三个地址转换函数都只能处理IPv4协议,而不能处理IPv6地址。不可重入不可重入的函数的话,那么不同任务调用这个函数时可能修改其他任务调用这个函数的数据,从而导致不可预料的后果地址转换函数地址转换函数(cont.)n#include int
29、inet_pton(int family,const char*strptr,void*addrptr);返回:1-成功,0输入无效,-1:出错const char*inet_ntop(int family,const void*addrptr,char*strptr,size_t len);返回:指向结果的指针成功,NULL出错字母P和N分别代表presentation(地址的表达格式)和numeric(数值格式)。nfamily参数可以是AF_INET,也可以是AF_INET6。n长度参数len是目标的大小,如果太小无法容纳表达格式结果,则返回一个空指针。另外,目标指针调用前必须先由调用者
30、分配空间。inet_pton的实现(只支持的实现(只支持IPv4)int inet_pton(int family,const char*strptr,void*addrptr)if(family=AF_INET)struct in_addr in_val;if(inet_aton(strptr,&in_val)memcpy(addrptr,&in_val,sizeof(struct in_addr);return(1);return(0);else errno=EAFNOSUPPORT;/*以不被支持的地址族做为family的参数*/return(-1);Tcp套接字TCP套接字编程(con
31、t.)socket()bind()listen()accept()read()write()close()socket()connect()write()read()close()阻塞直到接收到客户连接请求TCP服务器端TCP客户端TCP套接字nTCP套接字实现过程n服务器端步骤n创建套接字n绑定套接字n设置套接字为监听模式,进入被动接受连接请求状态n接受请求,建立连接n读/写数据n终止连接n客户端步骤n创建套接字n与远程服务程序连接n读/写数据n终止连接nsocket类似于电话插座。以一个国家级电话网为例。电话的通话双方相当于相互通信的2个进程,区号是它的网络地址;区内一个单位的交换机相当于
32、一台主机,主机分配给每个用户的局内号码相当于socket号。任何用户在通话之前,首先要占有一部电话机,相当于申请一个socket;同时要知道对方的号码,相当于对方有一个固定的socket。nsocket实质上提供了进程通信的端点。进程通信之前,双方首先必须各自创建一个端点,否则是没有办法建立联系并相互通信的。正如打电话之前,双方必须各自拥有一台电话机一样。在网间网内部,每一个socket用一个半相关描述:(协议,本地地址,本地端口)一个完整的socket有一个本地唯一的socket号,由操作系统分配。n对于基于TCP的通信,无论是服务器还是客户,都必须首先产生其TCP通信传输端点,即TCP套接
33、字。n应用程序通过调用socket()产生套接字。该函数调用必须给出所使用的地址簇、套接字类型和协议标志。该函数返回一个套接字描述符。n由于系统中套接字也是一种文件,所以套接字描述符可以看成是一种文件描述符。之后的任何I/O操作都是作用于该套接字描述符。其数据结构包括一个网络连接的5种信息:通信协议、本地协议地址、本机主机端口、远程主机地址和远程协议端口。nsocket函数:为了执行网络输入输出,一个进程必须做的第一件事就是调用socket函数获得一个文件描述符。#include int socket(int family,int type,int protocol);返回:非负描述字成功-1
34、失败第一个参数指明了协议簇,目前支持5种协议簇,最常用的有AF_INET(IPv4协议)和AF_INET6(IPv6协议);第二个参数指明套接口类型,有三种类型可选:SOCK_STREAM(字节流套接口)、SOCK_DGRAM(数据报套接口)和SOCK_RAW(原始套接口);如果套接口类型不是原始套接口,那么第三个参数就为0。#include int sockfd;/crteate socketif(sockfd=socket(AF_INET,SOCK_STREAM,0)=-1)/handle exceptionTCP套接字编程(cont.)socket()bind()listen()acce
35、pt()read()write()close()socket()connect()write()read()close()阻塞直到接收到客户连接请求TCP服务器端TCP客户端n当用socket创建一个套接口后,该套接口还是不能直接使用,因为它只存在于一个名字空间中,也就是只确定了通信所希望使用的服务类型,并没有与该主机上提供服务的某个端口联系在一起,称之为:未命名的套接口。bind()函数通过给一个未命名套接口分配一个本地名字来为套接口建立本地捆绑(主机地址/端口号)。基本套接字函数bind#include int bind(int sockfd,const struct sockaddr*a
36、ddr,socklen_len len)返回:0成功;-1出错并置errnon该函数指明套接字将使用本地的哪一个协议端口进行数据传送(IP地址和端口号),注意:协议地址addr是通用地址。nLen是该地址结构(第二个参数)的长度。n一般而言,服务器调用此函数,而客户则很少调用它?n因为:客户端是主动向服务器发出请求的,客户开始发送数据,系统就给客户端分配一个随机的端口,这个端口和客户端的IP会随着数据一起发给服务器,服务器就可以从中或得客户的IP和端口,接下来服务器就可以利用获得的IP和端口给客户端回应消息。一般情况下,如果你要建立网络服务器应用程序,则你要通知服务器操作系统:请在某地址 xx
37、x.xxx.xxx.xxx上的某端口 yyyy上进行侦听,并且把侦听到的数据包发送给我。如果你的服务器有多个网卡(每个网卡上有不同的IP地址),而你的服务(不管是在udp端口上侦听,还是在tcp端口上侦听),出于某种原因:可能是你的服务器操作系统可能随时增减IP地址,也有可能是为了省去确定服务器上有什么网络端口(网卡)的麻烦 可以要在调用bind()的时候,告诉操作系统:“我需要在 yyyy 端口上侦听,所以发送到服务器的这个端口,不管是哪个网卡/哪个IP地址接收到的数据,都是我处理的。”这时候,服务器程序则在0.0.0.0这个地址上进行侦听。绑定地址时,可以指定地址和端口号,也可以指定其中之
38、一,甚至一个也不指定。通配地址:INADDR_ANY,其值一般为0,它通知内核选择IP地址。IP地址 端口 结果通配地址 0 内核选择IP地址和端口号通配地址 非0 内核选择IP地址,进程指定端口本地IP 0 进程指定IP地址,内核选择端口本地IP 非0 进程指定IP地址和端口号 若指定端口号为0,调用函数bind时,内核选择一个临时端口(在实际中,端口号都要指定);但若指定一个通配但若指定一个通配IP地址,则直到套接字已连接(地址,则直到套接字已连接(TCP)或数据报已在套接)或数据报已在套接字上发出(字上发出(UDP),内核才选择一个本地),内核才选择一个本地IP地址。地址。bind函数(
39、续)n另外,需要注意以下几点:n参数addr中的相关字段在初始化时,必须是网络字节序;n如果由内核来选择IP地址和临时端口号,函数并不返回所选择的值。为了获得这些值,进程必须调用getsockname函数getsockname(int sockfd,struct sockaddr*localaddr,socklen_t*localaddrlen);n函数bind返回的一个常见错误是:所绑定的地址已被其它进程使用,此时errno的值为EADDRINUSE,我们可以通过设置套接字选项SO_REUSEADDR。setsockopt函数函数#include int setsockopt(int soc
40、kfd,int level,int optname,const void*optval,socklen_t optlen);返回:0OK;-1出错。n该函数用于任意类型、任意状态套接口的设置选项值,尽管在不同协议层上存在选项,但本函数定义了最高的“套接口”层次上得选项。选项影响套接口的操作,诸如:广播数据是否可以从套接口发送等等。nsockfd必须指向一个打开的套接字描述字。nLevel是选项所在的层及协议,有如下值:SOL_SOCKET(通用套接字)IPPROTO_TCP(传输层,TCP协议)IPPROTO_IP(网际层,IP协议)nOptname是所要操作的选项名。(SO_、IP_、TCP
41、_是选项名的前缀,不同的level有不同的前缀。)noptval是一个指向变量的指针,通过它设置选项的新值,此变量的大小由最后一个参数指定。bind函数的用法struct sockaddr_in addr;int port=1234;int opt=1;setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt);bzero(&addr,sizeof(addr);addr.sin_family=AF_INET;addr.sin_addr.s_addr =htonl(INADDR_ANY);addr.sin_port=htons(port);if(
42、bind(fd,(struct sockaddr*)&addr,sizeof(addr)=-1)/*错误处理*/SO_REUSEADDR:允许套接口和一个已在使用中得地址绑定。一般缺省条件下一个套接口不能与一个已在使用中的本地地址绑定,但有时会需要“重用”地址。仅在bind()调用时该选项才被解释。TCP套接字编程(cont.)socket()bind()listen()accept()read()write()close()socket()connect()write()read()close()阻塞直到接收到客户连接请求TCP服务器端TCP客户端基本套接字函数listennlisten函数
43、:listen函数仅被TCP服务器调用,它的作用是将用sock创建的主动套接口转换成被动套接口,并等待来自客户端的连接请求。#include int listen(int sockfd,int backlog);返回:0成功-1失败第一个参数是socket函数返回的套接口描述字;第二个参数规定了内核为此套接口排队的最大连接个数。由于listen函数第二个参数的原因,内核要维护两个队列:已完成连接队列和未完成连接队列。未完成队列中存放的是TCP连接的三路握手为完成的连接,accept函数是从已连接队列中取连接返回给进程;当以连接队列为空时,进程将进入睡眠状态。客户服务器Connect调用在未完成
44、队列建立条目SYN JSYN K,ack J+1ack K+1该条目从未完成队列移至已完成队列,acceptConnect返回TCP三路握手和监听套接口的两个队列listen函数(续)三路握手完成两队列之和不能超过backlog已完成连接队列(ESTABLISHED状态)未完成连接队列(SYN_RCVD状态)新到达的SYN分节服务器TCPacceptTCPTCP为监听套接口维护的两个队列为监听套接口维护的两个队列listen函数(续)另外几点说明:n不同的实现对backlog有不同的解释,如源自Berkeley的实现将backlog增加一个模糊因子,把它乘以1.5,再作为两个队列之和;n不要把
45、backlog定义为0,因为有些实现允许1个连接排队,而有些实现不允许有连接排队;n当一个客户SYN到达时,若两个队列都是满的,tcp就忽略此分节,且不发送RST。这是因为,这种情况是暂时的,客户tcp将重发SYN,期望不久的将来就能在队列中找到空闲条目。如果发送如果发送RSTRST,将会出现?将会出现?要是TCP服务器发送一个RST,客户的connect函数立即返回一个错误,强制应用进程处理这种情况,而不是让TCP正常的重传机制处理。客户区别不了两种情况,作为SYN的响应,意为“此端口上没有服务器”的RST和意为“有服务器在此端口上但其队列满”的RST。TCP套接字编程(cont.)sock
46、et()bind()listen()accept()read()write()close()socket()connect()write()read()close()阻塞直到接收到客户连接请求TCP服务器端TCP客户端基本套接字函数connect#include int connect(int sockfd,const struct sockaddr*addr,socklen_t addrlen);返回:0成功;-1出错;n函数connect激发TCP的三路握手过程;仅在成功或出错返回;错误有以下几种情况:n如果客户没有收到SYN分节的响应(总共75秒,这之间需要可能需要重发若干次SYN),则
47、返回ETIMEDOUT。n如果对客户的SYN的响应是RST,则表明该服务器主机在指定的端口上没有进程在等待与之相连。函数返回错误ECONNREFUSED;n如果客户发出的SYN在中间路由器上引发一个目的地不可达ICMP错误,客户上的内核保存此消息,并按第一种情况,连续发送SYN,直到规定时间,返回保存的消息(即ICMP错误)作为EHOSTUNREACH或ENETUNREACH错误返回给进程。connect函数(续)n客户在调用connect前不必非得调用bind函数,此时,内核会选择一个合适的IP地址和临时端口号;n如果函数connect失败,则套接字不可再用,必须关闭。不能再对此套接字再调用
48、函数connect。int sockfd;struct sockaddr_in server;bzero(&server,sizeof(server);server.sin_family=AF_INET;server.sin_port=htons(1234);server.sin_addr.s_addr=inet_addr(“127.0.0.1”);if(connect(sockfd,(struct sockaddr*)&server,sizeof(server)=-1)/handle exceptionTCP套接字编程(cont.)socket()bind()listen()accept()
49、read()write()close()socket()connect()write()read()close()阻塞直到接收到客户连接请求TCP服务器端TCP客户端基本套接字函数accept#include int accept(int sockfd,struct sockaddr*cliaddr,socklen_t*addrlen);返回:非负描述字(connfd)OK;-1出错;naccept函数由TCP服务器调用;从已完成连接队列头返回下一个已完成连接;如果该队列空,则进程进入睡眠状态。n函数返回的套接字为已连接套接字,应与监听套接字区分开来n该函数最多返回三个值返回三个值:一个既可能
50、是新套接字也可能是错误指示的整数,一个客户进程的协议地址(由cliaddr所指),以及该地址的大小(这后两个参数是值结果参数);也就是说,服务器可以通过参数cliaddr来得到请求连接并获得成功的客户的地址和端口号;accept函数示例struct sockaddr_inservaddr,cliaddr;socklen_tlen;intlistenfd,connfd;connfd=accept(listenfd,(struct sockaddr*)&cliaddr,&len);if(connfd=-1)/*出错处理*/printf(“connection from%s”,inet_ntop(A