1、引言
上一篇着重讲述了网络的一些概念方面的问题,本章将重点放在编程应程序的网络接口的学习。
![这里写图片描述](https://img-blog.csdn.net/20170803133614036?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvR1BGWUNGNTIx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
上图中位蓝色字体处才网络编程的重点。
2、TCP协议
前面说过它是一种面向连接的,可靠的, 基于IP实现的传输层协议,能偶实现对数据精确收发的控制。
当应用层向TCP层发送8位字节为单位的数据流时,TCP通常会安静数据流分割成适当长度数据报文段,紧接着TCP会将数据报传递给IP层。
TCP为了达到数据的准确传送,它会保证如下事情:
(1)通信双方之前首先建立连接,将通信路径先打通,保证传输路径的可靠性和高效性。
(2)数据受到后有应答机制,如果接收方在规定的时间内对方没有应答,那么数据包会 被从新发送。
(3)TCP会有相应的校验函数来收到的数据是否正确,如果数据出错会被重发。
(4)在网络不好时,会自动按照滑动协议对流量进行控制,配合当前的网路通信状况。
(5)对分割的数据报文都有编有相应的序号,一旦数据失序,数据同样会被从新发送。
总之TCP协议的目的就是,保证数据能够被无丢失、无失序、无重复、无错误地可靠的传输到,只是TCP协议对于网络的要求比较高,对于那些对数据的精确度较高的应用程序,如HTTP(超文本协议)、FTP(简单文件传输)、QQ服务器管理客户信息、Telnet(远程登录)和MAIL等应用层协议都需要用到TCP协议。
2.1、TCP连接时的握手机制
为了提高TCP协议传输数据时数据的精度,在真正传输数据之前,收发的双方会先握手三次建立连接,建立连接的发起方是客户端。每当每次成功发送数据后,数据接收方会回答数据是否发送成功,如果回答失败,或者数据发送错误,数据会被从新发送,以保证数据的正确性。当双方断开连接时双方会用30s-2分钟的时间四次握手已断开连接,发起方可以是客户端或者服务器任何一方,如下图所示:
![这里写图片描述](https://img-blog.csdn.net/20170803135402476?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvR1BGWUNGNTIx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
2.2、流量控制(滑动窗口)
数据以数据窗口的形式发送(窗口大小决定了每次发送数据的大小)
1)窗口大小由建立连接的双方根据各自的网络状况和缓冲区情况来决定
2)网络不好导致数据丢失,数据每丢失一次,窗口减为以前的二分之一
当网络状态恢复时,如果窗口大小在初始大小的二分之一以下时,每成功发送一个字节,窗口大小加1倍,当窗口恢复到初始的二分之一时,每成功发送一次,窗口加1字节。
2.3、TCP通信模式
1)、客户/服务器(server/client)通信模式
![这里写图片描述](https://img-blog.csdn.net/20170803135635686?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvR1BGWUNGNTIx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
3、UDP协议
刚好好与TCP相反,它是一种面向无连接的,不可靠的,同样也是基于IP实现的传输层协议。
由于UDP与TCP相反,她不提供对应用层数据的分割,组装,编号排序,也不会确认对方是否收到,唯一的目的就是发送出去,之后就一概不管,特点如下:
(1)通信前不会建立连接,不会确定下固定的收发数据路径,多次发送的UDP数据包可能走得
是不同路径。
(2)不管对方收到数据与否,均无应答机制,发送方无需关心对方是否收到。
(3)没有校验机制。
(4)如果数据发生失序等情况,数据不会被重发。
对于网络状况不是很佳,并且数据庞大,并且允许部分数据丢失时,比如视频、音频和多媒体类的数据,就可以采用UDP协议传输,我们知道DNS使用的就是UDP服务。具体来说,如下情况非常适于利用此种协议传输。
·MSN/Skype/QQ等实时通信软件的点对点之间的文本数据传,以及音视频数据传输;
·当网络比较困难,如果利用TCP进行数据传输时,由于过于平凡的应答以确定数据传输的正确
性,导致难于支持数据的精确传输;
·广播/组播;
·流媒体或者网络多媒体;
3.1、UDP通信模式
1)、客户/服务器(server/client)通信模式
![这里写图片描述](https://img-blog.csdn.net/20170803135738396?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvR1BGWUNGNTIx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
2)、点对点(ptop)通信模式
![这里写图片描述](https://img-blog.csdn.net/20170803135818503?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvR1BGWUNGNTIx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
4、编程模型
4.1、TCP编程接口模型
由于TCP只能用于服务器客户模式,所以我们必须实现服务器和客户两端
![这里写图片描述](https://img-blog.csdn.net/20170803135909127?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvR1BGWUNGNTIx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
4.2、udpudp编程接口模型
4.2.1、点对点(ptop)
![这里写图片描述](https://img-blog.csdn.net/20170803140011181?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvR1BGWUNGNTIx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
对于UDP来说,通信之前不需要握手建立连接,而是直接通过对方的IP地址与其通信,如果自己同时还希望能够接收到对方发送的信息的话,那么自己还需要绑定自己的地址,实际上点对点通信是不能够自己独立存在的,它必须由别人如服务器提供通信对方客户的IP地址。
4.2.2、服务器模式
UDP也可以工作于服务器模式,而服务器模式只不过是点对点通信方式的变体,我们只要把点对点的其中一个改为服务器即可。所有的UDP客户都只将信息发送给服务器,而服务器会建立一张表,记录下所有客户的IP和端口,这样服务器就能够与所有的客户通信,满足客户的服务请求。
5、大小端序和端口号
5.1、大小端序
以一个32位整形数为例:
![这里写图片描述](https://img-blog.csdn.net/20170803140108233?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvR1BGWUNGNTIx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
·大端序(big-endian):The most significant bit存在低地址,The last significant bit存在高地址
![这里写图片描述](https://img-blog.csdn.net/20170803140205400?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvR1BGWUNGNTIx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
·小端序(little-endian):The last significant bit存在低地址,The most significant bit存在高地址
![这里写图片描述](https://img-blog.csdn.net/20170803140247149?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvR1BGWUNGNTIx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
·主机端序:对于主机而言,到底采用什么样的端序因公司不同而不同,而网络一律采用大
端序,因此主机和网络之间往往要进行大小端序的转换。
·主机大小端测试c程序
test_endian.c
#include <stdio.h>
int main(void)
{
char va = (char)0x12345678;
if(0x12 == va) printf("big-endian\n");
else if(0x78 == va) printf("little-endian\n");
return 0;
}
5.2、端口号
5、函数编程接口函数
我们发现不管是TCP还是UDP,实际上实现它们所需的函数都是基本相同的,这里我们集中的将这些函数的原型,所需头文件和功能列出来以供参考,当然如果想要查看最原版最权威函数说明的话,请利用man命令请查看相关函数手册。
5.1、socket函数
(1)、函数原型以及所需头文件
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
(2)、函数功能
根据指定的地址族(domain)、套接字类型(type)和协议编号(protocol)指定的具体协议来创建一个网络通信的应用层所需的通信端口,并返回一个网络套接字文件描述符合分配相应子资源,以支持网络操作。
(3)、参数
·domain:地址协议族,作用有两个,第一个用以知名所用的协议族,第二个指明该协
议中具体IP地址族的格式的。
值得注意的是,BSD的unix(linux)中PF_***用于指明协议族,PF是protocol family的缩写,而AF_***用于指明地址族,AF是address family的缩写,所以domain这个位置我们应该写PF_***,而在绑定地址时指定AF_***,但是由于AF_***的宏值实际上与PF_***的宏值是相等的,所以在domain这里这里我们直接写AF_***来代替PF_***即可。常用参数如下,
AF_UNIX, AF_LOCAL:用于本地(本机)通信协议,当然我们前面已经学过信号,管
道,system V等本机进程间的IPC通信方式,实际上套接字除了能够用于跨机网
络间进程的通信外,也是可以用于本机进程间通信的。当然进行网络间通信时,
如果将源和目的IP设为本机IP或本机回环地址的话,也可以进行本机进程间通
信的。
AF_INET:使用适用于IPV4的,用在Internet上的TCP/IP协议族
AF_INET6:使用适用于IPV6的,用在Internet上的TCP/IP协议族
AF_IPX:novell协议族,一种用于局域网通信的协议,它的效率较高,但是兼容 性差很难用到Internet上,由于TCP/IP协议族的高兼容性,现在的Internet 都用TCP/IP协议族。当让如果某局域对于效率要求很高的话(比如很多局域网
联网游戏),可以采用Novell协议实现局域网数据通信,但是如果访问Internet
时,就必须进行novell与TCP/IP协议族之间的转换才行。 似乎windows并不
支持novell,但是unix和linux显然是支持的。
AF_NETLINK:只有linux才支持,专门用于内核和应用层之间的通信。以前内核与应
用的数据交流都是需要调用专用的API接口才行,但是功能单一,如果我们希望
内核和应用之间实现任意的数据交流的话,应用就可以调用domain填写了 AF_NETLINK的socket API接口,只是内核必须要调用特定的内核API接口才能
与应用的socket通信。
AF_APPLETALK:由苹果公司创建的网络协议的名字,用于Apple系列的个人计算中。
AF_PACKET:与链路层通信,直接获取包含以太网帧头的链路层数据,可以用于监听 所有通过本机网卡的数据。
AF_UNSPEC:不指定任何协议族,这时第三个参数protocol协议族就必须明确指定你
需要哪一个协议了。
·type:用于设置套接字类型,常用参数如下,
SOCK_STREAM:提供有序的、可靠地,基于双向连接地流模式套接字(或称流式套接
字),且支持带外数据(紧急数据)。
流模式是指,传送的数据可被拆成任意大小的字节块,以字节为单位进行传输,也就是说,应用层发送的数据可以根据照实际通信条件优劣,被分为大小不等的多个数据报文,分多批发送,当然一次最多能够发送的数据大小,肯定是由收发双方根据通信优劣和缓存情况共同决定的,并且是可变的,由于数据能够被拆散发送,所以非常有利于控制的收发数据的精确性。
通信前建立连接的目的就是为了摸清双方的实际情况,实时做出调整,帮正数据在满足双方共同通信条件下,被精确地接收和发送。
当domian选为AF_INET或AF_INET6的TCP/IP协议族时,如果type被选为
SOCK_STREAM,那么说明TCP/IP协议族的传输层选的一定是TCP协议,TCP要求
数据精确传输,只有流模式套接字才满足TCP的要求。
SOCK_DGRAM:与SOCK_STREAM相反,它支持无连接 ,无序,不可靠,固定长度的数
据报套接字。数据报是指,应用层传输的数据,被组装为固定长度的数据报,它
不会根据通信情况作出数据报长度的调整,接收方只能一次接收整个数据报,如、
果数据报的长度超过了对方缓冲区的大小,多余的数据将被丢弃。
流式套接字在连接时,会根据双方的通信和各自的收发缓冲区的情况做出调整,而数据报不会管对方的情况,只是根据自己的要求将数据发出,对方是否能被精确收到,并不关心。
当domian选为AF_INET或AF_INET6的TCP/IP协议族时,如果type被选为
SOCK_DGRAM,那么说明TCP/IP协议族的传输层选的一定是UDP协议。
SOCK_SEQPACKET:提供有序的,可靠地,基于双向链接的,固定数据报文长度的数据 传输,接收方必须一次性地完整地接收对方发送的数据,这就像是SOCK_STREAM
和SOCK_DGRAM的综合版。
SOCK_RDM:提供可靠地数据包传输,但是不保证有序。我们与前面的做个对比,
SOCK_STREAM,提供有序,可靠的流套接字
SOCK_DGRAM,提供无序,不可靠的数据包套接字
SOCK_SEQPACKET,提供有序,可靠地数据包套接字
SOCK_RDM,提供无序,可靠的数据包套接字
SOCK_RAW:提供原始的网络层协议的访问,当domian选为AF_INET或AF_INET6的 TCP/IP协议族时,如果type被选为SOCK_RAW,那么说明是直接与TCP/IP协议 族的IP协议,可以实现所有通过Ip层数据包的监控,也可以用于开发基于IP
层的新传输层协议(类似TCP和UDP)。Ping程序直接对应IP层通信。
SOCK_PACKET:直接接受网卡设备驱动进行数据通信。
自从Linux 2.6.27后,参数type还有第二个目的,通过OR如下宏,可以修改socket的行为。
SOCK_NONBLOCK:设置此标志后,套接字文件描述符所指的文件表中的文件状态标志
会被OR上O_NONBLOCK,指定为非阻塞,当然是用fcntl函数进行修改,能得到
相同的结果。
SOCK_CLOEXEC:指定该标志后,文件描述符状态标志会被设置为FD_CLOEXE(close-on
-exec,执行新程序后关闭该描述符),使用fcntl函数设置也能得到相同结果。
·protocol:协议号,用于指定具体所使用的协议,一般来说,如果domian和type都
被明确指定了的话,那么所使用的协议就是唯一的,该参数填0即可,它会自动到
/etc/protocols文件中找到协议唯一对应的协议号,当然你也可以自己填入该协议 所对应的的协议号,但是一定要填写正确,否则函数错误返回,比如TCP是6,UDP
是17。
但是在如下两种情况下,我们必须明确的指定协议编号,以确定到底需要使用什么协议,
(1)当domain指定为AF_UNSPEC时(不指定具体协议族),所使用的协议族就 不明确,这时我们就必须通过指定具体的协议号,来明确的指出使用什么协议。
(2)有事即便是domain和type都指定了,但是有好几个协议都涉及同样的操
作,那么我们就必须明确的指定需要使用这几个协议中的那个协议了,比如:
socket(AF_PACKET, SOCK_RAW, ?);
当domian和type向如上的设置时,表示我们可以监听链路层经过网卡设备驱动的帧数据,而通过网卡驱动的协议数据有三种,第一个ip,第二个arp,第三个rarp,具体我们要侦测那个协议的数据或混杂监听时,就参数protocol就必须明确指定,设置参数如下:
ETH_P_IP:IP协议的数据
ETH_P_ARP:ARP协议数据
ETH_P_RARP:RARP协议数据
ETH_P_ALL:IP/ARP/RARP均可
(4)、返回值
函数调用成功,返回一个套接字文件描述符,失败返回-1,切而errno被设置。
(5)、注意
·协议号存放在/etc/protocols文件中,也可以利用getprotoent系列等函数读取
/etc/protocols文件查的协议号,具体请查看该函数手册。
·当一个进程试图写或者读连接已经断开了的流式套接字时,进程会被返回SIGPIPE信
号。
·SOCK_DGRAM和SOCK_RAW 套接字类型,都是利用sendto(对方IP端口)发送数据,然
后利用recvfrom接受数据。
·SOCK_PACKET已经被packet(7)替代。
·当带外数据到达时,会产生SIGURG信号(紧急信号)。当试图读写断开连接的流逝套
接字时,会产生SIGPIPE信号。当底层数据准备好,可以发送异步通知信号SIGIO通知
收数据。此时。都可以通过fcntl函数的F_SETOEN操作,设置进程或进程组接收以上
产生的信号。
·利用 setsockopt(2) and getsockopt(2)函数可以设置或则获取socket操作属性。
5.1、主机与网络间大小端序转换函数
(1)、函数原型以及所需头文件
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
(2)、函数功能
·htonl:将无符号长整形数从主机大小端序转为网络大小端序。
·htons:将无符号短整形数从主机大小端序转为网络大小端序。
·ntohl:将无符号短整形数从网络大小端序转为主机大小端序。
·ntohs:将无符号短整形数从网络大小端序转为主机大小端序。
(3)、参数
·hostlong:主机端序的无符号长整形数。
·hostshort:主机端序的无符号短整型数。
·netlong:网络端序无符号长整数。
·netshort:网络端序无符号短整形数。
(4)、返回值:返回转换后得值。
(5)、注意:这些函数常用于将端口号的主机与网络间的大小端序的转换。
5.2、点分十进制的字符串IP地址与32位无符号整形IP地址见的转换函数
(1)、函数原型以及所需头文件
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);
in_addr_t inet_addr(const char *cp);
in_addr_t inet_network(const char *cp);
char *inet_ntoa(struct in_addr in);
struct in_addr inet_makeaddr(int net, int host);
in_addr_t inet_lnaof(struct in_addr in);
in_addr_t inet_netof(struct in_addr in);
(2)、函数功能
·inet_aton:
·htons:将无符号短整形数从主机大小端序转为网络大小端序。
·ntohl:将无符号短整形数从网络大小端序转为主机大小端序。
·ntohs:将无符号短整形数从网络大小端序转为主机大小端序。
(3)、参数
·hostlong:主机端序的无符号长整形数。
·hostshort:主机端序的无符号短整型数。
·netlong:网络端序无符号长整数。
·netshort:网络端序无符号短整形数。
(4)、返回值:返回转换后得值。
(5)、注意:这些函数常用于将端口号的主机与网络间的大小端序的转换。
4.3、服务器应对多客户的实现模式
1)、多进程
2)、多线程
3)、多路复用
4.4、网络超时实现
4.5、带外数据
10、网络编程高级篇
10.1、局域网广播
10.2、局域网组播
10.3、网络数据抓包