61
Linux 系 系系 —— socket 系系 系系系系系系系系系系 系系系系系系 系系 系系

Linux 系统开发 —— socket 编程

  • Upload
    amaris

  • View
    147

  • Download
    8

Embed Size (px)

DESCRIPTION

Linux 系统开发 —— socket 编程. 计算机科学与工程学院 信息安全专业 张柱 制作. 第二章 面向非连接的协议. 主要内容: 面向连接与面向非连接通信的差异。 怎样实现非连接的输入和输出操作? 怎样编写一个数据报服务器? 关于套接口地址的操作。 怎样 编写一个数据报客户?. 2.1 通信的方法 2.1.1 非连接通信的优点. 所谓 面向非连接 的通信,就是在通信之前不建立连接。提供不可靠的数据包协议,相对于面向连接的协议,其有如下的优势: 更加简单。 富有弹性。 高效。 快速。 具备广播的能力。. - PowerPoint PPT Presentation

Citation preview

Page 1: Linux 系统开发 —— socket 编程

Linux 系统开发 ——socket 编程

计算机科学与工程学院 信息安全专业 张柱 制作

Page 2: Linux 系统开发 —— socket 编程

第二章 面向非连接的协议主要内容:面向连接与面向非连接通信的差异。怎样实现非连接的输入和输出操作?怎样编写一个数据报服务器?关于套接口地址的操作。怎样 编写一个数据报客户?

Page 3: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.1 通信的方法 2.1.1 非连接通信的优点所谓面向非连接的通信,就是在通信之前不建立连接。提供不可靠的数据包协议,相对于面向连接的协议,其有如下的优势:

更加简单。 富有弹性。 高效。 快速。 具备广播的能力。

Page 4: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.1 通信的方法 2.1.2 非连接通信的却点相对于面向连接的协议,其有如下的不足:

通信过程的不可靠。 多数据报的无序性。 消息的尺寸有限制。

可靠性问题是 UDP 协议无法得到更广泛应用的一个主要原因。引起数据报丢失的原因很多,如中途路由器的数据报校验错误、路由器或接收主机的缓冲区不足等等原因。 UDP 分组传输的距离越长,发生丢失的可能性就越大。UDP 协议的多数据报无序性,是指对于多数据报的传输,接收端收到的数据顺序可能是无序的。

Page 5: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.1 通信的方法 2.1.2 非连接通信的却点对于消息尺寸的限制,理论上, UDP 数据报的最大尺寸略小于 64KB ,实际上,许多 UNIX 主机只提供 32KB 的最大尺寸,甚至只有 8KB 。对于超出限制的 UDP 分组,它将被分解为若干较小的 IP分组,然后在接收端进行重新组装。虽然 UDP 协议是不连接、不可靠的协议,但是有些应用是面向 UDP 协议,而不是 TCP 协议的。比如:DNS、 NFS和 SNMP 等等。

Page 6: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.1 通信的方法 2.1.3 非连接通信的过程调用

socket()

sendto()

recvfrom()

close()

Client

socket()

bind()

Server

recvfrom()

一直阻塞到收到来自客户的数据

处理请求

sendto()

数据(请求)

数据(应答)

UDP通信调用

通信过程:客户不与服务器建立连接,而是调用 sendto() 函数给服务器发送数据报,其中指定目的地址 ( 服务器地址 ) 。服务器不接受来自客户端的连接,而是调用 recvfrom() ,等待来自某个客户的数据到达。recvfrom() 将与所接收的数据报一道返回客户的协议地址。

Page 7: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.2 实现数据报的输入和输出在第一章中,使用的 read()和 write() 可以用于在面向连接的协议中,对套接口进行读和写的操作。当接收和发送基于面向非连接协议的数据报时,就要使用另外的一对函数: sendto()和 recvfrom() 。因为每个消息都可能被发往不同的目的地,能够用来发送数据报的函数必须可以让程序员指定接收者的地址;同样,接收数据报的函数也需要提供一个简单的方法来使得程序员确定发送者的地址。

Page 8: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.2 实现数据报的输入和输出 2.2.1 sendto(2) 函数介绍sendto(2) 函数允许程序员写一个数据报,并同时指定接收者的地址。其语法如下:#include <sys/types.h>#include <sys/socket.h>int sendto(int s,const void *msg,int len,unsigned flags,

const struct sockaddr *to,int tolen);调用成功返回实际发送的字节数,失败返回 -1 ,同时设置errno 。参数 s 是发送者套接口的文件描述符,由先前由 socket()函数生成。 msg 指向需要发送的数据报信息的缓冲区。 len 欲发送的数据报信息的长度 ( 字节数 )。 flags 发送选项,通常取 0。 to 指向接收者的通用套接口地址。 tolen 接收者的套接口地址长度。

