08Linux网络编程.docx
《08Linux网络编程.docx》由会员分享,可在线阅读,更多相关《08Linux网络编程.docx(57页珍藏版)》请在冰豆网上搜索。
08Linux网络编程
LINUX网络编程
1.1.TCP/IP协议概述
协议protocol:
通信双方必须遵循的规矩由iso规定rpc文档
osi参考模型:
(应-表-会-传-网-数-物)
应用层表示层会话层传输层网络层数据链路层物理层
tcp/ip模型4层:
应用层{http超文本传输协议ftp文件传输协议telnet远程登录ssh安全外壳协议stmp简单邮件发送pop3收邮件}
传输层{tcp传输控制协议,udp用户数据包协议}
网络层{ip网际互联协议icmp网络控制消息协议igmp网络组管理协议}
网络接口层{arp地址转换协议,rarp反向地址转换协议,mpls多协议标签交换}
TCP协议:
传输控制协议面向连接的协议能保证传输安全可靠速度慢(有3次握手)
UDP协议:
用户数据包协议非面向连接速度快不可靠
通常是ip地址后面跟上端口号:
ip用来定位主机port区别应用(进程)
http的端口号80ssh-->22telnet-->23ftp-->21用户自己定义的通常要大于1024
1.2.OSI参考模型及TCP/IP参考模型
TCP/IP协议族的每一层的作用:
·网络接口层:
负责将二进制流转换为数据帧,并进行数据帧的发送和接收。
要注意的是数据帧是独立的网络信息传输单元。
·网络层:
负责将数据帧封装成IP数据报,并运行必要的路由算法。
·传输层:
负责端对端之间的通信会话连接和建立。
传输协议的选择根据数据传输方式而定。
·应用层:
负责应用程序的网络访问,这里通过端口号来识别各个不同的进程。
TCP/IP协议族的每一层协议的相关注解:
·ARP:
(地址转换协议)用于获得同一物理网络中的硬件主机地址。
·MPLS:
(多协议标签交换)很有发展前景的下一代网络协议。
·IP:
(网际互联协议)负责在主机和网络之间寻址和路由数据包。
·ICMP:
(网络控制消息协议)用于发送报告有关数据包的传送错误的协议。
·IGMP:
(网络组管理协议)被IP主机用来向本地多路广播路由器报告主机组成员的协议。
·TCP:
(传输控制协议)为应用程序提供可靠的通信连接。
适合于一次传输大批数据的情况。
并适用于要求得到相应的应用程序。
·UDP:
(用户数据包协议)提供了无连接通信,且不对传送包进行可靠的保证。
适合于一次传输少量数据。
1.3.TCP协议
(1)概述
TCP是TCP/IP体系中面向连接的运输层协议,它提供全双工和可靠交付的服务。
它采用许多机制来确保端到端结点之间的可靠数据传输,如采用序列号、确认重传、滑动窗口等。
首先,TCP要为所发送的每一个报文段加上序列号,保证每一个报文段能被接收方接收,并只被正确的接收一次。
其次,TCP采用具有重传功能的积极确认技术作为可靠数据流传输服务的基础。
这里“确认”是指接收端在正确收到报文段之后向发送端回送一个确认(ACK)信息。
发送方将每个已发送的报文段备份在自己的缓冲区里,而且在收到相应的确认之前是不会丢弃所保存的报文段的。
“积极”是指发送发在每一个报文段发送完毕的同时启动一个定时器,加入定时器的定时期满而关于报文段的确认信息还没有达到,则发送发认为该报文段已经丢失并主动重发。
为了避免由于网络延时引起迟到的确认和重复的确认,TCP规定在确认信息中捎带一个报文段的序号,使接收方能正确的将报文段与确认联系起来。
最后,采用可变长的滑动窗口协议进行流量控制,以防止由于发送端与接收端之间的不匹配而引起的数据丢失。
这里所采用的滑动窗口协议与数据链路层的滑动窗口协议在工作原理上完全相同,唯一的区别在于滑动窗口协议用于传输层是为了在端对端节点之间实现流量控制,而用于数据链路层是为了在相邻节点之间实现流量控制。
TCP采用可变长的滑动窗口,使得发送端与接收端可根据自己的CPU和数据缓存资源对数据发送和接收能力来进行动态调整,从而灵活性更强,也更合理。
(2)三次握手协议
在利用TCP实现源主机和目的主机通信时,目的主机必须同意,否则TCP连接无法建立。
为了确保TCP连接的成功建立,TCP采用了一种称为三次握手的方式,三次握手方式使得“序号/确认号”系统能够正常工作,从而使它们的序号达成同步。
如果三次握手成功,则连接建立成功,可以开始传送数据信息。
其三次握手分别为:
1)源主机A的TCP向主机B发送连接请求报文段,其首部中的SYN(同步)标志位应置为1,表示想跟目标主机B建立连接,进行通信,并发送一个同步序列号X(例:
SEQ=100)进行同步,表明在后面传送数据时的第一个数据字节的序号为X+1(即101)。
2)目标主机B的TCP收到连接请求报文段后,如同意,则发回确认。
再确认报中应将ACK位和SYN位置为1.确认号为X+1,同时也为自己选择一个序号Y。
3)源主机A的TCP收到目标主机B的确认后要想目标主机B给出确认。
其ACK置为1,确认号为Y+1,而自己的序号为X+1。
TCP的标准规定,SYN置1的报文段要消耗掉一个序号。
运行客户进程的源主机A的TCP通知上层应用进程,连接已经建立。
当源主机A向目标主机B发送第一个数据报文段时,其序号仍为X+1,因为前一个确认报文段并不消耗序号。
当运行服务进程的目标主机B的TCP收到源主机A的确认后,也通知其上层应用进程,连接已经建立。
至此建立了一个全双工的连接。
三次握手:
为应用程序提供可靠的通信连接。
适合于一次传输大批数据的情况。
并适用于要求得到响应的应用程序。
(3)TCP数据报头
TCP头信息
·源端口、目的端口:
16位长。
标识出远端和本地的端口号。
·序号:
32位长。
标识发送的数据报的顺序。
·确认号:
32位长。
希望收到的下一个数据报的序列号。
·TCP头长:
4位长。
表明TCP头中包含多少个32位字。
·6位未用。
·ACK:
ACK位置1表明确认号是合法的。
如果ACK为0,那么数据报不包含确认信息,确认字段被省略。
·PSH:
表示是带有PUSH标志的数据。
接收方因此请求数据报一到便可送往应用程序而不必等到缓冲区装满时才发送。
·RST:
用于复位由于主机崩溃或其他原因而出现的错误的连接。
还可以用于拒绝非法的数据报或拒绝连接请求。
·SYN:
用于建立连接。
·FIN:
用于释放连接。
·窗口大小:
16位长。
窗口大小字段表示在确认了字节之后还可以发送多少个字节。
·校验和:
16位长。
是为了确保高可靠性而设置的。
它校验头部、数据和伪TCP头部之和。
·可选项:
0个或多个32位字。
包括最大TCP载荷,窗口比例、选择重复数据报等选项。
1.4.UDP协议
(1)概述
UDP即用户数据报协议,它是一种无连接协议,因此不需要像TCP那样通过三次握手来建立一个连接。
同时,一个UDP应用可同时作为应用的客户或服务器方。
由于UDP协议并不需要建立一个明确的连接,因此建立UDP应用要比建立TCP应用简单得多。
它比TCP协议更为高效,也能更好地解决实时性的问题。
如今,包括网络视频会议系统在内的众多的客户/服务器模式的网络应用都使用UDP协议。
(2)Udp数据包头格式
1.5.协议的选择
(1)对数据可靠性的要求
对数据要求高可靠性的应用需选择TCP协议,如验证、密码字段的传送都是不允许出错的,而对数据的可靠性要求不那么高的应用可选择UDP传送。
(2)应用的实时性
TCP协议在传送过程中要使用三次握手、重传确认等手段来保证数据传输的可靠性。
使用TCP协议会有较大的时延,因此不适合对实时性要求较高的应用,如VOIP、视频监控等。
相反,UDP协议则在这些应用中能发挥很好的作用。
(3)网络的可靠性
由于TCP协议的提出主要是解决网络的可靠性问题,它通过各种机制来减少错误发生的概率。
因此,在网络状况不是很好的情况下需选用TCP协议(如在广域网等情况),但是若在网络状况很好的情况下(如局域网等)就不需要再采用TCP协议,而建议选择UDP协议来减少网络负荷。
2.网络相关概念
1)套接口的概念:
套接口,也叫“套接字”。
是操作系统内核中的一个数据结构,它是网络中的节点进行相互通信的门户。
它是网络进程的ID。
网络通信,归根到底还是进程间的通信(不同计算机上的进程间通信)。
在网络中,每一个节点(计算机或路由)都有一个网络地址,也就是IP地址。
两个进程通信时,首先要确定各自所在的网络节点的网络地址。
但是,网络地址只能确定进程所在的计算机,而一台计算机上很可能同时运行着多个进程,所以仅凭网络地址还不能确定到底是和网络中的哪一个进程进行通信,因此套接口中还需要包括其他的信息,也就是端口号(PORT)。
在一台计算机中,一个端口号一次只能分配给一个进程,也就是说,在一台计算机中,端口号和进程之间是一一对应关系。
所以,使用端口号和网络地址的组合可以唯一的确定整个网络中的一个网络进程。
例如,如网络中某一台计算机的IP为10.92.20.160,操作系统分配给计算机中某一应用程序进程的端口号为1500,则此时10.92.20.1601500就构成了一个套接口。
2)端口号的概念:
在网络技术中,端口大致有两种意思:
一是物理意义上的端口,如集线器、交换机、路由器等用于连接其他网络设备的接口。
二是指TCP/IP协议中的端口,端口号的范围从0~65535,一类是由互联网指派名字和号码公司ICANN负责分配给一些常用的应用程序固定使用的“周知的端口”,其值一般为0~1023.例如http的端口号是80,ftp为21,ssh为22,telnet为23等。
还有一类是用户自己定义的,通常是大于1024的整型值。
3)ip地址的表示:
通常用户在表达IP地址时采用的是点分十进制表示的数值(或者是为冒号分开的十进制Ipv6地址),而在通常使用的socket编程中使用的则是二进制值,这就需要将这两个数值进行转换。
ipv4地址:
32bit,4字节,通常采用点分十进制记法。
例如对于:
10000000000010110000001100011111
点分十进制表示为:
128.11.3.31
ip地址的分类:
特殊的ip地址:
2.1.socket概念
Linux中的网络编程是通过socket接口来进行的。
socket是一种特殊的I/O接口,它也是一种文件描述符。
它是一种常用的进程之间通信机制,通过它不仅能实现本地机器上的进程之间的通信,而且通过网络能够在不同机器上的进程之间进行通信。
每一个socket都用一个半相关描述{协议、本地地址、本地端口}来表示;一个完整的套接字则用一个相关描述{协议、本地地址、本地端口、远程地址、远程端口}来表示。
socket也有一个类似于打开文件的函数调用,该函数返回一个整型的socket描述符,随后的连接建立、数据传输等操作都是通过socket来实现的;
2.2.socket类型
(1)流式socket(SOCK_STREAM)用于TCP通信
流式套接字提供可靠的、面向连接的通信流;它使用TCP协议,从而保证了数据传输的正确性和顺序性。
(2)数据报socket(SOCK_DGRAM)用于UDP通信
数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证是可靠、无差错的。
它使用数据报协议UDP。
(3)原始socket(SOCK_RAW)用于新的网络协议实现的测试等
原始套接字允许对底层协议如IP或ICMP进行直接访问,它功能强大但使用较为不便,主要用于一些协议的开发。
2.3.socket信息数据结构
structsockaddr
{
unsignedshortsa_family;/*地址族*/
charsa_data[14];/*14字节的协议地址,包含该socket的IP地址和端口号。
*/
};
structsockaddr_in
{
shortintsa_family;/*地址族*/
unsignedshortintsin_port;/*端口号*/
structin_addrsin_addr;/*IP地址*/
unsignedcharsin_zero[8];/*填充0以保持与structsockaddr同样大小*/
};
structin_addr
{
unsignedlongints_addr;/*32位IPv4地址,网络字节序*/
};
头文件
sa_family:
AF_INETàIPv4协议AF_INET6àIPv6协议
2.4.数据存储优先顺序的转换
计算机数据存储有两种字节优先顺序:
高位字节优先(称为大端模式)和低位字节优先(称为小端模式)。
内存的低地址存储数据的低字节,高地址存储数据的高字节的方式叫小端模式。
内存的高地址存储数据的低字节,低地址存储数据高字节的方式称为大端模式。
eg:
对于内存中存放的数0x12345678来说
如果是采用大端模式存放的,则其真实的数是:
0x12345678
如果是采用小端模式存放的,则其真实的数是:
0x78563412
如果称某个系统所采用的字节序为主机字节序,则它可能是小端模式的,也可能是大端模式的。
而端口号和IP地址都是以网络字节序存储的,不是主机字节序,网络字节序都是大端模式。
要把主机字节序和网络字节序相互对应起来,需要对这两个字节存储优先顺序进行相互转化。
这里用到四个函数:
htons(),ntohs(),htonl()和ntohl().这四个地址分别实现网络字节序和主机字节序的转化,这里的h代表host,n代表network,s代表short,l代表long。
通常16位的IP端口号用s代表,而IP地址用l来代表。
函数原型如下:
2.5.地址格式转化
通常用户在表达地址时采用的是点分十进制表示的数值(或者是为冒号分开的十进制Ipv6地址),而在通常使用的socket编程中使用的则是32位的网络字节序的二进制值,这就需要将这两个数值进行转换。
这里在Ipv4中用到的函数有inet_aton()、inet_addr()和inet_ntoa(),而IPV4和Ipv6兼容的函数有inet_pton()和inet_ntop()。
IPv4的函数原型:
#include
#include
#include
intinet_aton(constchar*straddr,structin_addr*addrptr);
char*inet_ntoa(structin_addrinaddr);
in_addr_tinet_addr(constchar*straddr);
函数inet_aton():
将点分十进制数的IP地址转换成为网络字节序的32位二进制数值。
返回值:
成功,则返回1,不成功返回0.
参数straddr:
存放输入的点分十进制数IP地址字符串。
参数addrptr:
传出参数,保存网络字节序的32位二进制数值。
函数inet_ntoa():
将网络字节序的32位二进制数值转换为点分十进制的IP地址。
函数inet_addr():
功能与inet_aton相同,但是结果传递的方式不同。
inet_addr()若成功则返回32位二进制的网络字节序地址。
IPv4和IPv6的函数原型:
#include
intinet_pton(intfamily,constchar*src,void*dst);
constchar*inet_ntop(intfamily,constvoid*src,char*dst,socklen_tlen);
函数inet_pton跟inet_aton实现的功能类似,只是多了family参数,该参数指定为AF_INET,表示是IPv4协议,如果是AF_INET6,表示IPv6协议。
函数inet_ntop跟inet_ntoa类似,其中len表示表示转换之后的长度(字符串的长度)。
Example:
#include
#include
#include
#include
intmain()
{
charip[]="192.168.0.101";
structin_addrmyaddr;
/*inet_aton*/
intiRet=inet_aton(ip,&myaddr);
printf("%x\n",myaddr.s_addr);
/*inet_addr*/
printf("%x\n",inet_addr(ip));
/*inet_pton*/
iRet=inet_pton(AF_INET,ip,&myaddr);
printf("%x\n",myaddr.s_addr);
myaddr.s_addr=0xac100ac4;
/*inet_ntoa*/
printf("%s\n",inet_ntoa(myaddr));
/*inet_ntop*/
inet_ntop(AF_INET,&myaddr,ip,16);
puts(ip);
return0;
}
2.6.名字地址转化
通常,人们在使用过程中都不愿意记忆冗长的IP地址,尤其到Ipv6时,地址长度多达128位,那时就更加不可能一次性记忆那么长的IP地址了。
因此,使用主机名或域名将会是很好的选择。
主机名与域名的区别:
主机名通常在局域网里面使用,通过/etc/hosts文件,主机名可以解析到对应的ip;域名通常是再internet上使用。
众所周知,XX的域名为:
,而这个域名其实对应了一个XX公司的IP地址,那么XX公司的IP地址是多少呢?
我们可以利用ping来得到XX公司的ip地址,如图。
那么,系统是如何将这个域名转化为IP地址220.181.111.148的呢?
在linux中,有一些函数可以实现主机名和地址的转化,最常见的有gethostbyname()、gethostbyaddr()等,它们都可以实现IPv4和IPv6的地址和主机名之间的转化。
其中gethostbyname()是将主机名转化为IP地址,gethostbyaddr()则是逆操作,是将IP地址转化为主机名。
函数原型:
#include
structhostent*gethostbyname(constchar*hostname);
structhostent*gethostbyaddr(constchar*addr,size_tlen,intfamily);
结构体:
structhostent
{
char*h_name;/*正式主机名*/
char**h_aliases;/*主机别名*/
inth_addrtype;/*主机IP地址类型IPv4为AF_INET*/
inth_length;/*主机IP地址字节长度,对于IPv4是4字节,即32位*/
char**h_addr_list;/*主机的IP地址列表*/
}
#defineh_addrh_addr_list[0]/*保存的是ip地址*/
函数gethostbyname():
用于将域名()或主机名转换为IP地址。
参数hostname指向存放域名或主机名的字符串。
函数gethostbyaddr():
用于将IP地址转换为域名或主机名。
参数addr是一个IP地址,此时这个ip地址不是普通的字符串,而是要通过函数inet_aton()转换。
len为IP地址的长度,AF_INET为4。
family可用AF_INET:
Ipv4或AF_INET6:
Ipv6。
Example1:
将XX的转换为ip地址
#include
#include
#include
intmain(intargc,char**argv)
{
char*ptr,**pptr;
structhostent*hptr;
charstr[32]={'\0'};
/*取得命令后第一个参数,即要解析的域名或主机名*/
ptr=argv[1];//如
/*调用gethostbyname()。
结果存在hptr结构中*/
if((hptr=gethostbyname(ptr))==NULL)
{
printf("gethostbynameerrorforhost:
%s\n",ptr);
return0;
}
/*将主机的规范名打出来*/
printf("officialhostname:
%s\n",hptr->h_name);
/*主机可能有多个别名,将所有别名分别打出来*/
for(pptr=hptr->h_aliases;*pptr!
=NULL;pptr++)
printf("alias:
%s\n",*pptr);
/*根据地址类型,将地址打出来*/
switch(hptr->h_addrtype)
{
caseAF_INET:
caseAF_INET6:
pptr=hptr->h_addr_list;
/*将刚才得到的所有地址都打出来。
其中调用了inet_ntop()函数*/
for(;*pptr!
=NULL;pptr++)
printf("address:
%s\n",inet_ntop(hptr->h_addrtype,*pptr,str,sizeof(str)));
printf("firstaddress:
%s\n",inet_ntop(hptr->h_addrtype,hptr->h_addr,str,sizeof(str)));
break;
default:
printf("unknownaddresstype\n");
break;
}
return0;
}
编译运行
#gcctest.c
#./a.out
officialhostname:
alias:
address:
220.181.111.148
……
firstaddress:
220.181.111.148
(注:
这里需要联网才能访问。
可以尝试用自己的虚拟机的主机名,利用命令hostname可以查看自己的主机名,用hostname–i可以查看主机名对