一、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