Page 9: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.2 实现数据报的输入和输出 2.2.1 sendto(2) 函数介绍注意:参数 to ,必须是一个有效的接收者的套接口地址。参数 tolen ,必须是正确的接收者的套接口地址长度。参数 flags ,表示发送的方式,通常取值 0 ,表示无特定选项。 MSG_OOB 表示发送带外数据。其他取值如下:

Flags 十六进制 意 义0 0x0000 正常,无特定选项

MSG_OOB 0x0001 处理带外数据MSG_DONTROUTE 0x0004 旁路路由,使用直接接口MSG_DONTWAIT 0x0040 非阻塞,等待写入MSG_NOSIGNAL 0x4000 当一端断开连接,不生成 SIGPIPE信号

Page 10: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.2 实现数据报的输入和输出 2.2.2 recvfrom(2) 函数介绍recvfrom(2) 函数使得程序员能够在接收数据的同时,也能得到发送者的地址。其语法如下:#include <sys/types.h>#include <sys/socket.h>int recvfrom(int s,void *buf,int len,unsigned flags,

struct sockaddr *from,int *fromlen);调用成功返回实际接收的字节数。失败返回 -1 ,同时设置errno 。参数 s 是接收数据报的套接口文件描述符。 buf 指向接收消息的缓冲区指针。 len 接收缓冲区的最大长度 ( 字节数 )。flags 发送选项,通常取 0。 from 指向发送端套接口地址的指针。 fromlen 指向发送端套接口地址长度的指针。

Page 11: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.2 实现数据报的输入和输出 2.2.2 recvfrom(2) 函数介绍注意:缓冲区 buf 必须足够大,以容纳所接收的数据报,即 len 参数必须足够大。参数 fromlen 是一个指向地址结构长度的指针。在调用前,该指针指向 from 地址结构的最大长度。调用后,返回发送者地址的真正长度。

Flags 十六进制 意 义0 0x0000 正常,无特定选项

MSG_OOB 0x0001 处理带外数据MSG_PEEK 0x0002 读入数据报,但不从内核中删除

MSG_WAITALL 0x0100 阻塞,直到所有要求满足MSG_ERRQUEUE 0x2000 从错误队列中接收一个分组MSG_NOSIGNAL 0x4000 当一端断开连接,不生成 SIGPIPE信号

Page 12: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.3 关于地址族 2.3.1 套接口地址结构套接口不一定需要地址。无名套接口,使用 socketpair(2)调用生成的一对套接口是互相连接的,但是它们是没有地址的。匿名调用,在两个连接的套接口中,一个连接远端套接口,一个是发出连接的本地套接口。远端套接口必须有地址,而本地套接口可以是无地址,即匿名的。生成地址,地址仅在使用时产生。因为有时需要一个地址进行通信,但是不关心其具体值。而且这个地址仅在通信过程中保持有效,故如果分配固定地址,即浪费资源,又加重了网管员的负担。

Page 13: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.3 关于地址族 2.3.1 套接口地址结构 ( 续 )1. 如下为通用套接口地址结构#include <sys/socket.h>struct sockaddr{ sa_family_t sa_family; // 地址族,无符号整数 2 字节 , 取值为AF_* char sa_data[14];// 地址数据, 14 字节}2. 使用套接口本地地址?提供本地服务使用。本地套接口 domain参数使用 AF_LOCAL或 AF_UNIX 。支持抽象套接口名。本地地址结构如下:#include <sys/un.h>struct sockaddr_un{ sa_family_t sun_family; // 值为 AF_LOCAL或 AF_UNIX, 2字节 char sun_path[108]; // 有效的 UNIX 路径名。结尾无需’ \0’}

Page 14: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.3 关于地址族 2.3.1 套接口地址结构 ( 续 )传统本地地址的命名空间时文件系统的路径名。一个进程可以使用任何有效的路径名来命名本地套接口。套接口“ /dev/printer” 的实际布局,如右图。在填写地址前,应该将地址结构中的所有字节清 0 ,方法如下:struct sockaddr_un uaddr;memset(&uaddr,0,

sizeof uaddr);

sun_family=AF_LOCAL / d

e v / p

tnir

e r

sun_path[108]

Page 15: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.3 关于地址族 2.3.1 套接口地址结构 ( 续 )例程:清单 2.3(生成本地套接口地址,片断)sockaddr_un addr_unix;int len_unix,sck_unix;const char pth_unix[]=“/tmp/my_sock”;// 生成套接口sck_unix=socket (AF_UNIX,SOCK_STREAM,0);………unlink(pth_unix);//删除文件描述符 pth_unix 关联的文件memset(&addr_unix,0,sizeof addr_unix); // 地址结构清 0

#include <string.h>void menset(void *s,int c,size_t n);// 用字符 c填充缓冲区 s的 n 个字节。

