1.网络是分层的
1)OSI 7层模型
2)网络为什么要分层
这个其实类似于程序的模块化思想,就如同我们将一个大的程序封装为一个个的函数实现。整个程序的框架结构不变,而具体逻辑操作交由不同的函数实现。函数的实现方法可以多样化,但实现的功能相同,对外提供的接口保持不变(这里,函数的接口则相当于各分层协议的标准)。当需要改变程序的某些功能实现时,通常我们只需针对具体的函数模块作调整,这样,不至于牵一发而动全身。
协议的标准化促进了更多的用户高效互联。
各层次的众多协议似乎都只有一个目的,就是不断地对其上层的数据按本层次的需求进行封装,然后再交由本层次的其他服务进行解读处理。这样不同的协议层各司其职,协同合作共同完成一份用户裸数据的转移操作。
3)网络分层的具体表现
2.TCP/IP协议引入
1)TCP/IP协议是用的最多的网络协议实现
2)TCP/ IP分为4层,对应OSI的7层
3)我们编程时最关注应用层,了解传输层,网际互联层和网络接入层不用管
3.BS和CS
1)CS架构介绍(client server,客户端服务器架构)
2)BS架构介绍(broswer server,浏览器服务器架构)
4.关于TCP理解的重点
1) TCP协议工作在传输层,对上服务socket接口,对下调用IP层
2)TCP协议面向连接,通信前必须先3次握手建立连接关系后才能开始通信。
3)TCP协议提供可靠传输,不怕丢包、乱序等。
5.TCP如何保证可靠传输
1)TCP在传输有效信息前要求通信双方必须先握手,建立连接才能通信
2)TCP的接收方收到数据包后会ack给发送方,若发送方未收到ack会丢包重传
3)TCP的有效数据内容会附带校验,以防止内容在传递过程中损坏
4)TCP会根据网络带宽来自动调节适配速率(滑动窗口技术)
5)发送方会给各分割报文编号,接收方会校验编号,一旦顺序错误即会重传。
6.TCP的三次握手
1)建立连接需要三次握手
2)建立连接的条件:服务器listen时客户端主动发起connect
工作过程
TCP标志位:
TCP共有6个标志位,分别是:
图解三次握手和四次挥手的过程:
三次握手建立连接阐述:
第一次握手:客户端要和服务端进行通信,首先要告知服务端一声,遂发出一个SYN=1的连接请求信号,”服务端哥哥,我想给你说说话”。
第二次握手:当服务端接收到客户端的连接请求,此时要给客户端一个确认信息,”我知道了(ACK),我这边已经准备好了,你现在能连吗(SYN)”。
第三次握手:当客户端收到了服务端的确认连接信息后,要礼貌的告知一下服务端,“好的,咱们开始联通吧(ACK)”。
到此整个建立连接的过程已经结束,接下来就是双方你一句我一句甚至同时交流传递信息的过程了。
四次挥手断开连接阐述:
第一次挥手:双方交流的差不多了,此时客户端也已经结尾了,接下来要断开通信连接,所以告诉服务端“我说完了(FIN)”,此时自身形成等待结束连接的状态。
第二次挥手:服务端知道客户端已经没话说了,服务端此时还有两句心里话要给客户端说,“我知道你说完了(ACK),我再给你说两句,&*……%¥”。
第三次挥手:此时客户端洗耳恭听继续处于等待结束的状态,服务器端也说完了,自身此时处于等待关闭连接的状态,并对告诉客户端,“我说完了,咱们断了吧(FIN)”。
第四次挥手:客户端收知道服务端也说完了,也要告诉服务端一声(ACK),因为连接和断开要双方都按下关闭操作才能断开,客户端同时又为自己定义一个定时器,因为不知道刚才说的这句话能不能准确到达服务端(网络不稳定或者其他因素引起的网络原因),默认时间定为两个通信的最大时间之和,超出这个时间就默认服务器端已经接收到了自己的确认信息,此时客户端就关闭自身连接,服务器端一旦接收到客户端发来的确定通知就立刻关闭服务器端的连接。
到此为止双方整个通信过程就此终结。这里要声明一下:断开链接不一定就是客户端,谁都可以先发起断开指令,另外客户端和服务端是没有固定标准的,谁先发起请求谁就是客户端。
问题:
1.为什么断开链接的时候客户端设置的定时器时间等待要2MSL(两个通信报文的最大时间)?
这个问题也很好理解,当客户端最终告诉服务器端断开确认的时候,他不知道自己的发出的指令是否能准确的一次性被服务器接收。假如服务器没有接收到(这已经耗费了一个报文的最大通信时间了),服务器端将会重新发起一个结束通话的指令(FIN)到客户端,客户端又接收到了服务器发来的结束通信指令将继续给服务器进行一个确认,有人会说那要是客户端发出的确认信息服务端没收到,而服务端重发的断开指令客户端也没收到怎么办,说实话我也无奈,遇到这种情况咱们干脆认为网确实不行了。
2.为什么建立连接要三次握手而断开连接要四次挥手?
说起这个,打一个比喻,目前祖国正在高速发展高铁,建立连接的过程正如上海到北京打通一条高铁线,TCP通信过程是一个全双工模式,即在这条高铁线上要有两个轨道,即能从上海发车到北京又能从北京发车到上海,甚至两边可以同时发车。所以断开连接前提就是要保证两条轨道都没有车,然后双方才能各自发起断开动作。
双方各自工作流程图:
客户端工作流程:
注意:在TIME_WAIT状态中,如果TCP client端最后一次发送的ACK丢失了,它将重新发送。TIME_WAIT状态中所需要的时间是依赖于实现方法的。典型的值为30秒、1分钟和2分钟。等待之后连接正式关闭,并且所有的资源(包括端口号)都被释放。
服务器端工作流程:
附加
SYN网络攻击:
原理:
在三次握手过程中,Server发送SYN-ACK之后,收到Client的ACK之前的TCP连接称为半连接(half-open connect),此时Server处于SYN_RCVD状态,当收到ACK后,Server转入ESTABLISHED状态。SYN攻击就是Client在短时间内伪造大量不存在的IP地址,并向Server不断地发送SYN包,Server回复确认包,并等待Client的确认,由于源地址是不存在的,因此,Server需要不断重发直至超时,这些伪造的SYN包将产时间占用未连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络堵塞甚至系统瘫痪。
检测:
SYN攻击时一种典型的DDOS攻击,检测SYN攻击的方式非常简单,即当Server上有大量半连接状态且源IP地址是随机的,则可以断定遭到SYN攻击了。windows下打开cmd,输入命令:”netstat -n -p TCP“,查看是否有大量的”SYN_RECEIVED“状态。
7.TCP的四次握手
1)关闭连接需要四次握手
2)服务器或者客户端都可以主动发起关闭
注:这些握手协议已经封装在TCP协议内部,socket编程接口平时不用管
8.基于TCP通信的服务模式
1)具有公网IP地址的服务器(或者使用动态IP地址映射技术)
2)服务器端socket、bind、listen后处于监听状态
3)客户端socket后,直接connect去发起连接。
4)服务器收到并同意客户端接入后会建立TCP连接,然后双方开始收发数据,收发时是双向的,而且双方均可发起
5)双方均可发起关闭连接
9.常见的使用了TCP协议的网络应用
1)http、ftp
2)QQ服务器
3)mail服务器
10.socket编程接口介绍
10.1.建立连接
1)socket。socket函数类似于open,用来打开一个网络连接,如果成功则返回一个网络文件描述符(int类型),之后我们操作这个网络连接都通过这个网络文件描述符。
2)bind----绑定,对一些属性的修改,
3)listen
4)connect
10.2发送和接收
1)send和write
2)recv和read
10.3辅助性函数:完成地址转换
1)inet_aton、inet_addr、inet_ntoa
2)inet_ntop、inet_pton------------------n指网络32位二进制,p表示点分十进制格式
10.4表示IP地址相关数据结构
1)都定义在 netinet/in.h
2)struct sockaddr,这个结构体是网络编程接口中用来表示一个IP地址的,注意这个IP地址是不区分IPv4和IPv6的(或者说是兼容IPv4和IPv6的)
3)typedef uint32_t in_addr_t; 网络内部用来表示IP地址的类型
4)struct in_addr
{
in_addr_t s_addr;
};
5)struct sockaddr_in-----------------用来封装IPV4的
{
__SOCKADDR_COMMON (sin_);
in_port_t sin_port; /* Port number. */
struct in_addr sin_addr; /* Internet address. */
/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};
(6)struct sockaddr这个结构体是linux的网络编程接口中用来表示IP地址的标准结构体,bind、connect等函数中都需要这个结构体,这个结构体是兼容IPV4和IPV6的。在实际编程中这个结构体会被一个struct sockaddr_in或者一个struct sockaddr_in6所填充。
11.IP地址格式转换函数实践
11.1 inet_addr、inet_ntoa、inet_aton
#include
#include
#include
#include
#define IPADDR "192.168.0.1"
int main(void)
{
in_addr_t addr;
addr = inet_addr(IPADDR);
printf("addr = 0x%x.\n", addr);
return 0;
}
点分十进制表示的格式便于观看,而16进制格式不便于观看。而且16进制的格式采用的是大端格式。
11.2 inet_pton、inet_ntop
#define IPADDR "192.168.0.1"
int main(void)
{
int ret = -1;
const char *s;
char buf[15];
struct in_addr addr = {0};
//点分十进制转16进制
ret = inet_pton(AF_INET, IPADDR, &addr);
if (ret != 1)
{
printf("inet_pton eror!.\n");
}
printf("16 进制 addr = 0x%x.\n", addr.s_addr);
//16进制转成点分十进制
s = inet_ntop(AF_INET, &addr, buf, sizeof(buf));
if(NULL == s)
{
printf("inet_ntop error!.\n");
}
printf("10 进制 addr = %s.\n", s);
return 0;
}
12 服务器端程序编写
1)socket
2)bind
3)listen
4)accept,返回值是一个fd,ac
5)accept正确返回就表示我们已经和前来连接我的客户端之间建立了一个TCP连接了,以后我们就要通过这个连接来和客户端进行读写操作,读写操作就需要一个fd,这个fd就由accept来返回了。
注意:socket返回的fd叫做监听fd,是用来监听客户端的,不能用来和任何客户端进行读写;accept返回的fd叫做连接fd,用来和连接那端的客户端程序进行读写。
#include
#include
#include
#include
#include
#define SERVERPORT 9003
#define SERVERADDR "192.168.1.3"
#define BACKLOG 100
int main(void)
{
int socketfd = -1;
int ret = -1;
socklen_t len;
struct sockaddr_in serveraddr = {0};
struct sockaddr_in clientaddr = {0};
//第一步:打开socket文件描述符
socketfd = socket(AF_INET, SOCK_STREAM, 0);
if(socketfd < 0)
{
printf("socket error!.\n");
return -1;
}
printf("socketfd = %d.\n", socketfd);
//第二步:绑定socketfd和当前电脑的ip地址和端口号
serveraddr.sin_family = AF_INET; //设置地址族为IPV4
serveraddr.sin_port = htonl(SERVERPORT);//设置端口号信息
serveraddr.sin_addr.s_addr = inet_addr(SERVERADDR);
ret = bind(socketfd, (const struct sockaddr *)&serveraddr, sizeof(serveraddr));
if(ret < 0)
{
printf("bind error!.\n");
return -1;
}
printf("bind sucess!.\n");
//第三步:监听端口
ret = listen(socketfd, BACKLOG);//阻塞等待客户端来连接服务器
if(ret < 0)
{
printf("listen error!.\n");
return -1;
}
printf("listen sucess!.\n");
//第四步:阻塞等待客户端接入
ret = accept(socketfd, (struct sockaddr *)&clientaddr, &len);
if(ret < 0)
{
printf("accept error!.\n");
return -1;
}
printf("accept sucess! ret = %d.\n", ret);
return 0;
}
13客户端程序编写
1)socket
2)connect
#include
#include
#include
#include
#include
#define SERADDR "192.168.1.3"
//服务器开放给我们的地址和端口号
#define SERVERPORT 9003
int main(void)
{
int sockfd = -1;
int ret;
struct sockaddr_in seraddr = {0};
//第一步:打开socket文件描述符
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
printf("socket error!.\n");
return -1;
}
printf("socket success! sockfd = %d.\n", sockfd);
//第二步:连接服务器
seraddr.sin_family = AF_INET; //设置地址族为IPV4
seraddr.sin_port = htonl(SERVERPORT);//设置端口号信息
seraddr.sin_addr.s_addr = inet_addr(SERADDR);
printf("seraddr.sin_port = %d.\n", seraddr.sin_port);
printf("seraddr.sin_addr.s_addr = 0x%x.\n", seraddr.sin_addr.s_addr);
ret=connect(sockfd,(const struct sockaddr*)&seraddr,sizeof(seraddr));
if(ret < 0)
{
printf("connect error!.\n");
return -1;
}
printf("connect success!.\n");
return 0;
}
概念:端口号,实质就是一个数字编号,用来在我们一台主机中(主机的操作系统中)唯一的标识一个能上网的进程。端口号和IP地址一起会被打包到当前进程发出或者接收到的每一个数据包中。每一个数据包将来在网络上传递的时候,内部都包含了发送方和接收方的信息(就是IP地址和端口号),所以IP地址和端口号这两个往往是打包在一起不分家的。
14.客户端发送&服务器接收
客户端代码:
#include
#include
#include
#include
#include
#include
#define SERPORT 6013
#define SERADDR "192.168.1.3"//要设置为服务器的IP地址
#define CMD_REGISTER 1
#define STAT_OK 0
#define STAT_ERR 1
typedef struct FORMAT
{
char name[20];
int age;
int cmd;
int stat;
}info;
char sendbuf[100];
char recvbuf[100];
int main(void)
{
//第一步:socket函数,获取网络连接的文件描述符
int sockfd=-1;
int ret=-1;
struct sockaddr_in seraddr={0};
sockfd=socket(AF_INET,SOCK_STREAM,0);
if(-1==sockfd)
{
perror("socket error:");
return -1;
}
printf("socket success.socket=%d.\n",sockfd);
//第二步:connect函数,连接服务器
seraddr.sin_family=AF_INET;//定义ip类型,IPV4还是IPV6
seraddr.sin_port=htons(SERPORT);
seraddr.sin_addr.s_addr=inet_addr(SERADDR);
ret=connect(sockfd,(const struct sockaddr*)&seraddr,sizeof(seraddr));
printf("连接成功\n");
/*
while(1)
{
//第三步:send函数,客户端给服务器发送信息
printf("请输入你想发给服务器的内容:\n");
scanf("%s",sendbuf);
ret=send(sockfd,sendbuf,strlen(sendbuff),0);
//第四步:客户端接收服务器端的回复
memset(recvbuf,0,sizeof(recvbuf));
ret=recv(sockfd,recvbuf,sizeof(recvbuf),0);
printf("服务器回复内容是:%s\n",recvbuf);
//第五步:客户端解释服务器的回复,再做下一步定夺
}
*/
info st1;
while(1)
{
printf("\n请输入学生姓名:\n");
scanf("%s",st1.name);
printf("\n请输入学生年龄:\n");
scanf("%d",&st1.age);
st1.cmd=CMD_REGISTER;
ret=send(sockfd,&st1,sizeof(info),0);
//第四步:客户端接收服务器端的回复
memset(&st1,0,sizeof(st1));
ret=recv(sockfd,&st1,sizeof(st1),0);
if(STAT_OK==st1.stat)
{
printf("注册学生信息成功!");
}
else
printf("注册学生信息失败!");
}
return 0;
}
15.服务器发送&客户端接收
服务器代码:
#include
#include
#include
#include
#include
#define MYPORT 6013
#define SERADDR "192.168.1.3"//要设置为服务器的IP地址
#define BLEN 100
#define STAT_OK 0
#define STAT_ERR 1
#define CMD_REGISTER 1
typedef struct FORMAT
{
char name[20];
int age;
int cmd;
int stat;
}info;
char recvbuf[100];
int main(void)
{
int sockfd=-1;
int ret=-1;
int clifd=-1;
socklen_t len=0;
struct sockaddr_in seraddr={0};//也可以使用memset函数进行初始化
struct sockaddr_in clientaddr={0};
//第一步:socket函数,获取网络连接的文件描述符
sockfd=socket(AF_INET,SOCK_STREAM,0);
if(-1==sockfd)
{
perror("socket error:");
return -1;
}
printf("socket success!socket=%d.\n",sockfd);
//第二步:bind函数,绑定sockfd与服务器的ip地址、端口号
//填充seraddr这个结构体
seraddr.sin_family=AF_INET;//定义ip类型,IPV6还是IPV4
seraddr.sin_port=htons(MYPORT);
seraddr.sin_addr.s_addr=inet_addr(SERADDR);
ret=bind(sockfd,(const struct sockaddr*)&seraddr,sizeof(seraddr));//类型不一样,会警告
if(ret==-1)
{
perror("bind error:");//函数本身有错误号时可以用perror来显示错误信息
}
printf("bind success!\n");
//第三步:listen函数,监听服务器的当前端口(其他端口不监听)
ret=listen(sockfd,BLEN);//第二个参数是服务器允许的队列长度,比如最多100号
if(ret==-1)
{
perror("listen error:");
}
printf("listen success!\n");
//第四步:accept函数,阻塞等待用户连接
//注意accept返回值是网络文件描述符,和sockfd不同,它才是真正的用于发送数据的fd,而sockfd只是用于侦听的。
clifd=accept(sockfd,(struct sockaddr*)&clientaddr,&len);
printf("用户连接成功!\n");
/*
while(1)
{
//第五步:recv函数,服务器开始接收数据
ret=recv(clifd,recvbuf,sizeof(recvbuf),0);
printf("client发送的内容是:%s\n",recvbuf);
memset(recvbuf,0,sizeof(recvbuf));
//第六步:服务器解释数据包
//第七步:回复客户端OK
ret=send(clifd,"OK",2,0);
}
*/
while(1)
{
info st;
ret=recv(clifd,&st,sizeof(st),0);
if(CMD_REGISTER==st.cmd)
{
printf("用户要注册学生信息\n");
printf("姓名:%s,年龄:%d\n",st.name,st.age);
st.stat=STAT_OK;
ret=send(clifd,&st,sizeof(info),0);
}
}
return 0;
}
16.探讨:如何让服务器和客户端好好沟通
1)客户端和服务器原则上都可以任意的发和收,但是实际上双方必须配合:client发的时候server就收,而server发的时候client就收
2)必须了解到的一点:client和server之间的通信是异步的,这就是问题的根源
3)解决方案:依靠应用层协议来解决。说白了就是我们server和client事先做好一系列的通信约定
17.自定义应用层协议第一步:规定发送和接收方法
1)规定连接建立后由客户端主动向服务器发出1个请求数据包,然后服务器收到数据包后回复客户端一个回应数据包,这就是一个通信回合
2)整个连接的通信就是由N多个回合组成的。
18.自定义应用层协议第二步:定义数据包格式
19.常用应用层协议:http、ftp••••••
59.4 UDP简介