特爱听
Chapter 16 -1
网络编程
11.4 UDP套接口编程
UDP 工作流程:
基本函数介绍:
EG
Server :
#include<stdio.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<errno.h>
#include<string.h>
#define SERVER_PORT 8888
#define MAX_MSG_SIZE 1024
int main(void)
{
int sockfd , addrlen, n ;
struct sockaddr_in addr ;
char msg[MAX_MSG_SIZE] ;
sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd<0)
{
fprintf(stderr,"Socket Error :%s\n",strerror(errno));
return 1;
}
addrlen =sizeof(struct sockaddr_in);
// bzero =sizeof(&addr , addrlen);
bzero(&addr,addrlen);
/*fill server sockadd structure*/
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(SERVER_PORT);
if(bind(sockfd , (struct sockaddr *)(&addr),addrlen)<0)
{
fprintf(stderr,"Bind Error :%s \n",strerror(errno));
return 1;
}
/*read the data from netwok and print the data */
while(1)
{
bzero(msg , MAX_MSG_SIZE );
n = recvfrom(sockfd , msg , sizeof(msg),0,(struct sockaddr *)(&addr),&addrlen);
fprintf(stderr,"Recieve messagee from client :%s \n",msg);
/* Read the date terminate input by user, sent to the network*/
fgets(msg,MAX_MSG_SIZE,stdin);
printf("Server endpoint input message :%s \n",msg);
sendto(sockfd,msg,n,0,(struct sockaddr*)(&addr),addrlen);
}
close(sockfd);
return 0 ;
}
Client:
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<errno.h>
#include<stdio.h>
#include<unistd.h>
#define MAX_BUF_SIZE 1024
int main( int argc ,char *argv [])
{
int sockfd ,port ,addrlen , n ;
char buf[MAX_BUF_SIZE] ;
struct sockaddr_in addr ;
if(argc !=3 )
{
fprintf(stderr,"Usage :%s sever_ip server_port \n",argv[0]);
return 1 ;
}
if( (port = atoi(argv[2]))<0 )
{
fprintf(stderr,"Usage :%s server_ip server_port \a\n",argv[0]);
return 1;
}
sockfd=socket(AF_INET,SOCK_DGRAM,0);
if(sockfd <0 ) {
fprintf(stderr,"Socket Error:%s\n",strerror(errno));
return 1 ;
}
addrlen = sizeof(struct sockaddr_in);
bzero(&addr,addrlen);
addr.sin_family = AF_INET ;
addr.sin_port = htons(port);
/**/
if(inet_aton(argv[1],&addr.sin_addr)<0)
{
fprintf(stderr,"Ip Error :%s\n",strerror(errno));
return 1 ;
}
while(1)
{
bzero(buf,MAX_BUF_SIZE);
fgets(buf,MAX_BUF_SIZE,stdin);
sendto(sockfd , buf , strlen(buf),0,(struct sockaddr *)(&addr),addrlen);
printf("Client endpoint input message :%s\n",buf);
n = recvfrom(sockfd, buf , strlen(buf),0,(struct sockaddr*)(&addr),addrlen);
fprintf(stdout,"Recervie message from server :%s\n",buf);
}
close (sockfd);
return 0 ;
}
11.5 原始套接口编程
需要注意的是,原始套接口只能够由有root权限的用户创建。相比于TCP和UDP套接口,原始套接口具有以下功能
n .使用原始套接口可以读/写ICMP(互联网控制消息协议)及ICMPv6分组,如ping
就使用原始套接口发送ICMP应答请求。
n .使用原始套接口可以读/写特殊的IP数据包,内核不处理这些数据包的IP协议字段,
而出错的诊断将依靠协议字段的意义。
n .利用原始套接口通过设置IP_HDRINCL套接口选项可以构造自己的IP头部。
基于原始套接口编程的相关系统调用与TCP和UDP套接口相同,比如函数socke(),
bind(), connect()等都能使用,下面简单介绍原始套接口的创建之后,给出一个其体的实例来
说明它的使用方法。
ICMP协议简介
要直正了解ping命令实现原理,就要了解ping命令所使用到的TCP/IP协议。ICMP
(Internet Control Message,网际控制报文协议)是为网关和目标主机而提供的一种差错控制机制,使它们在遇到差错时能把错误报告给报文源发送方。ICMP协议是IP层的一个协议,但是由于差错报告在发送给报文源发送方时可能也要经过若干子网,因此牵涉到路由选择等问题,所以ICMP报文需通过IP协议来发送。ICMP数据报的数据发送前需要两级封装:
首先添加ICMP报头形成ICMP报文,再添加IP报头形成IP数据报,封装过程如图11.10所示。
IP报头格式
由于IP层协议是一种点对点的协议,而非端对端的协议,它提供无连接的数据报服务,没有建立端口连接的概念,因此很少使用bindO和connectO函数,若有使用也只是用于设置IP地址.,发送数据使用sendto()函数,接收数据使用recvfromO 函数.IP报头格式如图
在linux中IP报头格式数据
|
struct ip { #ifdef _IP_VHL u_char ip_vhl; /* version << 4 | header length >> 2 */ #else #if BYTE_ORDER == LITTLE_ENDIAN u_int ip_hl:4, /* header length */ ip_v:4; /* version */ #endif #if BYTE_ORDER == BIG_ENDIAN u_int ip_v:4, /* version */ ip_hl:4; /* header length */ #endif #endif /* not _IP_VHL */ u_char 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_char ip_ttl; /* time to live */ u_char ip_p; /* protocol */ u_short ip_sum; /* checksum */ struct in_addr ip_src,ip_dst; /* source and dest address */ }; |
其中,ping程序只使用以下数据:
IP报头长度IHL ( Internet Header Length):以4字节为一个单位来记录IP报头的长
度,是上述IP数据结构的ipee hl变量。
生存时间TTL (Time To Live):以秒为单位,指出Ip数据报能在网络上停留的最长
时间,其值由发送方设定,并在经过路由的每一个节点时减1,当该值为0时,数据报将被丢失,是上述IP数据结构的ip_ttl 变量
ICMP报头格式
ICMP报文分为两种,一种是错误报告报文,另一种是查询报文。ICMP差错报告报文又有五中:
Ø 终点不可达。
Ø 源站抑制。
Ø 时间超过。
Ø 参数问题。
Ø 改变路由(重定向)。
ICMP的查询报文又分为4种:
ü 回送请求和回答报文。
ü 时间戳请求和回答报文。
ü 掩码地址请求和回答报文。
ü 路由器询问和通告报文。
ICMP的报头共有8字节,前4个字节采用统一的格式,共有3个字段,即类型、代码
和检验和,长度分别为8位、8位和16位。接着的4个字节的内容与ICMP的类型有关。 ping命令只使用众多ICMP报文中的两种:请求回送(ICMP ECHO)和请求回应
(ICMP ECHOREPLY )。在Linux中的定义如下(<netinet/ip icmp.h>):
#define ICMP_ECHO 0
#define ICMP_ECHOREPLY 8
这两种ICMP类型报头格式如图:
在linux中,ICMP数据结构的定义如下(取至<netinet/ip_icmp.h>)
使用宏定义令表达更简洁,其中ICMP报头为8 字节,数据报长度最大为64k字节。
下面介绍几个在本小节的实例程序中将要用到的概念:
校验和算法:这一算法称为网际校验和算法,把被校验的数据进行16位累加,然后
取反码.若数据字节长度为奇数,则数据尾部补上一个字节的0以凑成偶数。此算法适用于IPv4, ICMPv4, IGMPv4, ICMPv6. UDP和TCP校验和,更详细的信息请参考RFC 1071,校验和字段为上述ICMP数据结构的icmp_cksum变量。
标识符:用于唯一标识ICMP报文,为上述ICMP数据结构的icmp id宏所指的变量。
顺序号:ping命令的icmp seq便由这里读出,代表ICMP报文的发送顺序,为上迷
ICMP数据结构的icmpee seq宏所指的变量。
:
在本实例中,ping命令需要显示的信息,包括icmp- seq和ttI都已有实现的办法,但还
缺少往返时间rtt的算法实现。为了实现这一功能,可利用ICMP数据报携带一个时间戳。使用下列函数生成时间截。
#include<sys/time.h>
#include<unistd.h>
Int gettimeofday( struct timeval *tv , void *tz);
返回:若成功则返回0,若失败返回一I。错误代码存于ermo.
gettimeofday()函数会把目前的时间由tv所指向的结构体timeval返回,当地时区的信息
则放到tz所指向的结构体timezone中。
其中timeval结构的定义如下(细心的读者会发现,我们在本书的9.2.2小节中已经介绍
过这个结构):
timeval结构中,tv_sec为秒数,tv_usec为微秒数.在发送和接收报文时由gettimeofday
分别生成两个timeval结构,两者之差即为往返时间,即ICMP报文发送与接收的时间差,而timeval结构由ICMP数据报携带,tz指针表示时区,一般都不使用,赋NULL值。
然后,系统自带的ping命令当它接送完所有ICMP报文后,会对所有发送和所有接收的ICMP报文进行统计,从而计算ICMP报文丢失的比率。为达此目的,定义两个全局变最:
接收计数器和发送计数器,用于记录ICMP报文接受和发送数目。丢失数目=发送总数一接
收总数,丢失比率=丢失数目/发送总数。
Ping 源代码
浙公网安备 33010602011771号