#include <unistd.h>int unlink(const char *pathname);// 用于删除目录项和文件。

Page 16: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.3 关于地址族 2.3.1 套接口地址结构 ( 续 )例程:清单 2.3(生成本地套接口地址,片断,续)// 下面初始化地址结构addr_unix.sun_family=AF_UNIX;// 设置地址族// 设置本地地址strncpy(addr_unix.sun_path,pth_unix,sizeof addr_unix.

sun_path-1)[sizeof addr_unix.sun_path-1]=0;/*strncpy(addr_unix.sun_path,pth_unix,sizeof addr_unix.* sun_path-1) ;*(addr_unix.sun_path )[sizeof addr_unix.sun_path-1]=0;*/len_unix=SUN_LEN(&addr_unix);//计算地址长度,宏 SUN_LEN 要求在字符串尾部为空字符。

#include <string.h>char *strncpy(char *dst,const char *src,size_t n);//从源字符串中拷贝 n 个字符到目标字符串中

Page 17: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.3 关于地址族 2.3.1 套接口地址结构 ( 续 )

例程:(生成本地套接口地址,片断,续)// 给套接口绑定地址z=bind(sck_unix,(struct sockaddr *)&adr_unix,len_unix);if (z==-1) bail("bind()");// 通过调用系统命令 netstat完成显示套接口状况system("netstat -pa |grep af_local”);// 关闭使用完毕的套接口close(sck_unix);unlink(pth_unix);//删除文件描述符 pth_unix 关联的文件

int bind(int s,struct sockaddr *addr, int addr_len);

Page 18: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.3 关于地址族 2.3.1 套接口地址结构 ( 续 )小结:生成一个本地的套接口的基本步骤:通过 sck_unix=socket() 建立套接口;使用 unlink(pth_unix)删除文件描述符 pth_unix 关联的文件;因为使用 AF_LOCAL和 AF_UNIX 时,将导致生成本地的文件对象,所以应该保证在使用前后断开该文件描述符和对象的连接。初始化地址结构。给套接口 sck_unix绑定地址。套接口建立完毕,可以正常使用。使用完毕后,使用 close(sck_unix) 调用,关闭套接口。调用 unlink(pth_unix) 。

Page 19: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.3 关于地址族 2.3.1 套接口地址结构 ( 续 )

生成本地抽象套接口地址。为什么要使用抽象本地地址?因为传统 AF_UNIX 套接口存在缺陷:总生成一个文件系统对象。无必要,不方便。在 Linux 内核 2.2 以上,可以对本地套接口生成抽象地址,从而避免了生成文件系统对象。生成抽象地址的套接口,只需要把路径名的第一个字节设置成空字节,而空字节后的其他部分就是抽象名。使用抽象本地地址的套接口不会生成本地文件系统对象,因此无需使用 unlink() 调用删除。

Page 20: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.3 关于地址族 2.3.1 套接口地址结构 ( 续 )例程:清单 2.5(本地抽象套接口地址,片断)const char pth_unix[]=“Z*MY-SOCKET*”;sck_unix=socket (AF_UNIX,SOCK_STREAM,0);………memset(&addr_unix,0,sizeof addr_unix);addr_unix.sun_family=AF_UNIX;strncpy(addr_unix.sun_path,pth_unix,sizeof addr_unix.

sun_path-1)[sizeof addr_unix.sun_path-1]=0;len_unix=SUN_LEN(&addr_unix);/* 使用宏 SUN_LEN计算抽象地址长度时,地址的第一个* 字节不能为空字节’ \0’ ,故在完成计算抽象地址长度后, * 在将地址的第一个字节清 0 。 */

Z 是占位符,后面将其清 0

Page 21: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.3 关于地址族 2.3.1 套接口地址结构 ( 续 )

例程:(本地抽象套接口地址片断,续)// 将路径名的第一个字节清 0addr_unix.sun_path[0]=0;z=bind(sck_unix,(struct sockaddr *)&adr_unix,len_unix);// 通过调用系统命令 netstat完成显示套接口状况system("netstat -pa |grep af_unix”);// 关闭使用完毕的套接口close(sck_unix);

Page 22: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.3 关于地址族 2.3.1 套接口地址结构 ( 续 )小结:使用抽象的本地地址,系统不会生成本地文件对象,无需对文件对象进行删除。使用抽象本地地址的要点:将路径名的第一个字节清零,因此在定义路径名时要考虑第一个字符是占位符,并在计算地址长度后,将其清 0 。先计算地址长度,在将 sun_path[0]清 0 ,这是因为计算地址长度的宏 SUN_LEN 要求第一个字符不为’ \0’ 。程序运行时回显的“ path” 字段,用“@” 表示是一个抽象套接口。

Page 23: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.3 关于地址族 2.3.1 套接口地址结构 ( 续 )

