TCPIP网络编程(2)

TCPIP网络编程(2),第1张

地址族与数据序列

IP(Internet Protocol)是为了收发网络数据而分配给计算机的值。


端口号是为了区分程序中创建的套接字而分配的序号。


目前IP地址分为两类,IPV4(四字节地址族 ), IPV6(16字节地址族)。


目前以IPV4地址为例,4字节IP地址分为网络地址和主机地址,且分为A,B, C, D, E类地址。


一般不会使用已经被预约了的E类地址。


其中网络地址(网络ID)是为了区分网络而设置的一部分IP地址,也就是说,向某个IP地址传输数据的时候,并不是一开始就浏览4字节的IP地址,而是先浏览4字节IP地址中的网络地址,首先将数据传输到对应的网络,构成网络的路由器接收到数据后,浏览传输数据的主机地址(主机ID)并将数据传输给目标计算机。


因此,向某网络传输数据,实际上是首先向构成网络的路由器或者交换机传输数据,再由接收数据的路由器根据IP地址中的主机地址,向目标主机传输数据。


可以通过IP地址的第一个字节的数值,判断网络ID占用的字节数:

IP地址分类依据
地址类型首字节范围
A类地址0~127
B类地址128~191
C类地址192-~223
端口号

IP地址用于区分计算机,只要有IP地址就能向目标主机传输数据,但是仅凭IP地址无法将数据传输给最终的应用程序,此时就需要使用到端口号,端口号是在统一 *** 作系统内,对多个socket进行区分的编号。


计算机中一般配有NIC(NetWork Interface Car)数据传输设备,通过NIC向计算机内部传输数据时会用到IP地址, *** 作系统负责把传输到内部的数据分配给套接字,此时就需要使用端口号进行区分套接字。


端口号为16为数据,可分配0-65535,其中0-1023是知名端口,因此应该避开。


地址信息的表示

在网络编程中,IP地址和端口号以结构体的形式表示,表示IPV4的结构体信息如下:

struct sockaddr_in 
{
    short   sin_family;
    USHORT sin_port;
    IN_ADDR sin_addr;
    CHAR sin_zero[8];
};
struct in_addr
{
    in_addr_t  s_addr;   // 32位IPV4地址
}

sin_family: 地址族 ,可选 AF_INET,AF_INET, AF_LOCAL

sin_port: 16位端口号,以网络字节序保存

sin_addr: 32位IP地址,以网络字节序保存

sin_zero: 无特殊含义,必须填充为零,为了实现sockaddr_in和sockaddr大小相同而填充的数据

struct sockaddr
{
    sa_family_t  sin_family;     // 地址族
    char   sa_data[14];          // IP和端口
}

而在socket编程中,需要传递的参数的类型通常为sockadd类型的指针。


但是sockadd对于包含地址和端口信息来讲特别麻烦,因此设计了新的结构体sockadd_in。


网络字节序与主机字节序

字节序:

大端序:高位字节存放低位地址

小端序:高位字节存放高位地址

在通过网络传输数据时统一约定方式为大端序,这种约定称为网络字节序。


而代表CPU保存数据的

方式称为主机字节序。


字节序转换函数:htonl,htons,其中h代表host(主机),n代表network(网络),l表示long,s表示short。


inet_addr 网络地址的初始化:通常在程序中需要以字符串的形式保存IP地址,而在初始化网络地址时,需要以int32类型表示,可使用inet_addr(ipStr) 函数将字符串形式的IP地址转换为int32类型的地址,且该函数可以自动将主机字节序转换为网络字节序。


INADDR_ANY 常数:每次在初始化服务器端套接字的时候,都需要输入IP地址会导致非常繁琐。


此时可以用常数INADDR_ANY初始化服务器端的IP地址,若采用这种方式,则可以自动获取运行服务器端的IP地址。


(若同一计算机中分配了多个IP地址,多宿主计算机,例如路由器,则只要端口号一致,就可以从不同IP地址接收数据,在服务器端中一般会优先采用这种初始化方式。


而在客户端中,除非带有一部分服务器功能,否则不会采用)

注:统一计算机中可以分配多个IP地址,实际的IP地址个数与安装的NIC的数量相等,即使是服务器端的套接字,也需要决定接收哪个NIC(IP地址)传来的数据,因此服务器端的套接字初始化过程中也需要IP地址信息,但是若服务器端只安装有一个NIC,则可以直接使用INADDR_ANY进行IP地址初始化。


windows下套接字初始化

与Linux系统不同的是,在windows下进行socket编程时,首先必须调用WSAStartup()函数,设置程序中用的Winsock版本,并初始化相应的版本库。


#include 

int WSAStratup(WORD wVersionRequested, LPWSADATA lpWSADATA);

因为Winsock中存在多个版本,应该准备WORD(WORD是通过typedef定义的ushort类型的数据,2个字节即一个字)类型的套接字版本信息作为第一个参数传入,例如,若版本为1.2,则表示主版本号为1,副版本号为2,应该传入0x0201,即低八位为主版本号,高八位为副版本号。


