| 关键词: 数据 int 协议 函数 MSG 套接字 TCP 时候 客户机 16 |
) 6. 高级套接字函数 在前面的几个部分里面,我们已经学会了怎么样从网络上读写信息了.前面的一些函数(read,write)是网络程序里面最基本的函数.也是最原始的通信函数.在这一章里面,我们一起来学习网络通信的高级函数.这一章我们学习另外几个读写函数. 6.1 recv和send recv和send函数提供了和read和write差不多的功能.不过它们提供 了第四个参数来控制读写操作. int recv(int sockfd,void *buf,int len,int flags) int send(int sockfd,void *buf,int len,int flags) 前面的三个参数和read,write一样,第四个参数可以是0或者是以下的组合 _______________________________________________________________ | MSG_DONTROUTE | 不查找路由表 | | MSG_OOB | 接受或者发送带外数据 | | MSG_PEEK | 查看数据,并不从系统缓冲区移走数据 | | MSG_WAITALL | 等待所有数据 | |--------------------------------------------------------------| MSG_DONTROUTE:是send函数使用的标志.这个标志告诉IP协议.目的主机在本地网络上面,没有必要查找路由表.这个标志一般用网络诊断和路由程序里面. MSG_OOB:表示可以接收和发送带外的数据.关于带外数据我们以后会解释的. MSG_PEEK:是recv函数的使用标志,表示只是从系统缓冲区中读取内容,而不清楚系统缓冲区的内容.这样下次读的时候,仍然是一样的内容.一般在有多个进程读写数据时可以使用这个标志. MSG_WAITALL是recv函数的使用标志,表示等到所有的信息到达时才返回.使用这个标志的时候recv回一直阻塞,直到指定的条件满足,或者是发生了错误. 1)当读到了指定的字节时,函数正常返回.返回值等于len 2)当读到了文件的结尾时,函数正常返回.返回值小于len 3)当操作发生错误时,返回-1,且设置错误为相应的错误号(errno) 如果flags为0,则和read,write一样的操作.还有其它的几个选项,不过我们实际上用的很少,可以查看 Linux Programmers Manual得到详细解释. 6.2 recvfrom和sendto 这两个函数一般用在非套接字的网络程序当中(UDP),我们已经在前面学会了. 6.3 recvmsg和sendmsg recvmsg和sendmsg可以实现前面所有的读写函数的功能. int recvmsg(int sockfd,struct msghdr *msg,int flags) int sendmsg(int sockfd,struct msghdr *msg,int flags) struct msghdr { void *msg_name; int msg_namelen; struct iovec *msg_iov; int msg_iovlen; void *msg_control; int msg_controllen; int msg_flags; } struct iovec { void *iov_base; /* 缓冲区开始的地址 */ size_t iov_len; /* 缓冲区的长度 */ } msg_name和 msg_namelen当套接字是非面向连接时(UDP),它们存储接收和发送方的地址信息.msg_name实际上是一个指向struct sockaddr的指针,msg_name是结构的长度.当套接字是面向连接时,这两个值应设为NULL. msg_iov和msg_iovlen指出接受和发送的缓冲区内容.msg_iov是一个结构指针,msg_iovlen指出这个结构数组的大小. msg_control和msg_controllen这两个变量是用来接收和发送控制数据时的 msg_flags指定接受和发送的操作选项.和recv,send的选项一样 6.4 套接字的关闭 关闭套接字有两个函数close和shutdown.用close时和我们关闭文件一样. 6.5 shutdown int shutdown(int sockfd,int howto) TCP连接是双向的(是可读写的),当我们使用close时,会把读写通道都关闭,有时侯我们希望只关闭一个方向,这个时候我们可以使用shutdown.针对不同的howto,系统回采取不同的关闭方式. howto=0这个时候系统会关闭读通道.但是可以继续往接字描述符写. howto=1关闭写通道,和上面相反,着时候就只可以读了. howto=2关闭读写通道,和close一样 在多进程程序里面,如果有几个子进程共享一个套接字时,如果我们使用shutdown, 那么所有的子进程都不能够操作了,这个时候我们只能够使用close来关闭子进程的套接字描述符. 7. TCP/IP协议 你也许听说过TCP/IP协议,那么你知道到底什么是TCP,什么是IP吗?在这一章里面,我们一起来学习这个目前网络上用最广泛的协议. 7.1 网络传输分层 如果你考过计算机等级考试,那么你就应该已经知道了网络传输分层这个概念.在网络上,人们为了传输数据时的方便,把网络的传输分为7个层次.分别是:应用层,表示层,会话层,传输层,网络层,数据链路层和物理层.分好了层以后,传输数据时,上一层如果要数据的话,就可以直接向下一层要了,而不必要管数据传输的细节.下一层也只向它的上一层提供数据,而不要去管其它东西了.如果你不想考试,你没有必要去记这些东西的.只要知道是分层的,而且各层的作用不同. 7.2 IP协议 IP协议是在网络层的协议.它主要完成数据包的发送作用. 下面这个表是IP4的数据包格式 0 4 8 16 32 -------------------------------------------------- |版本 |首部长度|服务类型| 数据包总长 | -------------------------------------------------- | 标识 |DF |MF| 碎片偏移 | -------------------------------------------------- | 生存时间 | 协议 | 首部较验和 | ------------------------------------------------ | 源IP地址 | ------------------------------------------------ | 目的IP地址 | ------------------------------------------------- | 选项 | ================================================= | 数据 | ------------------------------------------------- 下面我们看一看IP的结构定义 struct ip { #if __BYTE_ORDER == __LITTLE_ENDIAN unsigned int ip_hl:4; /* header length */ unsigned int ip_v:4; /* version */ #endif #if __BYTE_ORDER == __BIG_ENDIAN unsigned int ip_v:4; /* version */ unsigned int ip_hl:4; /* header length */ #endif u_int8_t ip_tos; /* type of service */ u_short ip_len; /* total length */ u_short ip_id; /* identification */ u_short ip_off; /* fragment offset field */ #define IP_RF 0x8000 /* reserved fragment flag */ #define IP_DF 0x4000 /* dont fragment flag */ #define IP_MF 0x2000 /* more fragments flag */ #define IP_OFFMASK 0x1fff /* mask for fragmenting bits */ u_int8_t ip_ttl; /* time to live */ u_int8_t ip_p; /* protocol */ u_short ip_sum; /* checksum */ struct in_addr ip_src, ip_dst; /* source and dest address */ }; ip_vIP协议的版本号,这里是4,现在IPV6已经出来了 ip_hlIP包首部长度,这个值以4字节为单位.IP协议首部的固定长度为20个字节,如果IP包没有选项,那么这个值为5. ip_tos服务类型,说明提供的优先权. ip_len说明IP数据的长度.以字节为单位. ip_id标识这个IP数据包. ip_off碎片偏移,这和上面ID一起用来重组碎片的. ip_ttl生存时间.没经过一个路由的时候减一,直到为0时被抛弃. ip_p协议,表示创建这个IP数据包的高层协议.如TCP,UDP协议. ip_sum首部校验和,提供对首部数据的校验. ip_src,ip_dst发送者和接收者的IP地址 关于IP协议的详细情况,请参考 RFC791 7.3 ICMP协议 ICMP是消息控制协议,也处于网络层.在网络上传递IP数据包时,如果发生了错误,那么就会用ICMP协议来报告错误. ICMP包的结构如下: 0 8 16 32 --------------------------------------------------------------------- | 类型 | 代码 | 校验和 | -------------------------------------------------------------------- | 数据 | 数据 | -------------------------------------------------------------------- ICMP在中的定义是 struct icmphdr { u_int8_t type; /* message type */ u_int8_t code; /* type sub-code */ u_int16_t checksum; union { struct { u_int16_t id; u_int16_t sequence; } echo; /* echo datagram */ u_int32_t gateway; /* gateway address */ struct { u_int16_t __unused; u_int16_t mtu; } frag; /* path mtu discovery */ } un; }; 关于ICMP协议的详细情况可以查看 RFC792 7.4 UDP协议 UDP协议是建立在IP协议基础之上的,用在传输层的协议.UDP和IP协议一样是不可靠的数据报服务.UDP的头格式为: 0 16 32 --------------------------------------------------- | UDP源端口 | UDP目的端口 | --------------------------------------------------- | UDP数据报长度 | UDP数据报校验 | --------------------------------------------------- UDP结构在中的定义为: struct udphdr { u_int16_t source; u_int16_t dest; u_int16_t len; u_int16_t check; }; 关于UDP协议的详细情况,请参考 RFC768 7.5 TCP TCP协议也是建立在IP协议之上的,不过TCP协议是可靠的.按照顺序发送的.TCP的数据结构比前面的结构都要复杂. 0 4 8 10 16 24 32 ------------------------------------------------------------------- | 源端口 | 目的端口 | ------------------------------------------------------------------- | 序列号 | ------------------------------------------------------------------ | 确认号 | ------------------------------------------------------------------ | | |U|A|P|S|F| | |首部长度| 保留 |R|C|S|Y|I| 窗口 | | | |G|K|H|N|N| | ----------------------------------------------------------------- | 校验和 | 紧急指针 | ----------------------------------------------------------------- | 选项 | 填充字节 | ----------------------------------------------------------------- TCP的结构在中定义为: struct tcphdr { u_int16_t source; u_int16_t dest; u_int32_t seq; u_int32_t ack_seq; #if __BYTE_ORDER == __LITTLE_ENDIAN u_int16_t res1:4; u_int16_t doff:4; u_int16_t fin:1; u_int16_t syn:1; u_int16_t rst:1; u_int16_t psh:1; u_int16_t ack:1; u_int16_t urg:1; u_int16_t res2:2; #elif __BYTE_ORDER == __BIG_ENDIAN u_int16_t doff:4; u_int16_t res1:4; u_int16_t res2:2; u_int16_t urg:1; u_int16_t ack:1; u_int16_t psh:1; u_int16_t rst:1; u_int16_t syn:1; u_int16_t fin:1; #endif u_int16_t window; u_int16_t check; u_int16_t urg_prt; }; source发送TCP数据的源端口 dest接受TCP数据的目的端口 seq标识该TCP所包含的数据字节的开始序列号 ack_seq确认序列号,表示接受方下一次接受的数据序列号. doff数据首部长度.和IP协议一样,以4字节为单位.一般的时候为5 urg如果设置紧急数据指针,则该位为1 ack如果确认号正确,那么为1 psh如果设置为1,那么接收方收到数据后,立即交给上一层程序 rst为1的时候,表示请求重新连接 syn为1的时候,表示请求建立连接 fin为1的时候,表示亲戚关闭连接 window窗口,告诉接收者可以接收的大小 check对TCP数据进行较核 urg_ptr如果urg=1,那么指出紧急数据对于历史数据开始的序列号的偏移值 关于TCP协议的详细情况,请查看 RFC793 7.6 TCP连接的建立 TCP协议是一种可靠的连接,为了保证连接的可靠性,TCP的连接要分为几个步骤.我们把这个连接过程称为"三次握手". 下面我们从一个实例来分析建立连接的过程. 第一步客户机向服务器发送一个TCP数据包,表示请求建立连接. 为此,客户端将数据包的SYN位设置为1,并且设置序列号seq=1000(我们假设为1000). 第二步服务器收到了数据包,并从SYN位为1知道这是一个建立请求的连接.于是服务器也向客户端发送一个TCP数据包.因为是响应客户机的请求,于是服务器设置ACK为1,sak_seq=1001(1000+1)同时设置自己的序列号.seq=2000(我们假设为2000). 第三步客户机收到了服务器的TCP,并从ACK为1和ack_seq=1001知道是从服务器来的确认信息.于是客户机也向服务器发送确认信息.客户机设置ACK=1,和ack_seq=2001,seq=1001,发送给服务器.至此客户端完成连接. 最后一步服务器受到确认信息,也完成连接. 通过上面几个步骤,一个TCP连接就建立了.当然在建立过程中可能出现错误,不过TCP协议可以保证自己去处理错误的. 说一说其中的一种错误. 听说过DOS吗?(可不是操作系统啊).今年春节的时候,美国的五大网站一起受到攻击.攻击者用的就是DOS(拒绝式服务)方式.概括的说一下原理. 客户机先进行第一个步骤.服务器收到后,进行第二个步骤.按照正常的TCP连接,客户机应该进行第三个步骤. 不过攻击者实际上并不进行第三个步骤.因为客户端在进行第一个步骤的时候,修改了自己的IP地址,就是说将一个实际上不存在的IP填充在自己IP数据包的发送者的IP一栏.这样因为服务器发的IP地址没有人接收,所以服务端会收不到第三个步骤的确认信号,这样服务务端会在那边一直等待,直到超时. 这样当有大量的客户发出请求后,服务端会有大量等待,直到所有的资源被用光,而不能再接收客户机的请求. 这样当正常的用户向服务器发出请求时,由于没有了资源而不能成功.于是就出现了春节时所出现的情况. 来源http://www.linuxeden.com/ |
|
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系
[邮箱地址] 删除
|