3.internet(IPv4) 套接口地址结构:#include <netinet/in.h>struct in_addr{ uint32_t s_addr;//internet 地址, 32 为无符号整数} ;struct sockaddr_in{ sa_family_t sin_family;// 地址族,取 AF_INET uint16_t sin_port;//TCP/IP 端口号,网络字节序 struct in_addr sin_addr;//internet 地址,网络字节序 unsigned char sin_zero[8];//占位字节,无需初始化} ;

Page 24: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.3 关于地址族 2.3.2 网络字节序转换对于多字节数据,不同的 CPU 有不同的组织方式,其中有两种最基本的字节序:小端字节序,一种将低字节存储在起始位置的方法;大端字节序,一种将高字节存储在起始位置的方法。例如, 0x1234 在两种不同的字节序下的存出情况:

0x12 0x34

0x34 0x12

大端字节序(网络)

小端字节序

Intel 的 CPU 使用的是小端字节序, Motorola的 CPU 大都使用大端字节序。因此,如果Motorola的 CPU 将一个16位的数据通过网络发送出去,而接收端为 IntelCPU 时,就会出现错误的理解,故需要进行转换。

Page 25: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.3 关于地址族 2.3.2 网络字节序转换 ( 续 )

大小端字节序的转换工作包括两个方向:主机字节序到网络字节序;网络字节序到主机字节序。其中,主机字节序是 CPU 使用的字节序,对于 Intel CPU来说,就是小端字节序,对于 Motorola CPU 来说,就是大端字节序。网络字节序就是大端字节序。相应的,有以下两类转换函数:短 (16位 )整数转换;长 (32位 )整数转换。

Page 26: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.3 关于地址族 2.3.2 网络字节序转换 ( 续 )

转换函数如下:#include <netinet/in.h>unsigned long htonl(unsigned long hostlong);// 主机字节序长整数转换成网络字节序长整数 (32位 )unsigned short htons(unsigned short hostshort);// 主机字节序短整数转换成网络字节序短整数 (16位 )unsigned long ntohl(unsigned long netlong);//网络字节序长整数转换成主机字节序长整数 (32位 )unsigned short ntohs(unsigned short netshort); //网络字节序短整数转换成主机字节序短整数 (16位 )

转换函数中,h 表示 host, n代表 net ,s代表 short, l代表

long

Page 27: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.3 关于地址族 2.3.2 网络字节序转换 ( 续 )

转换函数的使用 (例程 ) :short h_short=0x1234;short n_short;// 主机字节序短整数转换成网络字节序n_short=htons(h_short);//网络字节序短整数转换成主机字节序h_short=ntohs(n_short);

Page 28: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.3 关于地址族 2.3.2 网络字节序转换 ( 续 )

下面的清单 (片断 )展示了怎样初始化一个具有通用 IP 地址和通用端口号的 AF_INET 地址。struct sockaddr_in addr_inet;int addr_len;memset(&addr_inet,0,sizeof addr_inet);addr_inet.sin_family=AF_INET;addr_inet.sin_port=htons(0);// 端口号为 0 ,表示为通用端口号,系统会自动分配本地的端口号addr_inet.sin_addr.s_addr=htonl(INADDR_ANY);//INADDR_ANY 表示为通配地址addr_len=sizeof addr_inet;

Page 29: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.3 关于地址族 2.3.3 初始化特定的 Internet 地址例程,清单 2.9 (片断 ) 。下面的清单 (片断 )展示了怎样初始化一个具有特定 IP 地址和端口号的 AF_INET 地址。struct sockaddr_in addr_inet;int addr_len;const unsigned char IPno[]={127,0,0,3};// 特定的 IP 地址,采用网络字节序定义………sck_inet=socket(PF_INET,SOCK_STREAM,0); ………// 下面进行特定地址的初始化,建立套接口地址memset(&addr_inet,0,sizeof addr_inet);addr_inet.sin_family = AF_INET;addr_inet.sin_port = htons(90000);

Page 30: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.3 关于地址族 2.3.3 初始化特定的 Internet 地址例程,清单 2.9 (片断,续 )memcpy(&addr_inet.sin_addr.s_addr,IPno,4);// 将设置的地址数组复制到特定的结构中len_inet = sizeof addr_inet;//绑定套接口地址z=bind(sck_inet,(struct sockaddr *)&addr_inet,len_inet);………

注意:AF_LOCAL 地址的长度是可变的,而 AF_INET 地址的长度是固定的, 16 字节。

#include <string.h>void *memcpy(void *dst,const void *src size_t n);//从源缓冲区 src 中复制 n 个字节内容到目标缓冲区dst