为了避免以字节构造版本号信息的麻烦,提供了MAKEWORD函数,用于构造版本信息。


MAKEWORD(1,2);     // 主版本号为1, 副版本号为2

第二个参数需要传入一个WSADATA类型的结构体变量地址,调用完WSAStartup函数后,WSADATA类型的变量中将填充已经初始化的Winsock库信息,虽无特殊含义和用途,但是为了初始化套接字版本以及对应的库,必须传递此参数以调用WSAStartup函数。


TCP/IP协议栈

根据传输数据的方式不同,基于网络协议的套接字一般分为TCP套接字以及UDP套接字。


TCP套接字是面向连接的套接字,因此又称为基于流(Stream)的套接字。


TCP(Transimit Control Protocol)是传输控制协议的简写,表示对数据传输过程的控制。


TCP/IP协议栈如下图所示:

可以理解为数据的收发分为了四个层次化的过程,层次化的原因:在网络数据的传输过程中,并非仅凭计算机软件就能解决问题,而实编写软件前需要构建硬件系统,在此基础上通过软件实现各种算法,需要多个领域共同制定相应的规则,因此需要将“网络数据传输”这个大问题划分为不同层次的小问题,由各个领域逐步突破。


链路层

链路层是物理链接领域标准化的结果,也是最基本的领域,专门定义LAN,WAN,MAN等网络标准,若两台主机需要进行网络数据交换,则需要下图所示的物理链接,链路层就负责提供这些表标准。


 IP层

在准备好物理连接之后,就需要开始传输数据,在复杂的网络中传输数据,首先需要考虑路径的选择问题,向目标主机传输数据需要经过哪些路径,IP层就是解决此问题的。


IP本身是面向消息的,不可靠的协议,每次在需要传输数据的时候,会帮我们选择路径,但每次选择的路径不一定一致。


如果传输中发生路径错误,则会选择其他的路径。


如果发生数据丢失或者错误,则IP层无法解决。


即IP层无法应对数据错误。


TCP/UDP层

IP层解决了数据传输过程中的路径选择问题,只需按照此路径传输数据即可。


TCP层和UDP层以IP层提供的路径为基础,完成实际的网络数据传输工作。


故TCP/UDP层又可以称为传输层。


IP层只关注一个数据包的传输过程,因此,即使传输多个数据包,每个数据包也是由IP层单个进行传输的,假设利用IP层按照次序传输了ABC三个数据包,由于IP层传输数据的不可靠性,可能会导致对方先收到B,再收到AC。


或者对方可能只收到了AC,B在传输过程中发生了丢失。


由于TCP在传输数据的过程中,可以确认对方已收到数据,且能够重传丢失的数据,所以即便IP层不保证数据可靠性传输,这类通信也是可靠的。


应用层

在设计软件的过程中,需要根据程序的特点决定服务端和客户端之间的数据传输规则。


这就是应用层之间的协议。


TCP套接字的相关 *** 作:

bind函数:向套接字分配网络地址。


listen函数:调用listen函数进入等待连接请求状态,只有服务端调用了listen函数,客户端才有可能成功调用connect函数进行socket连接。


int listen(int socket, int backlog);

socket: 服务器端的监听套接字

backlog: 连接请求等待队列的长度,表示最多可进入队列的连接请求

成功返回0,失败返回-1,服务器端处于等待连接请求状态是指,在客户端请求连接时,受理连接请求前使请求一直处于等到状态。


如下图所示:

listen函数中传递的第一个参数即为服务端监听套接字,在客户端请求连接时,本身就需要从网络中收发数据,既然需要收发数据,就需要用到套接字,因此在服务端这个任务就由服务端监听套接字完成,其担负着接收连接请求的任务。


客户端请求连接,且服务端在忙时,会将请求放入连接请求等待队列,参数backlog决定了队列的大小。


被放入队列的连接请求的状态被称为等待连接请求状态。


accept函数:在调用listen函数之后,若有新的连接到来,则应该按照顺序进行处理。


服务端受理请求意味着可以进入数据收发的状态。


但是此时服务端需要额外的套接字与客户端进行数据交换,因为服务端套接字必须用来监听来自服务端的连接请求,因此无法直接用它与客户端进行数据交换。


而accept函数就是用来在服务端创建这个额外的套接字。


int accept(int sock, struct sockaddr* addr, socklen_t* addrlen);

sock: 服务器端的监听套接字

addr:用于保存发起连接的客户端的地址信息

addrlen: addr地址长度

若成功则返回创建的套接字,失败时返回-1,在调用accept函数时,如果连接请求等待队列为空,则accept函数不会返回,直到队列中出现新的客户端连接。


connect函数:客户端的主要工作为创建套接字,以及请求连接。


且客户端在调用connect函数之后,只有服务器端接收连接请求或者是发生断网等异常情况而中断连接请求,才会返回。


