目录
二、OSI七层协议模型 (open system interconnection)
一、TCP/IP四层协议图
二、OSI七层协议模型 (open system interconnection)
应用层————为应用数据提供服务
表示层————数据格式转化,数据加密
会话层————建立、维护和管理会话
传输层————建立、维护和管理端到端的链接,控制数据传输的方式
网络层————数据传输线路选择,IP地址及路由选择
数据链路层———物理通路的发送和数据包的划分,附加Mac地址到数据包
物理层———01比特流的转换
数据传输由顶向下,下层为上层提供服务
三、TCP/IP四层协议模型
应用层———负责处理特定的应用程序细节, 如ftp,http ,smtp,ssh 等
运输层———主要为两台主机上的应用提供端到端的通信, 如TCP,UDP。
网络层(互联网层)———处理分组在网络中的活动,比如分组的选路。
链路层(数据链路层/网络接口层)———包括操作系统中的设备驱动程序、计算机中对应的 网络接口卡,01比特流的转换
四、协议封装
下层协议通过封装为上层协议提供服务。应用程序数据在发送到物理网络上之前,将沿着协议栈从上往下依次传递。每层协议都将在上层数据的基础上加上自己的头部信息(有时也包括尾部信息),以实现该层的功能。
五、TCP协议头部
源端口号和目的端口号:再加上Ip首部的源IP地址和目的IP地址可以唯一确定一个TCP连接
数据序号:表示在这个报文段中的第一个数据字节序号
确认序号:仅当ACK标志为1时有效。确认号表示期望收到的下一个字节的序号(这个下面再详细分析)
偏移:就是头部长度,有4位,跟IP头部一样,以4字节为单位。最大是60个字节
保留位:6位,必须为0
6个标志位:
URG-紧急指针有效
ACK-确认序号有效
PSH-接收方应尽快将这个报文交给应用层
RST-连接重置
SYN-同步序号用来发起一个连接
FIN-终止一个连接
窗口字段:16位,代表的是窗口的字节容量,也就是TCP的标准窗口最大为2^16 - 1 = 65535个字节
校验和:源机器基于数据内容计算一个数值,收信息机要与源机器数值 结果完全一样,从而证明数据的有效性。检验和覆盖了整个的TCP报文段:这是一个强制性的字段,一定是由发送端计算和存储,并由接收端进行验证的。
紧急指针:是一个正偏移量,与序号字段中的值相加表示紧急数据最后一个字节的序号。TCP的紧急方式是发送端向另一端发送紧急数据的一种方式
选项与填充(必须为4字节整数倍,不够补0):
最常见的可选字段的最长报文大小MSS(Maximum Segment Size),每个连接方通常都在一个报文段中指明这个选项。它指明本端所能接收的最大长度的报文段。
该选项如果不设置,默认为536(20+20+536=576字节的IP数据报)
六、三次握手
(1)男孩喜欢女孩,于是写了一封信告诉女孩:我喜欢你,请和我交往吧!写完信之后,男孩焦急地等待,因为不知道信能否顺利传达给女孩。
(2)女孩收到男孩的情书后,心花怒放,原来我们是两情相悦呀!于是给男孩写了一封回信:我收到你的情书了,也明白了你的心意,其实,我也喜欢你!我愿意和你交往!;
写完信之后,女孩也焦急地等待,因为不知道回信能否能顺利传达给男孩。
(3)男孩收到回信之后很开心,因为发出的情书女孩收到了,并且从回信中知道了女孩喜欢自己,并且愿意和自己交往。然后男孩又写了一封信告诉女孩:你的心意和信我都收到了,谢谢你,还有我爱你!
女孩收到男孩的回信之后,也很开心,因为发出的情书男孩收到了。由此男孩女孩双方都知道了彼此的心意,之后就快乐地交流起来了~~
所谓的三次握手即TCP连接的建立。这个连接必须是一方主动打开,另一方被动打开的。
(1)首先客户端向服务器端发送一段TCP报文,其中:
标记位为SYN,表示“请求建立新连接”;序号为Seq=X(X一般为1);随后客户端进入SYN-SENT阶段。
(2)服务器端接收到来自客户端的TCP报文之后,结束LISTEN阶段。并返回一段TCP报文,其中:
标志位为SYN和ACK,表示“确认客户端的报文Seq序号有效,服务器能正常接收客户端发送的数据,并同意创建新连接”(即告诉客户端,服务器收到了你的数据);序号为Seq=y;确认号为Ack=x+1,表示收到客户端的序号Seq并将其值加1作为自己确认号Ack的值;随后服务器端进入SYN-RCVD阶段。
(3)客户端接收到来自服务器端的确认收到数据的TCP报文之后,明确了从客户端到服务器的数据传输是正常的,结束SYN-SENT阶段。并返回最后一段TCP报文。其中:
标志位为ACK,表示“确认收到服务器端同意连接的信号”(即告诉服务器,我知道你收到我发的数据了);序号为Seq=x+1,表示收到服务器端的确认号Ack,并将其值作为自己的序号值;确认号为Ack=y+1,表示收到服务器端序号Seq,并将其值加1作为自己的确认号Ack的值;随后客户端进入ESTABLISHED阶段。服务器收到来自客户端的“确认收到服务器数据”的TCP报文之后,明确了从服务器到客户端的数据传输是正常的。结束SYN-SENT阶段,进入ESTABLISHED阶段。
在客户端与服务器端传输的TCP报文中,双方的确认号Ack和序号Seq的值,都是在彼此Ack和Seq值的基础上进行计算的,这样做保证了TCP报文传输的连贯性。一旦出现某一方发出的TCP报文丢失,便无法继续"握手",以此确保了"三次握手"的顺利完成。
七、四次挥手
1. 客户端发送断开TCP连接请求的报文,其中报文中包含seq序列号,是由发送端随机生成的,并且还将报文中的FIN字段置为1,表示需要断开TCP连接。(FIN=1,seq=x,x由客户端随机生成)
2. 服务端会回复客户端发送的TCP断开请求报文,其包含seq序列号,是由回复端随机生成的,而且会产生ACK字段,ACK字段数值是在客户端发过来的seq序列号基础上加1进行回复,以便客户端收到信息时,知晓自己的TCP断开请求已经得到验证。(FIN=1,ACK=x+1,seq=y,y由服务端随机生成)
3. 服务端在回复完客户端的TCP断开请求后,不会马上进行TCP连接的断开,服务端会先确保断开前,所有传输到A的数据是否已经传输完毕,一旦确认传输数据完毕,就会将回复报文的FIN字段置1,并且产生随机seq序列号。(FIN=1,ACK=x+1,seq=z,z由服务端随机生成)
4.客户端收到服务端的TCP断开请求后,会回复服务端的断开请求,包含随机生成的seq 字段和ACK字段,ACK字段会在服务端的TCP断开请求的seq基础上加1,从而完 成服务端请求的验证回复。(FIN=1,ACK=z+1,seq=h,h为客户端随机生成)
至此TCP断开的4次挥手过程完毕。
八、UDP通信
1. TCP与UDP
当使用网络套接字通信时,
套接字的“域”都取AF_INET;
套接字的type:
SOCK_STREAM 此时,默认使用TCP协议进行通信。
SOCK_DGRAM 此时,默认使用UDP协议进行通信。
TCP通信,是一个有序的、可靠的、面向连接的
UDP通信,是不保证有序到达的数据报服务。(在局域网内,使用UDP已很可靠)
2. 使用UDP通信
与TCP通信使用上的区别:
1)创建套接字时的type(参数2)不同。
TCP通信,使用SOCK_STREAM
UDP通信,使用SOCK_DGRAM
2)发送数据和接收数据时,使用的接口不同
TCP通信,发送数据,使用write(或send)
接收数据,使用read(或recv)
UDP特性,发送数据,使用sendto
接收数据,服务器端使用recvfrom
客户端使用recv
3)不需要使用listen
4)不需要先建立连接(TCP客户端和服务器端分别使用connect和receive建立连接)
步骤总结:
基于UDP的网络套接字通信
服务器端
(1) 创建一个网络套接字
(2) 设置服务器地址
(3) 绑定该套接字,使得该套接字和对应的端口关联起来
(4) 循环处理客户端请求
使用recvfrom等待接收客户端发送的数据
使用sendto发送数据至客户端
客户端
(1) 创建一个套接字
(2) 设置服务器地址
(3) 使用sendto向服务器端(接收端)发送数据
(4) 使用recv接受数据
3. sendto与recvfrom、recv
1) sendto
功能:UDP服务器或客户端用于发送数据
原型:int sendto (int sockfd, // 套接字
void *buff, // 发送缓存区
size_t len, // 发送缓冲区的长度
init flags, // 标志,一般取0
struct sockaddr *to, // 目的主机地址
socklen_t tolen // 目的主机地址长度
);
返回值:成功,返回实际发送数据的字节数
失败,返回-1
2) recvfrom
功能:UDP服务器用于接收数据
原型: 与sendto类似。
int recvfrom (int sockfd, // 套接字
void *buff, // 接收缓存区
size_t len, // 接受缓冲区的长度
init flags, // 标志,一般取0
struct sockaddr *to, // 源主机地址
socklen_t *tolen // 源主机地址长度
);
注意:参数6必须要初始化为对应地址的长度!
3) recv
功能:UDP客户端用于接收数据
原型: ssize_t recv (int sockfd, void *buf, size_t len, int flags);
注意: 该调用的参数不需要指定地址。
因为当使用udp时,对应的套接字被自动绑定在一个短暂的动态的端口上。
实例1:
服务器接收、客户端发送
client
#include <sys/un.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#define BUFF_SIZE 1024
int main(void)
{
int sockfd;
struct sockaddr_in server_addr;
int ret;
int c;
char buff[BUFF_SIZE];
// 创建一个套接字
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// 设置服务器地址
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr("10.10.0.9");
server_addr.sin_port = htons(9000);
// 向服务器发送数据
strcpy(buff, "hello world");
ret = sendto(sockfd, buff, strlen(buff) + 1, 0, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (ret == -1) {
perror("sendto");
exit(errno);
}
printf("ret = %d\n", ret);
return 0;
}
server
#include <sys/un.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#define BUFF_SIZE 1024
int main(void)
{
int server_sockfd;
int client_sockfd;
char ch;
int ret;
int recv_len;
char buff[BUFF_SIZE];
//用于UNIX系统内部通信的地址, struct sockaddr_un
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int client_addr_len =sizeof(struct sockaddr_in);
server_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// 设置服务器地址
server_addr.sin_family = AF_INET; //地址的域,相当于地址的类型, AF_UNIX表示地址位于UNIX系统内部
server_addr.sin_addr.s_addr = INADDR_ANY; //inet_addr("10.10.0.9");
server_addr.sin_port = htons(9000);
// 绑定该套接字,使得该套接字和对应的系统套接字文件关联起来。
ret = bind(server_sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (ret == -1) {
perror("bind");
exit(1);
}
// 创建套接字队列, 保存进入该服务器的客户端请求。
//ret = listen(server_sockfd, 5);
// 循环处理客户端请求
while (1) {
printf("server waiting\n");
// 等待并接收客户端请求
//client_sockfd = accept(server_sockfd, (struct sockaddr*)&client_addr, &client_addr_len);
recv_len = recvfrom(server_sockfd, buff, sizeof(buff) , 0,
(struct sockaddr*)&client_addr, &client_addr_len);
if (recv_len < 0) {
perror("recvfrom");
exit(errno);
}
printf("received: %s\n", buff);
}
close(server_sockfd);
return 0;
}
实例2:
服务器收发、客户方发送、接收。
client
#include <sys/un.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#define BUFF_SIZE 1024
int main(void)
{
int sockfd;
struct sockaddr_in server_addr;
int ret;
int c;
char buff[BUFF_SIZE];
socklen_t addr_len;
// 创建一个套接字
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// 设置服务器地址
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr("10.10.0.99");
server_addr.sin_port = htons(9000);
// 向服务器发送数据
strcpy(buff, "hello world");
ret = sendto(sockfd, buff, strlen(buff) + 1, 0, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (ret == -1) {
perror("sendto");
exit(errno);
}
printf("send %d bytes\n", ret);
ret = recv(sockfd, buff, sizeof(buff), 0);
if (ret == -1) {
perror("recvfrom");
exit(errno);
}
printf("received %d bytes\n", ret);
printf("received: %s\n", buff);
return 0;
}
server
#include <sys/un.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#define BUFF_SIZE 1024
static void str2up(char *str)
{
while(*str) {
if (*str >= 'a' && *str <= 'z') {
*str = *str - 'a' + 'A';
}
str++;
}
}
int main(void)
{
int server_sockfd;
int client_sockfd;
char ch;
int ret;
int recv_len;
int send_len;
char buff[BUFF_SIZE];
//用于UNIX系统内部通信的地址, struct sockaddr_un
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int client_addr_len = sizeof(struct sockaddr_in);
server_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// 设置服务器地址
server_addr.sin_family = AF_INET; //地址的域,相当于地址的类型, AF_UNIX表示地址位于UNIX系统内部
server_addr.sin_addr.s_addr = INADDR_ANY; //inet_addr("10.10.0.9");
server_addr.sin_port = htons(9000);
// 绑定该套接字,使得该套接字和对应的系统套接字文件关联起来。
ret = bind(server_sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (ret == -1) {
perror("bind");
exit(1);
}
// 创建套接字队列, 保存进入该服务器的客户端请求。
//ret = listen(server_sockfd, 5);
// 循环处理客户端请求
while (1) {
printf("server waiting\n");
// 等待并接收客户端请求
//client_sockfd = accept(server_sockfd, (struct sockaddr*)&client_addr, &client_addr_len);
recv_len = recvfrom(server_sockfd, buff, sizeof(buff) , 0,
(struct sockaddr*)&client_addr, &client_addr_len);
if (recv_len < 0) {
perror("recvfrom");
exit(errno);
}
printf("received: %s\n", buff);
str2up(buff);
send_len = sendto(server_sockfd, buff, strlen(buff)+1, 0,
(struct sockaddr*)&client_addr, client_addr_len);
if (send_len == -1) {
perror("sendto");
exit(errno);
}
//printf("send_len=%d\n", send_len);
}
close(server_sockfd);
return 0;
}