一:TCP、UDP的区别
- TCP---传输控制协议,提供的是面向连接、可靠的字节流服务。当客户和服务器彼此交换数据前,必须现在双方之间建立一个TCP连接,之后才能传输数据
- UDP---用户数据报协议,是一个简单的面向数据报的运输层协议,UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地
二:网络编程基础知识
-
网络分层
-
OSI参考模型
- 物理层:数据转换
- 数据链路层:保证数据是正确传输(校验)
- 网络层:确认主机(IP地址)
- 传输层:TCP、UDP协议
- 会话层:socket编程
- 表示层:socket编程
- 应用层:socket编程
-
TCP/IP参考模型(各层对应协议)
- 应用层:http、ftp、telnet、tftp(UDP)【属于上面的:应用层、表示层、会话层】
- 传输层:tcp、udp
- 网络层:ip、icmp、igmp
- 网络接口层:arp、rarp【属于上面的:数据链路层、物理层】
-
-
网络程序设计模式:
-
C/S(客户端与服务器)
- QQ、3D(网络要求相对低,可以预先本地化),在本地安装程序,安全隐患,更新麻烦,需要维护客户端和服务器
-
B/S(浏览器与服务器)
- 网站,h5(不需要安装本地程序,更新方便,维护方便只需要维护服务器),网络要求高、3d效果的显示
-
-
数据包传输,协议格式
- 网络传输格式:[Ethernet header:主要包含14字节mac地址] + [IP header:主要包含20字节IP地址] + [TCP header:20字节,主要包含端口号和校验码] + [2字节App header] + [User data(0~1458字节)] + [Ethernet trailer]
- 一帧网络数据大小:46~1500字节,数据过大会进行分包
- IP header数据格式:
- 4位版本+4位首部长度+8位服务类型(TOS)+16位总长度(字节数)
- 16位标识+3位标志+13位片偏移
- 8位生存时间(TTL)+8位协议+16位首部校验和
- 32位源IP地址
- 32位目的IP地址
- 选项(如果有)
- 数据
- TCP header数据格式:
- 16位源端口号+16位目标端口号:出口进程号+入口进程号
- 32位序列号:每个包的序列号
- 32位确认号:回复的确认码
- 4位首部长+6位保留+[URG]+[ACK]+[PSH]+[RST]+[SYN]+[FIN]+16位窗口大小
- 16位校验和+16位紧急指针
- 可选项
- 数据
- UDP header数据格式:
- 16位源端口号+16位目标端口号:
- 16位UDP长度+16位UDP校验和
- 数据(若有)
-
端口号:用来确定系统中的进程
-
存储位置
- 存储在传输层,TCP/UDP头中,占两个字节
-
一些常用固定的端口号:所以我们建议使用5000以后的端口号
- 80:http协议
- 443:https协议
- tftp协议:69
- 21:ftp协议
-
-
IP地址
-
IP地址分类
- A类地址:[0+7位网络号] + [24位主机号]:网络号范围:0~127
- B类地址:[10+14位网络号] + [16位主机号]:网络号范围:128~191
- C类地址:[110+21位网络号] + [8位主机号]:网络号范围:192~223
- D类地址:[1110] + [28位多播地址]:网络号范围:224~239
- E类地址:[1111] + [28位保留今后使用]
-
-
字节序
- 举例(0x12345678):数据高位:0x12, 数据低位:0x78
- 大端存储:低位地址存储的是最高位数据,高地址存储的是低位数据(网络通信)
- 小端存储:低位数据存储低位数据,高位地址存储高位数据(大多为主机)
-
IP和域名:域名就是IP地址的代替名称
- 域名服务器DNS:保存域名对应的IP地址
-
套接字编程(应用层,以linux系统举例)
-
TCP协议通信:面向连接的传输层协议,能提供高可靠性通信,但是占用带宽高,实时性较差
-
TCP黏包问题
- 可以通过发送固定数据大小的封包数据:先发数据头(里面包含数据大小),先接收数据头,从头中解析数据大小,根据大小接收数据
-
创建服务器端
- 创建套接字socket():参数:地址族(IPV4/IPV6),数据类型(数据流/数据包/原始套接字[自定义底层协议头]),协议(TCP协议/UDP协议);返回值:创建的服务器套接字
- 绑定bind():参数:服务器套接字,服务器地址信息结构体(地址族、IP地址、端口号),结构体长度
- 监听listen():参数:服务器套接字,连接的客户端数量
- 接受连接accept():参数:服务器套接字,存储当前连接客户端的地址信息结构体,结构体长度;返回值:和客户端通信的套接字
- 接收数据recv()/read():参数:客户端通信套接字,接收数据的内存地址,接收数据的大小;recv()函数多了一个标志位参数(一般为0)
- 发送数据send()/write():参数:客户端通信套接字,发送数据的内存地址,发送数据的大小;write()函数多了一个标志位参数(一般为0)
- 关闭服务器close():关闭服务器TCP通信
-
创建客户端:
- 创建套接字socket():参数:地址族(IPV4/IPV6),数据类型(数据流/数据包/原始套接字[自定义底层协议头]),协议(TCP协议/UDP协议);返回值:创建的客户端套接字
- 连接服务器connect():参数:客户端套接字,服务器地址信息结构体(地址族、客户端自己的IP地址、端口号),结构体长度
- 发送数据send()/write():参数:客户端套接字,发送数据的内存地址,发送数据的大小;write()函数多了一个标志位参数(一般为0)
- 接收数据recv()/read():参数:客户端套接字,接收数据的内存地址,接收数据的大小;recv()函数多了一个标志位参数(一般为0)
- 关闭客户端close():关闭客户端TCP通信
-
TCP的三次握手
- 第一次:客户端向服务器发送一次连接请求(seq = 0)
- 第二次:服务器回复客户端的连接请求,并且向客户端发送一次连接请求(seq = 0 ack = 1)
- 第三次:客户端回应服务器的连接请求,并且建立连接(seq = 1 ack = 1)
-
TCP的四次挥手(此处以客户端先发起断开连接为例)
- 第一次:客户端向服务器发送一次断开请求(FIN)
- 第二次:服务器应答客户端的请求(此时客户端还不能关闭)
- 第三次:服务器向客户端发起关闭请求(服务器此时也可以关闭)
- 第四次:客户端回应服务器请求(此时客户端可以关闭)
-
-
UDP通信协议:不可靠传输协议,实时性高,带宽要求较低
-
UDP接收数据包可能是无序的问题
- 在广域网中传输时,因为有多条传输线路,所以可能会导致传输的结果是无序的。解决方法:在其数据包的前面加上编号,接收到的数据按编号排序(或者在应用层做校验,模仿TCP通信)
-
创建服务器端
- 创建套接字socket():参数:地址族(IPV4/IPV6),数据类型(数据流/数据包/原始套接字[自定义底层协议头]),协议(TCP协议/UDP协议);返回值:创建的服务器套接字描述符
- 绑定bind():参数:服务器套接字,服务器地址信息结构体(地址族、IP地址、端口号),结构体长度
- 接收数据recvfrom():套接字描述符,存储接收的数据缓冲区,缓冲区长度,标志设置为0,存储发送端的IP地址和端口,地址端口结构体长度
- 发送数据sendto():套接字描述符,要发送的数据长度,标志设置为0,接收数据的IP地址和端口,地址端口结构体长度
-
创建客户端
- 创建套接字socket():
- 发送数据sendto():套接字描述符,要发送的数据长度,标志设置为0,接收数据的IP地址和端口,地址端口结构体长度
- 接收数据recvfrom():套接字描述符,存储接收的数据缓冲区,缓冲区长度,标志设置为0,不用存储发送端的IP地址和端口(设置为NULL),不用地址端口结构体长度(设置为NULL)
-
UDP广播:
- 广播只允许在局域网中使用,广域网会形成网络风暴
- Linux广播是默认关闭的
-
开启广播:
- 配置网络属性setsockopt():参数:套接字,级别(SOL_SOCKET),级别对应的操作名称(SO_BROADCAST开启或关闭广播/SO_REUSEADDR开启或关闭地址复用),操作数(开关量:1表示开,0表示关),操作数长度
- 获取网络属性getsockopt():参数:套接字,级别(SOL_SOCKET),级别对应的操作名称(SO_BROADCAST开启或关闭广播/SO_REUSEADDR开启或关闭地址复用),操作数(开关量:1表示开,0表示关),操作数长度
- UDP发送数据方把地址改成广播地址(即主机号改成255),即可发送广播数据
-
UDP组播:
-
组播地址为D类地址(1110开头)
- 224.0.0.0~224.0.0.255:为预留的组播地址(永久组地址),地址224.0.0.0保留不做分配,其他地址共路由协议使用
- 224.0.1.0~224.0.1.255:是公用组播地址,可以用于Internet,欲使用需申请
- 224.0.2.0~238.255.255.255:为用户可用的组播地址(临时组地址),全网范围内有效
- 239.0.0.0~239.255.255:为本地管理组播地址,仅在特定的本地范围有效
-
组播通信
-
发送方
- 创建套接字
- 向组播地址发送数据
-
接收方
- 创建套接字
- 此步骤非必须:(为了测试,此处可以开启地址复用:setsockopt(套接字,SOL_SOCKET,,SO_REUSEADDR,&on, sizeof(on)))
- 绑定地址端口bind():参数:服务器套接字,服务器地址信息结构体(地址族、IP地址、端口号),结构体长度
- 添加到组播组setsockopt():套接字,级别(IPPROTO_IP),操作名(IP_ADD_MEMBERSHIP),操作值结构体(组播地址,本地地址),结构体长度
- 退出到组播组setsockopt():套接字,级别(IP_DROP_MEMBERSHIP),操作值结构体(组播地址,本地地址),结构体长度
-
-
-
多路复用:同时监听多个套接字文件符
-
select多路复用:
- select函数:参数:最大文件描述符+1,读文件描述符集合,写文件描述符集合,异常文件描述符集合,设置超时(为NULL则一直等待文件描述符有数据)
- 清除掉指定的文件描述符FD_CLR():要清除的文件描述符,装有文件描述符的容器
- 判断容器中是否有数据FD_ISSET():文件描述符,装有文件描述符的容器
- 将文件描述符添加到容器中:文件描述符,装有文件描述符的容器
- 清空容器中的文件描述符:FD_ZERO():装有文件描述符的容器
-
epoll多路复用:适用于更多客户端的情况
- 创建epoll句柄epoll_create():参数:监听文件描述符的个数
- 将文件描述符添加到epoll监听队列中epoll_ctl():参数:epoll文件描述符,操作(EPOLL_CTL_ADD添加/EPOLL_CTL_MOD修改/EPOLL_CTL_DEL删除),要添加的文件描述符,要添加事件的结构体(事件[EPOLLIN对应事件可读/EPOLLOUT表示对应文件描述符可写/EPOLLPRI表述对应描述符有紧急数据可读/EPOLLERR表示对应文件描述符发生了错误/EPOLLHUP表示对应文件描述符被挂断/EPOLLET设置成边缘触发模式],数据联合体[void */int/uint32_t/uint64_t类型数据])
- 监听-阻塞epoll_wait():epoll事件描述符,存放事件的容器,容器大小,超时时间; 返回值:返回当前发生事件文件描述符个数
-
-
-