这里的“接收请求”并不意味着服务器端调用accept函数,而实服务器端把连接请求信息记录到等到队列中。


因此connect函数返回后并不立即进行数据交换。


注:客户端套接字在何时分配IP地址以及端口呢?客户端在调用connect函数时自动分配这些参数,且分配工作是在 *** 作系统内核中进行的。


IP分配为主机的IP,端口采用随机分配的方式。


TCP原理

TCP套接字的数据收发无边界。


假设服务器端一次发送40字节的数据,客户端也可以通过调用4次read函数每次读取10字节进行接收。


那么在客户端接收10字节的数据后,剩下的30字节的数据在何处等候?

实际上上述TCP收发数据的原理涉及到TCP套接字的IO缓冲区原理:在套接字调用write函数后,并非立即传输数据,调用read函数也不是立即接收数据。


而实在调用write函数的时候,将数据移动到输出缓冲区,调用read函数的时候,将数据移动到输入缓冲区。


IO缓冲区具有如下特性:

  • I/O缓冲区在每个TCP套接字中单独存在
  • I/O缓冲区在创建套接字的时候自动生成
  • 即使关闭套接字也会继续传输输出缓冲区中的遗留数据
  • 关闭套接字将丢失输入缓冲区中的数据 

由于TCP会控制数据流,因此不会出现传输数据大于输入缓冲区的情况,因此,TCP中不会因为缓冲溢出而丢失数据。


TCP与对方套接字连接原理:

TCP在连接过程中,交换数据的格式如下所示,这个过程又称为三次握手:

 套接字是以全双工的方式工作的,因此在收发数据前需要做一些准备工作。


TCP套接字的连接过程如下:

1. 请求连接的主机A向主机B发送如下信息:

    [SYN] SEQ:1000, ACK-

    在该消息中,SEQ为1000,ACK为空,而SEQ为1000的含义为:现在传递的数据包序号为       1000,如果接收无误,请通知我向您传递1001好数据包。


这次传递的消息称为SYN,也就是收发数据前传输的同步消息。


2. 主机B向主机A传递消息:
    [SYN+ACK] SEQ:2000, ACK:1001

    在该消息中,SEQ表示的含义为:现在传输的数据包的序号为2000,如果接收无误,请通知我向您传递2001好数据包。


而ACK:1001的含义为,刚才传输的数据包序号为1000的数据包已经收到,现在请传递序号为1001的数据包

3. 主机A向主机B传输数据:

    [ACK] SEQ: 1001, ACK: 2001

   在该消息中,SEQ:1001表示传输数据包的序号,是在之前传输的数据包序号上加1,即为1001。


ACK:2001表示主机B向主机A传输的序号为2000的数据包已经收到,可以继续传输SEQ为2001的数据包了。


在TCP套接字的通讯过程中,收发数据前向数据包分配序号,并向对方通报此序号,都是为方式数据丢失做准备,通过向数据包分配序号并确认,可以在数据丢失的时候马上查看并重传丢失的数据包。


通过步骤123,主机A和主机B均确认了彼此已经准备就绪,可以进行数据传输了。


TCP与对方主机交换数据的原理:

通过第一步的三次握手流程,主机之间完成了数据交换的准备,可以正式开始收发数据,其方式如下图所示:

 主机A分两次向主机B传输了200字节的数据,首先主机A通过1个数据包发送了100个字节的数据,数据包的SEQ为1200,主机B收到数据包后,向主机A回复ACK:1302数据包,此时ACK的增量是发送的字节数,这样做的优点是,主机A不但能确定主机B是否收到了数据,而且能确定主机B实际 收到的数据的字节数。


因此回复数据包ACK的计算方式为:


                                                

其中SEQ表示主机A发送的数据包的序号,bytes表示传输的字节数,加1是为了告知对方下次要传递的数据包的序号。


在传输过程中,可能出现数据包丢失的情况,此时TCP具有重传机制,会进行数据的重新传输,如下图所示:

 假设在数据传输过程中发生了错误,主机A未收到主机B的回复信息,主机A会试着重传数据包,且此时主机A的TCP套接字会启动计时器以等待主机B的ACK信息,若相应的计时器超时则会进行数据包重传。


TCP断开与对方主机的套接字连接

TCP套接字的结束过程为:首先由套接字A向套接字B传输断开的消息,套接字B发出确认收到的消息,然后向套接字A传递可以断开连接的消息,套接字A同样发出确认消息。


整个流程如下图所示:

 数据包中的FIN表示断开连接,即双方各发送一次FIN消息后断开连接,此过程经历四个阶段,又称为四次握手。


在上述流程中,主机B向主机A传输了两次ACK5001,第二次FIN数据包中的ACK只是因为主机B接收到ACK消息后未收到数据而重传的。


--------------------------------------------to be continued-----------------------------------------------------

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/584429.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-12
下一篇 2022-04-12

发表评论

登录后才能评论

评论列表(0条)

保存