Page 31: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.3 关于地址族 2.3.4 理解网络掩码例程,清单 3.1(片断 ) 。用于检查 IP 地址的类型。………if ((msg & 0x80) ==0x00) { //A类地址

……}else if ((msg & 0xC0) ==0x80){ //B类地址

……}else if ((msg & 0xE0) ==0xC0) { //C类地址

……}else if ((msg & 0xF0) ==0xE0) { //D类地址……}else{ //E类地址……}

Page 32: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.3 关于地址族 2.3.5 inet_addr 和 inet_aton 函数为了减少将字符型 IP 地址转换成可用的套接口地址的编程负担,系统提供了转换函数。#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>unsigned long inet_addr(const char *string);/* 将点分十进制的字符串型 IP 地址转换成 32位二进制网络字节序地址 */int inet_aton(const char *string,struct in_addr *addr);/* 将点分十进制的字符串型 IP 地址转换成 32位二进制网络字节序地址,转换后的地址有 addr 带回,调用成功返回真,失败返回假,同时设置 errno*/

Page 33: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.3 关于地址族 2.3.5 inet_addr 和 inet_aton 函数( 续 )说明:参数 string 为输入的点分十进制的字符型 IP 地址; addr是一个被新 IP 地址更新的结构。inet_addr(3) 函数调用时,如果输入 string 参数无效,函数返回 INADDR_NONE ,但是不设置 errno 。inet_addr(3) 函数,对输入 string 参数为 255. 255. 255. 255 时,返回值仍然时 INADDR_NONE ,因此,该函数使用受限。inet_aton(3) 函数是 inet_addr(3) 函数的改进型。

Page 34: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.3 关于地址族 2.3.5 inet_addr 和 inet_aton 函数( 续 )例程:清单 3.3 (片断 ) 演示如何使用 inet_addr和inet_atonstruct sockaddr_in addr_inet;sck_inet=socket(PF_INET,SOCK_STREAM,0);………memset(&addr_inet,0,sizeof addr_inet);addr_inet.sin_family=AF_INET;addr_inet.sin_port= htons(9000);addr_inet.sin_addr.s_addr=inet_addr("127.0.0.95"); if (addr_inet.sin_addr.s_add==INADDR_NONE)

bail("inet_addr);/*if(!inet_aton("127.0.0.95",&addr_inet.sin_addr)) bail("inet_aton"); */len_inet=sizeof addr_inet;z=bind(sck_inet,(struct sockaddr *)&addr_inet,len_inet);

sockaddr_in 的成员类型

Page 35: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.3 关于地址族 2.3.6 inet_ntoa(3) 函数下面的函数用于将套接口中的地址转换成字符串形式的点分十进制 IP 地址。#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>char *inet_ntoa(struct in_addr addr);参数是 sockaddr结构中成员类型 struct in_addr ,是输入的套接口地址。返回字符串型的 IP 地址。注意:函数 inet_ntoa(3) 的返回值在下一次调用前一直有效。因此,如果在线程中调用该函数,要确定每次只有一个线程调用该函数。

Page 36: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.3 关于地址族 2.3.7 inet_network(3) 函数当我们需要提取 IP 地址中的网络 ID 或主机 ID 时,就需要将点分十进制的 IP 地址转换成主机字节序的 32位二进制 IP 地址。于是有:#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>char *inet_network(const char *addr);该函数输入的是字符串型的点分十进制 IP 地址 addr ,返回主机字节序的 32位二进制 IP 地址。如果输入参数错误,则返回 0xFFFFFFFF 。下面的方法可以提取 C类 IP 地址的网络 ID 和主机 ID :unsigned long net_addr,host_addr;net_addr=inet_network(“210.45.151.109”) & 0xFFFFFF00;host_addr=inet_network(“210.45.151.109”) ^ net_addr;

Page 37: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.3 关于地址族 2.3.8 inet_lnaof 和 inet_netof 函数下面的两个函数分别用于将主机 ID 和网络 ID从套接口地址中提取出来。#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>unsiged long inet_lnaof(struct in_addr addr);//从套接口地址中提取主机 IDunsigned long inet_netof(struct in_addr addr);//从套接口地址中提取网络 ID注意:上述两个函数输入的是网络字节序,计算的结果是根据网络类型来计算的,且都是一个长整数,且是主机序。

Page 38: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.3 关于地址族 2.3.9 inet_makeaddr(3) 函数inet_lnaof和 inet_netof 函数可以从套接口地址中分别提取IP 地址的主机 ID 和网络 ID, inet_makeaddr 函数则可以将网络 ID 和主机 ID合并成一个新的套接口地址。#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>struct in_addr inet_makeaddr(int net,int host);该函数输入的是数值形式的网络 ID 和主机 ID ,这两个数值形式的参数刚好可以有 inet_netof和 inet_lnaof 函数提供。返回网络字节序的套接口地址。

Page 39: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.3 关于地址族 2.3.10 例程例程,清单 3.7(片断 )memset(&addr_inet,0,sizeof addr_inet);addr_inet.sin_family=AF_INET;addr_inet.sin_port=hotns(0);if(!(net_addr=inet_aton(addr[x],&addr_inet.sin_addr))) bail("aton error,bad address!");host=inet_lnaof(addr_inet.sin_addr);net=inet_netof(addr_inet.sin_addr);………addr_inet.sin_addr=inet_makeaddr(net,host);………

Page 40: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.4 为套接口绑定地址 2.4.1 bind() 调用当用 socket(2) 函数创建一个套接口后,该套接口还处于无名状态。如果这个套接口对都在同一个 Linux 内核中,那么即使没有地址,套接口也可以使用。但是,如果该套接口对,位于两台不同的主机,就必须拥有地址。 bind(2) 函数就可以为套接口绑定名字,即分配地址。#include <sys/types.h>#include <sys/socket.h>int bind(int sockfd,struct sockaddr *addr,int addrlen);该函数调用成功返回 0 。失败返回 -1 ,同时设置 errno 。参数 sockfd由 socket() 函数生成的套接口的描述符; addr 分配给套接口的地址; addrlen 是套接口地址的长度。bind() 不能为一个已拥有地址的套接口重新绑定地址。另外,参数 addr为 sockaddr结构指针。

Page 41: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.4 为套接口绑定地址 2.4.2 获取对端地址#include <sys/socket.h>int getsockname(int s,struct sockaddr *name,socklen_t *nlen);int getpeername(int s,struct sockaddr *name,socklen_t *nlen);参数 s 就是需要知道地址的套接口的描述符; name 用于接收地址的缓冲区指针; nlen 指向地址缓冲区最大长度的指针,带回地址的实际长度。注意:因为该函数适用于任何套接口地址类型,故参数 name是通用结构 sockaddr 指针。参数 namelen 确定了地址缓冲区的最大字节数,返回时,将被重置为地址的真正长度,故参数 namelen 不能被赋为常量。

Page 42: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.4 为套接口绑定地址 2.4.2 获取对端地址( 续 )如果程序员编写某个 C 函数,需要使用本地套接口描述符作为输入参数,那么,一定要同时传递该套接口的地址,否则该函数无法获得该套接口的地址。有时,程序员不但需要确定本地套接口的地址,还要确定与本地套接口连接的远程套接口的协议地址。函数 getsockname(2) 可以在不以套接口地址为输入参数时,帮助程序员获得本地套接口的地址。 getpeername(2)函数可以获得与本地套接口连接的远程套接口的协议地址。getsockname(2)和 getpeername(2) 函数调用成功,返回0 ,同时套接口 s 的地址在参数 name 中,地址长度在namelen 中。失败,返回 -1 ,同时设置 errno 。

Page 43: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.4 为套接口绑定地址 2.4.3 例程实例 清单 5.4 。绑定地址sck_inet=socket(PF_INET,SOCK_STREAM,0);// 生成套接口if (sck_inet==-1) bail("socket()");//初始化地址结构memset(&addr_inet,0,sizeof addr_inet);addr_inet.sin_family=AF_INET;addr_inet.sin_port= htons(9000);if(!inet_aton("127.0.0.95",&addr_inet.sin_addr)){

bail("inet_aton"); }len_inet=sizeof addr_inet;z=bind(sck_inet,(struct sockaddr *)&addr_inet,len_inet); //绑定固定地址

addr_inet.sin_addr.s_addr=htonl(INADDR_ANY);if(addr_inet.sin_addr.s_addr==INADDR_NONE)

bail(“bad address”);//初始化通配地址len_inet=sizeof addr_inet;z=bind(sck_inet,(struct sockaddr *)&addr_inet,len_inet);

Page 44: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.5 编写 UDP 数据报服务器程序简单的 UDP 数据报服务程序的基本步骤:(1) 使用 socket(2) 函数生成服务器套接口。(2) 建立服务器的地址并用 bind(2) 函数将其绑定到套接口。(3) 通过调用 recvfrom(2) 函数等待来自客户端的请求数据报。(4)处理请求。(5) 将处理的结果返回客户端。(6) 重复执行步骤 (3) ,直到程序结束。

Page 45: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.5 编写 UDP 数据报服务器程序如下图,可以看出整个典型的 UDP 客户端 / 服务器端程序的调用。客户端不与服务器建立连接,而是只管使用 sendto()函数给服务器发送数据报,其中必须作为参数指定目的地址。类似的,服务器端不接受来自客户端的连接,而是只管调用recvfrom() ,来等待来自某个客户端的数据到达。 recvfrom将与所接收的数据报一道返回客户的协议地址,因此服务器可以把响应发送给正确的客户。注意:在 UDP 协议中,写一个长度为 0 的数据报是可行的,这将导致发送一个只含一个 IP头和一个 UDP头而没有数据的 IP数据报。问题,其长度为……?如果 recvfrom 函数的 from 参数是一个空指针,则相应的长度参数 fromlen 也必须是一个空指针,表示我们不关心数据报的来源。

发送长度为 0 的数据是可行的,其中仅包括 20 字节的 IP 包头个 8 字节UDP 报头。

Page 46: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.5 编写 UDP 数据报服务器程序socket()

bind()

recvfrom()

UDP服务器

一直阻塞到收到来自客户的数据

处理请求

sendto()

socket()

sendto()

recvfrom()

close()

数据(请求)

数据(应答)

UDP客户端

Page 47: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.5 编写 UDP 数据报服务器程序实例,清单 6.1(片断 ) 。编写一个数据报程序,以一定格式的字符串作为输入数据报,然后调用 strftime(2) 函数生成当前的时间 /日期字符串,最后将此结果用另一个数据报返回给客户端程序。int main(int argc, char *argv[]){

if (argc >= 2) srv_addr = argv[1];else srv_addr = “127.0.0.23”;//获得服务器地址s = socket(PF_INET,SOCK_DGRAM,0);// 生成套接口………memset(&addr_inet,0,sizeof addr_inet);//初始化地址结构addr_inet.sin_family = AF_INET;addr_inet.sin_port = htons(9090);if (!inet_aton(srv_addr,&addr_inet.sin_addr)) ………len_inet = sizeof addr_inet;

Page 48: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.5 编写 UDP 数据报服务器程序实例,清单 6.1(片断,续 ) 。z = bind(s,(struct sockaddr *)&addr_inet,len_inet);………//绑定服务器套接口地址for (; ; ) {//利用无条件循环,等待客户端的请求 len_inet = sizeof addr_clnt; z = recvfrom(s,dgram,sizeof dgram,0,(struct sockaddr *) &addr_clnt,&len_inet); ……… dgram[z] = 0; if (!strcasecmp(dgram,“QUIT”)) break; // 验证接收到的数据是否是“ QUIT” time(&td);// 以秒为单位获得当前时间 tm1 = *localtime(&td);//转换成年月日格式

接收请求的服务器套接口接收数据的缓冲区接收数据的缓冲区大小发送选项客户端的 socket 地址客户端地址长度

Page 49: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.5 编写 UDP 数据报服务器程序 //根据输入的格式,格式化日期和时间字符串 strftime(dtfmt,sizeof dtfmt,dgram,&tm1); z = sendto(s,dtfmt,strlen(dtfmt),0,(struct sockaddr *)&addr_clnt,len_inet); ……… } close(s); return 0;}

带回格式化日期的字符串缓冲区。字符串缓冲区的大小。规定日期字符串格式的模型字符串。一个提供年月日的时间结构指针

服务器端用于发送数据报的套接口发送的数据存放的缓冲区发送缓冲区大小客户端 ( 接收端 )socket 地址客户端 socket 地址长度

Page 50: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.5 编写 UDP 数据报服务器程序服务器程序的编译和执行 (假设源程序保存为 dgramsrv.c) :编译: gcc –ggdb –o dgramsrv –c dgramsrv.c执行:1)采用默认的地址,后台运行服务器。./dgramsrv &2) 指定地址,后台运行服务器。./dgramsrv 210.45.151.109 &

