网络基础
为什么需要网络通信?
进程间通信解决的是本机内通信;
网络通信解决的是任意不同机器通信;
实现网络通信需要哪些支持?
1、通信设备
网卡:PC机自带
路由器,交换机
光纤、电缆、基站
2、通信协议
操作系统自带协议栈;(Linux的特点:丰富的网络协议)
裸机开发需要独立的协议栈
3、简单网络通信只需要学会使用系统API
tcp/ip协议栈
OSI七层网络模型、Linux四层网络模型
协议栈为什么要分层?
物理层 :通信介质的信号到数字信号(二进制0101)转换;
数据链接层 :局域网之间计算机通信 通过mac地址(物理网卡)通信;
网络层:IP地址,路由(通过公网ip来访问全世界);
传输层 :tcp/udp tcp(可靠,速度慢) udp(不可靠,速度快) 端口(让不同的应用程序,同时使用网络);
会话层 : 控制发包的数据;
表示层 : 文件格式;
应用层 : 应用程序使用;
协议栈实现数据的压缩加密和解压解密
IP地址
IP地址:实际上是32位二进制数(01100100.00000100.00000101.00000110)100.4.5.6
公网ip(广域网通信):
a类 1.0.0.1~126.255.255.254
b类 128.0.0.1~191.255.255.254
c类 192.0.0.1~223.255.255.254
d类 224.0.0.1—239.255.255.254组播,VRRP协议,keepalive高可用
e类 240.0.0.1—255.255.255.254 科研
私网ip(局域网通信)
A 10.0.0.0~10.255.255.255
B 172.16.0.0~172.31.255.255
C 192.168.0.0~192.168.255.255
如何解决IP地址荒?
①ipv4升级到ipv6
②网络地址转换NAT(Network Address Translation):将内网ip转为公网ip
子网掩码的作用
①决定一个网段的大小(可用ip的数量)
192.168.11.0/24 === 192.168.11.0 netmask 255.255.255.0
192.168.11.0 网络号
192.168.11.1 起始地址
192.168.11.254 结束地址
192.168.11.255 广播地址
8位可用:256-4=252
②同一个网段的ip地址,才能直接相互通信,不同网段的ip地址,需要路由器才能相互通信
DNS协议(域名解析):把域名解析成ip地址
ARP协议:把ip地址解析成mac地址(物理地址,全球唯一)(局域网通信必须通过MAC地址)
TCP协议:传输控制协议
特点:面向连接、可靠的字节流传输
TCP的报文段格式
端口号
端口号的作用:确定和机器上哪个应用程序通信
如何连接?
建立连接:三次握手(记录对方的ip和端口,正式通信时会自动的使用记录的ip和端口)
第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手
断开连接:四次挥手
1)客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
2)服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
3)客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
4)服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
5)客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
6)服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。
为什么是三次握手和四次挥手?
网络通信是全双工的
如何保证可靠有序的传输?
三次握手建立连接;
应答机制,也就是将数据发送给对方后,对方必须应答是否发送成功;
使用“滑动窗口”机制,根据网络的好坏,控制发送的分组数据的大小;
UDP协议:用户数据报协议
无连接不可靠传输
UPD的报文格式
协议特点:
无连接的:发送数据之前不需要建立连接,减少了开销和发送数据之前的延时。
尽最大努力交付:不保证可靠的交付,主机不需要维持复杂的链接状态表。
面向报文的:发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付给IP层。既不拆分,也不合并,而是保留这些报文的边界,因此,应用程序需要选择合适的报文大小。
没有拥堵控制;
支持一对一、多对一和多对多的交互通信;
TCP VS UDP总结对比:
网络编程
C/S VS B/S
C/S架构:
优点:
由于客户端实现与服务器的直接相连,没有中间环节,因此响应速度快;
操作界面漂亮、形式多样,可以充分满足客户自身的个性化要求。;
C/S结构的管理信息系统具有较强的事务处理能力,能实现复杂的业务流程。
缺点:
需要专门的客户端安装程序,分布功能弱,针对点多面广且不具备网络条件的用户群体,不能够实现快速部署安装和配置。
兼容性差,对于不同的开发工具,具有较大的局限性。若采用不同工具,需要重新改写程序。
开发成本较高,需要具有一定专业水准的技术人员才能完成。
B/S架构:
优点:
具有分布性特点,可以随时随地进行查询、浏览等业务处理;
业务扩展简单方便,通过增加网页即可增加服务器功能。
维护简单方便,只需要改变网页,即可实现所有用户的同步更新。
开发简单,共享性强;
缺点:
表现要达到CS程序的程度需要花费不少精力;
在速度和安全性上需要花费巨大的设计成本,这是BS架构的最大问题;
TCP服务器实现过程
创建套接字:socket函数
int socket(int domain, int type, int protocol);
头文件:#include <sys/types.h>
#include <sys/socket.h>
功能:创建一个套接字文件,然后以文件形式来操作通信,不过套接字文件没有文件名
参数:
①domian :族/范围
作用:指定协议族
AF_INET(IPV4:ip为32位 )
AF_INET6(IPV6:ip为128位)
②type:套接字类型,说白了就是进一步指定,你想要使用协议族中的那个子协议来通信。
type的常见设置值:
SOCK_STREAM:
TCP协议:将type指定为SOCK_STREAM时,表示想使用的是“有序的、面向连接的、双向通信的、可靠的字节流通信”
SOCK_DGRAM:
UDP协议:type指定为SOCK_DGRAM时,表示想使用的是“无连接、不可靠的、固定长度的数据报通信”
SOCK_RDM:
表示想使用的是“原始网络通信”。
比如,当domain指定为TCP/IP协议族、type指定为SOCK_RDM时,就表示想使用ip协议来通信,使用IP协议来通信,其实就是原始的网络通信。
SOCK_NONBLOCK:
将socket返回的文件描述符指定为非阻塞的。
如果不指定这个宏的话,使用socket返回“套接字文件描述符”时,不管是是用来“监听”还是用来通信,都是阻塞操作的,但是指定这个宏的话,就是非阻塞的。
SOCK_CLOEXEC:
表示一旦进程exec执行新程序后,自动关闭socket返回的“套接字文件描述符”。
返回值:
成功:返回套接字文件描述符。
失败:返回-1,errno被设置
给服务器套接字绑定ip地址和端口号:bind函数
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
头文件:#include <sys/socket.h>
功能:将指定了通信协议(TCP)的套接字文件与IP以及端口绑定起来。
参数:
①sockfd:
socket函数创建的套接字
②addr:struct sockaddr结构体变量的地址,结构体成员用于设置你要绑定的ip和端口。
结构体成员
struct sockaddr_in
{
sa_family_t sin_family; //设置AF_***(地址族)
__be16 sin_port; //设置端口号
struct in_addr sin_addr; //设置Ip
}
struct in_addr
{
__be32 s_addr; //__be32是32位的unsigned int,因为IPV4的ip是32位的无符号整形数
};
返回值:成功返回0,失败返回-1,errno被设置。
字节序转化:
网络字节序采用大端字节序
为什么要进行字节序转化?
网络通信的过程:
发送计算机 网络 接收计算机
主机端序(大) ————> 网络端序 —————> 主机端序(小)
发送计算机的端序与接收计算机的端序可能不一致,比如发送者是大端序,而接收者是小端序,如果通信时数据的端序处理不好,数据可能会出现乱码,甚至导致无法接收到数据。
所以发送数据时,先从发送者的主机端序转成统一的网络端序,接收计算机接收到后,再统一的转成接收计算机的主机端序,如此接收者才能以正确的端序接收数据,否者就会出错。
字节序转化函数:
ip地址格式转化:
addr.sin_addr.s_addr = inet_addr(“192.168.1.105”);//指定ip地址
③addrlen
第二个参数的长度
listen函数:
将套接字文件描述符,从主动变为被动文件描述符(做监听准备)
主动描述符可以主动的向对方发送数据。
被动描述符只能被动的等别人主动想你发数据,然后再回答数据,不能主动的发送数据。
为什么要将“套接字文件描述符”转为被动描述符后,才能监听连接?
TCP服务器和客户端必须要建立连接,建立连接时的三次握手是由客户端主动先发起的,也就是由客户端率先主动向服务器发送三次握手的数据,而服务器这一端则被动的接收,然后回答。
int listen(int sockfd, int backlog);
头文件:
#include <sys/types.h>
#include <sys/socket.h>
功能:将套接字文件描述符,从主动文件描述符变为被动描述符,然后用于被动监听客户的连接
参数:
sockfd:socket所返回的套接字文件描述符
backlog:指定队列的容量。
返回值:成功返回0,失败返回-1,ernno被设置
被动监听客户的连接并响应:accept函数
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
头文件:
#include <sys/types.h>
#include <sys/socket.h>
功能:被动监听客户发起三次握手的连接请求,三次握手成功,即建立连接成功。accept被动监听客户连接的过程,其实也被称为监听客户上线的过程。
对于那些只连接了一半,还未连接完成的客户,会被记录到未完成队列中,队列的容量由listen函数的第二个参数(backlog)来指定。
服务器调用accept函数监听客户连接,而客户端则是调用connect来连接请求的。一旦连接成功,服务器这边的TCP协议会记录客户的IP和端口,如果是跨网通信的,记录ip的就是客户所在路由器的公网Ip。
返回值:
成功:返回一个通信描述符,专门用于与该连接成功的客户的通信,总之后续服务器与该客户间正式通信,使用的就是accept返回的“通信描述符”来实现的。
失败:返回-1,errno被设置
参数:
sockefd:已经被listen转为了被动描述符的“套接字文件描述符”,专门用于被动监听客户的连接。
addr:用于记录发起连接请求的那个客户的IP和端口(port)
addrlen:第二参数addr的大小,不过要求给的是地址。
代码演示
struct sockaddr_in clnaddr = {0};
int clnaddr_size = sizeof(clnaddr)
cfd = accept(sockfd, (struct sockaddr *)&clnaddr, &clnaddr_size);
printf(“cln_port= %d, cln_addr=%s\n”, ntohs(clnaddr.sin_port), inet_ntoa(clnaddr.sin_addr));
服务器调用read(recv)和write(send),收发数据,实现与客户的通信
send函数:
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:向对方发送数据
头文件:#include <sys/types.h>
#include <sys/socket.h>
recv函数:
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能:接收对方发送的数据
头文件:#include <sys/types.h>
#include <sys/socket.h>
调用close或者shutdown关闭TCP的连接
int shutdown(int sockfd, int how);
TCP客户端的实现过程
1、用socket创建套接字文件,指定使用TCP协议
2、调用connect主动向服务器发起三次握手,进行连接
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
头文件:
#include <sys/types.h>
#include <sys/socket.h>
功能:向服务器主动发起连接请求
返回值:成功返回0,失败返回-1,ernno被设置
参数:
sockfd:socket所返回的套接字文件描述符
addrlen:参数2所指定的结构体变量的大小
addr:用于设置你所要连接服务器的IP和端口。
3、调用read(recv)和write(send)收发数据
4、调用close或者shutdown关闭连接
出现无法绑定?
client终止时自动关闭socket描述符,server的TCP连接收到client发的FIN段后处于TIME_WAIT状态。TCP协议规定,主动关闭连接的一方要处于TIME_WAIT状态,等待两个MSL(maximum segment lifetime)的时间后才能回到CLOSED状态,因为我们先Ctrl-C终止了server,所以server是主动关闭连接的一方,在TIME_WAIT期间仍然不能再次监听同样的server端口。MSL在RFC1122中规定为两分钟;
解决办法:setsockopt函数
最终程序结果: