• 学习笔记11


    一、TCP/IP协议

    什么是TCP/IP协议?

    计算机与网络设备之间如果要相互通信,双方就必须基于相同的方法.比如如何探测到通信目标.由哪一边先发起通信,使用哪种语言进行通信,怎样结束通信等规则都需要事先确定.不同的硬件,操作系统之间的通信,所有这一切都需要一种规则.而我们就将这种规则称为协议 (protocol).也就是说,TCP/IP 是互联网相关各类协议族的总称。

    TCP/IP 的分层管理

    TCP/IP协议里最重要的一点就是分层。TCP/IP协议族按层次分别为 应用层,传输层,网络层,数据链路层,物理层。当然也有按不同的模型分为4层或者7层的。

    • 分层的好处

    把 TCP/IP 协议分层之后,如果后期某个地方设计修改,那么就无需全部替换,只需要将变动的层替换。而且从设计上来说,也变得简单了。处于应用层上的应用可以只考虑分派给自己的任务,而不需要弄清对方在地球上哪个地方,怎样传输,如果确保到达率等问题。

    五层介绍

    • 物理层

    该层负责 比特流在节点之间的传输,即负责物理传输,这一层的协议既与链路有关,也与传输的介质有关。通俗来说就是把计算机连接起来的物理手段。

    • 数据链路层

    控制网络层与物理层之间的通信,主要功能是保证物理线路上进行可靠的数据传递。为了保证传输,从网络层接收到的数据被分割成特定的可被物理层传输的帧。帧是用来移动数据结构的结构包,他不仅包含原始数据,还包含发送方和接收方的物理地址以及纠错和控制信息。其中的地址确定了帧将发送到何处,而纠错和控制信息则确保帧无差错到达。如果在传达数据时,接收点检测到所传数据中有差错,就要通知发送方重发这一帧。

    • 网络层

    决定如何将数据从发送方路由到接收方。网络层通过综合考虑发送优先权,网络拥塞程度,服务质量以及可选路由的花费等来决定从网络中的A节点到B节点的最佳途径。即建立主机到主机的通信。

    • 传输层

    该层为两台主机上的应用程序提供端到端的通信。传输层有两个传输协议:TCP(传输控制协议)和 UDP(用户数据报协议)。其中,TCP是一个可靠的面向连接的协议,udp是不可靠的或者说无连接的协议

    • 应用层

    应用程序收到传输层的数据后,接下来就要进行解读。解读必须事先规定好格式,而应用层就是规定应用程序的数据格式。主要的协议有:HTTP.FTP,Telent等。

    TCP的三次握手与四次挥手

    具体过程如下:

    第一次握手:建立连接。客户端发送连接请求报文段,并将syn(标记位)设置为1,Squence Number(数据包序号)(seq)为x,接下来等待服务端确认,客户端进入SYN_SENT状态(请求连接);

    第二次握手:服务端收到客户端的 SYN 报文段,对 SYN 报文段进行确认,设置 ack(确认号)为 x+1(即seq+1 ; 同时自己还要发送 SYN 请求信息,将 SYN 设置为1, seq为 y。服务端将上述所有信息放到 SYN+ACK 报文段中,一并发送给客户端,此时服务器进入 SYN_RECV状态。

    SYN_RECV是指,服务端被动打开后,接收到了客户端的SYN并且发送了ACK时的状态。再进一步接收到客户端的ACK就进入ESTABLISHED状态。

    第三次握手:客户端收到服务端的 SYN+ACK(确认符) 报文段;然后将 ACK 设置为 y+1,向服务端发送ACK报文段,这个报文段发送完毕后,客户端和服务端都进入ESTABLISHED(连接成功)状态,完成TCP 的三次握手。

    第一次挥手

    客户端设置seq和 ACK ,向服务器发送一个 FIN(终结)报文段。此时,客户端进入 FIN_WAIT_1 状态,表示客户端没有数据要发送给服务端了。

    第二次挥手

    服务端收到了客户端发送的 FIN 报文段,向客户端回了一个 ACK 报文段。

    第三次挥手

    服务端向客户端发送FIN 报文段,请求关闭连接,同时服务端进入 LAST_ACK 状态。

    第四次挥手

    客户端收到服务端发送的 FIN 报文段后,向服务端发送 ACK 报文段,然后客户端进入 TIME_WAIT 状态。服务端收到客户端的 ACK 报文段以后,就关闭连接。此时,客户端等待 2MSL(指一个片段在网络中最大的存活时间)后依然没有收到回复,则说明服务端已经正常关闭,这样客户端就可以关闭连接了。

    如果有大量的连接,每次在连接,关闭都要经历三次握手,四次挥手,这显然会造成性能低下。因此。Http 有一种叫做 长连接(keepalive connections) 的机制。它可以在传输数据后仍保持连接,当客户端需要再次获取数据时,直接使用刚刚空闲下来的连接而无需再次握手。

    网络套接字编程

    网络层的IP可以惟一标识网络中的主机,而传输层的协议、端口这两个东西可以表示主机中的进程(也就是网络应用程序)。

    因此,通过IP、协议、端口号,可以标识网络的进程。

    (1)服务器根据地址的类型(属于ipv4还是ipv6等)、socket类型(比如TCP、UDP)去创建socket,创建出的套接字socket本质上也是个文件描述符。

    (2)服务器绑定IP地址和端口号到套接字socket

    (3)服务器socket监听端口号请求,随时准备接收客户端发来的连接,但这个时候服务器的socket并没有被打开。

    (4)根据地址的类型(属于ipv4还是ipv6等)、socket类型(比如TCP、UDP)去创建socket,创建出的套接字socket本质上也是个文件描述符。

    (5)客户端根据服务器的ip地址和端口号,试图连接服务器

    (6)服务器socket接收到客户端的socket请求,被动打开,开始接收客户端的请求,并等待客户端返回连接信息。这个阶段,服务器的accept方法是阻塞的,即等到刚才试图连接的客户端返回连接信息,accept方法才能返回,才能继续接收下一个最新的客户端连接请求。

    (7)客户端连接成功,向服务器发送连接状态信息

    (8)服务器accept方法返回,连接成功

    (9)客户端发送消息

    (10)服务端接收消息

    (11)客户端关闭

    (12)服务端关闭

    主机字节序

    就是我们平常说的大端和小端模式:不同的CPU有不同的字节序类型,这些字节序是指整数在内存中保存的顺序,这个叫做主机序。引用标准的Big-Endian和Little-Endian的定义如下:
    

    a) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。

    b) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

    网络字节序:

    4个字节的32 bit值以下面的次序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。这种传输次序称作大端字节序。

    由于TCP/IP首部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节序。字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序,一个字节的数据没有顺序的问题了。

    所以:在将一个地址绑定到socket的时候,请先将主机字节序转换成为网络字节序,而不要假定主机字节序跟网络字节序一样使用的是Big-Endian。由于这个问题曾引发过血案!公司项目代码中由于存在这个问题,导致了很多莫名其妙的问题,所以请谨记对主机字节序不要做任何假定,务必将其转化为网络字节序再赋给socket。
    

    TCP服务端实例

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>		  /* See NOTES */
    #include <sys/socket.h>
    #include <netinet/in.h>  
    #include <arpa/inet.h>  
    #include <netdb.h>  
    #include <errno.h>
    #include <unistd.h>
     
    extern int errno;
     
    int main()
    {
    	int domain = AF_INET;
    	int type = SOCK_STREAM;
    	int protocol = 0;
    	int ret  = -1;
    	int nListenFd = -1;
    	int nNewClientFd = -1;
    	short int  port = 2000; 
    	struct sockaddr_in addr_in;
    	int backlog = 128; // 默认是128
    	int len = 0;
    	char chBuffer[1024] = {0};
    	int flags = 0;
    	
    	nListenFd = socket( domain,  type,  protocol);
    	if(nListenFd < 0)
    	{
    		printf("\n socket failed ! errno[%d]  err[%s]\n", errno, strerror(errno));
    		return -1;
    	}
     
    	memset(&addr_in, 0, sizeof(struct sockaddr_in));
    	addr_in.sin_family = AF_INET;
    	addr_in.sin_port = htons(port);//htons的返回值是16位的网络字节序整型数   htons尾的字母s代表short
    	addr_in.sin_addr.s_addr = htonl(INADDR_ANY);
     
    	ret = bind(nListenFd, ( struct sockaddr * )(&addr_in), sizeof(struct sockaddr_in));
        if(ret < 0)
        {
        	printf("\n bind failed ! errno[%d]  err[%s]\n", errno, strerror(errno));
        	close(nListenFd); //避免资源泄漏
    		return -1;
    	}
     
        ret = listen(nListenFd, backlog);
        if(ret < 0)
        {
    		printf("\n listen failed ! errno[%d]	err[%s]\n", errno, strerror(errno));
    		close(nListenFd); //避免资源泄漏
    		return -1;
    	}
     
    	nNewClientFd = accept(nListenFd, ( struct sockaddr *)NULL, NULL); //阻塞模式
    	if(nNewClientFd < 0)
    	{
    		printf("\n accept failed ! errno[%d]	err[%s]\n", errno, strerror(errno));
    		close(nListenFd); //避免资源泄漏
    		return -1;
    	}
    	len = recv(nNewClientFd, chBuffer, sizeof(chBuffer) , flags);//flags为0,阻塞模式
    	if(len < 0)
    	{
    		printf("\n recv failed ! errno[%d]	err[%s]\n", errno, strerror(errno));
    		close(nListenFd); //避免资源泄漏
    		close(nNewClientFd);
    		return -1;
    	}
     
    	chBuffer[sizeof(chBuffer) - 1] = 0;
     
    	printf("\n recv[%s]\n" , chBuffer);
     
    	len = send(nNewClientFd, "Welcome", sizeof("Welcome"), flags);
    	if(len < 0)
    	{
    		printf("\n send failed ! errno[%d]	err[%s]\n", errno, strerror(errno));
    		close(nListenFd); //避免资源泄漏
    		close(nNewClientFd);
    		return -1;
    	}
     
    	close(nNewClientFd);
    	close(nListenFd);
     
    	return 0;
    }
    

    TCP客户端实例

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>		  /* See NOTES */
    #include <sys/socket.h>
    #include <netinet/in.h>  
    #include <arpa/inet.h>  
    #include <netdb.h>  
    #include <errno.h>
    #include <unistd.h>
     
    extern int errno;
     
    int main()
    {
    	int domain = AF_INET;//AF_INET
    	int type = SOCK_STREAM;
    	int protocol = 0;
    	int ret  = -1;
    	int nClientFd = -1;
    	short int  port = 2000; 
    	struct sockaddr_in addr_in;
    	int len = 0;
    	char chBuffer[1024] = {0};
    	int flags = 0;
    	char *pchServerIP = "192.168.1.211";
    	
    	nClientFd = socket( domain,  type,  protocol);
    	if(nClientFd < 0)
    	{
    		printf("\n socket failed ! errno[%d]  err[%s]\n", errno, strerror(errno));
    		return -1;
    	}
     
        memset(&addr_in, 0, sizeof(struct sockaddr_in));
    	addr_in.sin_family = AF_INET;
    	addr_in.sin_port = htons(port);//htons的返回值是16位的网络字节序整型数   htons尾的字母s代表short
    	//addr_in.sin_addr.s_addr = htonl(inet_addr(pchServerIP));//htonl的返回值是16位的网络字节序整型数   htonl尾的字母l代表32位长整型
     
    	addr_in.sin_addr.s_addr = inet_addr(pchServerIP); //htonl(inet_addr(pchServerIP));
    	ret = connect(nClientFd, ( struct sockaddr * )(&addr_in), sizeof(struct sockaddr_in));
        if(ret < 0)
        {
        	printf("\n connect failed ! errno[%d]  err[%s]\n", errno, strerror(errno));
        	close(nClientFd); //避免资源泄漏
    		return -1;
    	}
     
    	len = send(nClientFd, "14.3", sizeof("14.3"), flags);
    	if(len < 0)
    	{
    		printf("\n send failed ! errno[%d]	err[%s]\n", errno, strerror(errno));
    		close(nClientFd); //避免资源泄漏
    		return -1;
    	}
        len = recv(nClientFd, chBuffer, sizeof(chBuffer) , flags);//flags为0,阻塞模式
    	if(len < 0)
    	{
    		printf("\n recv failed ! errno[%d]	err[%s]\n", errno, strerror(errno));
    		close(nClientFd); //避免资源泄漏
    		return -1;
    	}
     
    	chBuffer[sizeof(chBuffer) - 1] = 0;
     
    	printf("\n recv[%s]\n" , chBuffer);
     
    	
    	close(nClientFd);
     
    	return 0;
    }
    

    代码练习

    https://gitee.com/zhang_yu_peng/practice-code/blob/master/随机数.c

  • 相关阅读:
    Informix日期获取上周上月昨天去年SQL
    Oracle-创建一个DBLink的方法
    Kafka-Partitions与Replication Factor 调整准则
    Linux-删除文件空间不释放问题解决
    Redhat7-Oracle-sqlldr-安装配置
    Centos7-安装oracle客户端11.2.0.4
    Centos7-单机安装jumpserver
    Redhat6.4-yum本地源安装配置
    Linux-zip unzip 命令日常使用
    xxl-job日志
  • 原文地址:https://www.cnblogs.com/1208499954qzone/p/15614378.html
Copyright © 2020-2023  润新知