Page 51: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.6 编写 UDP 数据报客户程序UDP 数据报客户程序的基本步骤:(1) 确定服务器地址,即客户请求数据报的目的地址。(2) 在客户端生成发送请求和接收数据的套接口。(3)从客户的用户终端接收输入。(4) 将输入以数据报的形式发往服务器。(5)从服务器接收响应。(6) 将服务器的响应显示在客户终端,并从步骤 (3)开始循环,直到客户输入结束标志“ QUIT” 。

Page 52: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.6 编写 UDP 数据报客户程序实例,清单 6.2(片断 ) 。数据报客户程序,提示用户输入在 strftime(3) 中所需要的格式化信息。int main(int argc, char *argv[]){

if (argc >= 2) srv_addr = argv[1];else srv_addr = “127.0.0.23”;//获得服务器地址s = socket(PF_INET,SOCK_DGRAM,0);// 生成套接口………memset(&addr_inet,0,sizeof addr_inet);//初始化地址结构addr_inet.sin_family = AF_INET;addr_inet.sin_port = htons(9090);if (!inet_aton(srv_addr,&addr_inet.sin_addr)) ………len_inet = sizeof addr_inet;

Page 53: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.6 编写 UDP 数据报客户程序实例,清单 6.2(片断,续 ) 。for (; ; ) {//利用无条件循环等待服务器端的响应 ……… if (!fgets(dgram,sizeof dgram,stdin)) break;//从标准输入接受关于时间的格式化字符串, Ctrl+D结束。 z=strlen(dgram); if (z>0 && dgram[--z] == ‘\n’) dgram[z]=0;// 将字符数组 dgram 的结尾赋’ \0’ ,这样可以作为字符串处理。 z = sendto(s,dgram,strlen(dgram),0,(struct sockaddr *)

&addr_srv,len_inet);// 向服务器发送格式化字符串 ……… if (strcasecmp(dgram,"QUIT")== 0) break;//

Page 54: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.6 编写 UDP 数据报客户程序实例,清单 6.2(片断,续 ) 。 len_inet = sizeof addr; z = recvfrom(s,dgram,sizeof dgram,0,(struct sockaddr *)

&addr,&len_inet);// 接受服务器的响应数据 ………. dgram[z]=0;// 在接收缓冲区的末尾加上空字符,以便能够作为字符串处理 ……… } close(s); ………}

Page 55: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.6 编写 UDP 数据报客户程序小结:在客户端,程序应该先发送请求,然后进行一个的接收准备工作,最后接收服务器端的响应。此时,程序员应该考虑,在客户端等待服务器响应的时候,就应该忽略其他程序发送的信号,但这里暂时不予考虑。程序中初始化的 socket 地址,是服务器的地址。作为请求发送的目的地址。本客户端程序,没有通过调用 bind(2) ,给套接口绑定地址,效果上就像使用了通配地址 INADDR_ANY 。

Page 56: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.7 测试 UDP 数据报服务器 / 客户程序测试步骤如下:(1)启动服务器程序。(2)启动客户程序。(3) 在客户端进行输入。(4)按 Ctrl+D( 文件结束符 ) ,关闭客户程序;或输入“ QUIT” ,同时关闭服务器和客户端程序。如下在同一台主机上测试程序,先后台运行服务器程序:

Page 57: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.7 测试 UDP 数据报服务器 / 客户程序由于没有在运行服务器程序时,通过命令行输入 IP 地址,故采用了默认地址 127.0.0.23 。接着运行客户端程序:

格式化字符串的取值:%a, 简写星期; %A,完整星期;%d,日期; %Y,4位年; %l, 时; %M份; %p, 上下午标志; %b, 简写月份; %B,完整月份。

Page 58: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.7 测试 UDP 数据报服务器 / 客户程序 2.7.1 在没有服务器的状态下进行测试如果没有启动服务器程序,单独运行客户端程序也是可以的,也能生成套接口并要求输入,甚至函数 sendto(2) 的调用也能成功,但是在调用 recvfrom(2) 函数时,会进入阻塞。这恰好说明一个事实:发送一个数据报仅仅是将数据报发送出去,但并不能说明它已被成功地接收。

Page 59: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.7 测试 UDP 数据报服务器 / 客户程序 2.7.1 在没有服务器的状态下进行测试服务器程序运行在 IP 地址为 210.45.151.109 ,客户端程序运行在 210.45.151,1 上。先启动服务器程序:

接着,运行客户端程序:

Page 60: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.7 测试 UDP 数据报服务器 / 客户程序 2.7.2 在客户端程序中省略bind(2) 调用使用 bind(2) 函数的作用是什么?限制用于进行通信的接口。在清单 6.2 的客户端程序中,没有使用 bind(2) 对套接口绑定地址,程序可以选择任何一个接口进行数据报发送。在效果上,套接口就像被绑定了通配地址,当程序等待响应时,也可以使用任何一个接口来接收数据报。这时,套接口的端口号也是通配的。当然,也可以通过调用 bind(2) 函数,实现给套接口绑定通配地址,通过使用 INADDR_ANY 达到该目的。端口号使用 0 。代码如下:

memset(&addr_inet,0,sizeof addr_inet);addr_inet.sin_family=AF_INET;addr_inet.sin_port= htons(0);addr_inet.sin_addr.s_addr=htonl(INADDR_ANY);

Page 61: Linux 系统开发 —— socket 编程

计算机系信息安全教研室--张柱

2.7 测试 UDP 数据报服务器 / 客户程序 2.7.3 对通配地址的应答如果客户程序的地址和端口号是通配的,服务器怎么向这个套接口应答,即我们怎样向一个没有特定 IP 地址和 UDP端口号的客户端发送响应报文呢?这是因为 IP 地址和端口号都是在数据报发送时才分配的。例如:运行在 210.45.151.1 上的客户程序调用函数sendto(2) 发送数据报时,已知数据报的目的地址是210.45.151.109 。路由表指示 IP 地址为 210.45.151.1 的以太网接口可以用于发送数据报。因此,这个数据报就有了源地址 210.45.151.1 ,而通配端口号则是从目前所有可用的端口号中随机挑选的。如果客户主机具有多个接口,而且发送了一个数据报,那么这个数据报的源 IP 地址就可能发生变化。总之,源 IP 地址反映的是发送数据报所使用的网络接口的 IP 地址。