网络编程
概念
TCP:用来检测网络传输中差错的传输控制协议,可靠传输协议
IP:专门负责对不同网络进行互联的互联网协议IP,不可靠传输协议
网络采用分层的思想:
- 每一层实现不同的功能,对上层的数据做透明传输
- 每一层向上层提供服务,同时使用下层提供的服务
两种体系结构:
OSI | TCP/IP | 层级 |
---|---|---|
应用层 | 应用层 | 高层 |
表示层 | ||
会话层 | ||
传输层 | 传输层 | 底层(内核) |
网络层 | 网络层 | |
数据链路层 | ||
物理层 | 网络接口与物理层 |
网络接口与物理层(Link Layer):屏蔽硬件差异(驱动),提供统一的接口,使用struct net_device
描述底层设备
网络层(Internet Layer):端到端的传输(从设备到设备),也叫做IP层
传输层(Transport Layer):决定数据包应该交给哪一个任务处理
应用层(Application Layer):协议层,有FTP、HTTP、DNS等协议
各层日常需要掌握的协议:
- 网络接口与物理层:
- MAC地址:48位全球唯一,网络设备的身份标识
- ARP/RARP:ARP:IP地址---->MAC地址, RARP:MAC地址---->IP地址
- PPP协议:拨号协议
- 网络层:
- IP协议(分为IPv4和IPv6)
- ICMP:Internet管理控制协议
- IGMP:Internet分组管理协议
- 传输层:
- TCP:提供面向连接的,一对一的可靠数据传输的协议
- UDP:提供不可靠,无连接的尽力传输协议,但是效率高
- SCTP:TCP增强版,提供面向连接的,多对一或多对多的可靠传输协议
- 应用层:
- 网页访问协议:HTTP/HTTPS
- 邮件收发协议:POP3(收)/SMTP(发)、IMAP(可接受邮件的一部分)
- FTP
- Telnet/SSH:远程登陆
- NTP:网络时钟协议
- SNMP:简单网络管理协议(实现对网络设备的集中式管理)(开源)
- RTP/RTSP:用传输音视频的协议(安防监控)
可靠:数据无误,数据无丢失,数据无失序,数据无重复到达的通信
网络的封包和拆包:
- 封包:
- 应用层有一个数据
data
, - 传输层依照协议给数据加一个头
TCP头|data
- 网络层依照协议给数据加一个头
IP头|TCP头|data
- 网络接口与物理层给数据加一个头和一个尾
以太网头|IP头|TCP头|data|CRC验证
,此时组成了一个完整的包
- 应用层有一个数据
- 拆包:
- 和封包反序,中间路由器会替换包头
以太网头 | IP头 | TCP头 | data | CRC验证 |
---|---|---|---|---|
14字节 | 20字节 | 20字节 | 1460字节 | 4字节 |
CRC是硬件产生和校验的
MTU:Max Transfer Unit,最大传输单元,与网络类型有关,以太网MTU = 1500
MSS:MAXIUM Segmum Size,真正的用户数据有多大,以太网MSS = 1460
预备知识
socket IP地址 端口号 字节序
Socket:
- socket是一个编程的接口(用户态与内核态之间),是一种特殊的文件描述符(可对其执行IO操作函数,比如:read(),write())
- 代表网络编程的一种资源
socket类型:
- 流式套接字(SOCK_STREAM):唯一对应TCP
- 数据报套接字(SOCK_DGRAM):唯一对应UDP
- 原始套接字(SOCK_RAW):对应多个协议,传输时穿透传输层,可以对低层次协议直接访问,如:IP、ICMP
IP地址
IP地址分为IPv4和IPv6,IPv4最终都会转换为32位二进制数来表示
- IPv4:采用32位二进制数来表示 点分形式:192.168.1.1
- IPv6:采用了128位二进制数来表示
- mobileIPv6:local IP(本地注册的IP), roam IP(漫游IP)
- 特殊IP:
- 局域网IP:192.XXX.XXX.XXX 10.XXX.XXX.XXX
- 广播IP:XXX.XXX.XXX.255 255.255.255.255(全网广播)
- 组播IP:224.XXX.XXX.XXX~239.XXX.XXX.XXX
NAT 转发包??
端口号
区分一台主机收到数据包应该转交给哪个任务
- 16位二进制数字(1-65535)
- 已用端口:1~1023(FTP: 21, SSH: 22, HTTP: 80, HTTPS: 469),不建议使用
- 保留端口:1024~5000(不建议使用)
- 可以使用:5000~65535
- UTP端口与TCP端口独立
网络中的通信是IP地址+端口号来决定
IP数据流走向图解 ??
字节序
字节序是指不同的CPU访问内存中的多字节数据的时候,存在字节序的问题(大小端的问题),访问字符不存在大小端问题
0x12345678
高端-----低端
内存高端 | 内存低端 | 字节序 |
---|---|---|
1234 | 5678 | 大端 |
5678 | 1234 | 小端 |
一般来说:
- X86/ARM:小端
- powerpc/mips,ARM作为路由器:大端模式
网络传输的时候采用大端模式
本地字节序、网络字节序:
- 传输时,在网络中间传输时使用网络字节序,在两端需要分别转换位本地字节序
- 本地字节序转网络字节序:
u_long htonl(u_long hostlong);
h host本地 n network网络 ulong 4字节u_short htons(u_short short);
short 2字节
- 网络字节序转本地字节序:
u_long ntohl(u_long hostlong);
u_short ntohs(u_short short);
IP地址的转换:
in_addr_t inet_addr(const char *cp);
- cp是点分形式的IP地址,结果是32位二进制数,内部包含字节序转换
- 仅用于IPv4
- 出错返回-1
- 此函数不能用于255.255.255.255的转换
int inet_pton(int af, const char *src, void *dst);
- 适用于IPv4和IPv6
- 能正确处理255.255.255.255的转换问题
- 参数:
- af:地址协议族(AF_INET或AF_INET6)
- src:p是一个指针src(填写点分形式的IP地址【IPv4】或以冒号分隔的IP地址【IPv6】)
- dst:转换的结果给到dst
- 返回1成功
TCP编程的API
socket() bind() listen() accept() connect()
socket()
函数原型:int socket(int domain, int type, int protocol)
函数头文件:
- #include <sys/types.h> /* See NOTES */
- #include <sys/socket.h>
函数功能:创建一个主动套接字
函数参数:
- domain:
- AF_INET IPv4 Internet protocols ip(7)
- AF_INET6 IPv6 Internet protocols ipv6(7)
- AF_LOCAL Local communication unix(7)
- AF_NETLINK Kernel user interface device netlink(7)
- AF_PACKET Low level packet interface packet(7)
- type:
- SOCK_STREAM
- SOCK_DGRAM
- SOCK_RAW
- protocol:一般填0,原始套接字编程时需要填充
函数返回值:成功返回文件描述符,失败返回-1
bind()
函数原型:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
函数头文件:
- #include <sys/types.h> /* See NOTES */
- #include <sys/socket.h>
函数功能:为套接字绑定IP信息
函数参数:
- sockfd:通过socket()函数拿到的fd
- addr:采用struct sockaddr的结构体变量的地址
- addrlen:地址的长度
通用结构体:
struct sockaddr {
sa_family_t sa_family; //2字节
char sa_data[14]; //14字节
}
基于Internet通信的结构体
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET 2字节*/
in_port_t sin_port; /* port in network byte order 2字节*/
struct in_addr sin_addr; /* internet address 4字节*/
//sin_zero[8] 填充8字节
};
/* Internet address. */
struct in_addr {
uint32_t s_addr; /* address in network byte order 4字节*/
};
sin_addr可以设置为INADDY_ANY,使得服务器端可以在任意IP上运行,从本机任意网卡接收信息
实际使用的时候用sockaddr_in类型,然后将sockaddr_in强转成sockaddr类型,填入函数
//绑定bind
//2.1填充sockaddr_in结构体
bzero(&sin, sizeof(sin));//清空sin
sin.sin_family = AF_INET;
#if 0
sin.sin_port = htons(SERV_PORT);//将本地字节序的端口号转变为网络字节序的端口号
#else
sin.addr.s_addr = inet_addr(SERV_IP_ADDR);//IPv4的IP地址转换
int ret = inet_pton(AF_INET, SERV_IP_ADDR, (void *)&sin.sin_addr_s_addr);
if (ret != 1)
{
perror("inet_pton error");
exit(1);
}
//2.2绑定
ret = bind(fd, (struct sockaddr *)&sin, sizeof(sin));
if (ret < 0)
{
perror("bind error");
exit(1);
}
函数返回值:成功返回0,失败返回-1
listen()
函数原型:int listen(int sockfd, int backlog)
函数头文件:
- #include <sys/types.h> /* See NOTES */
- #include <sys/socket.h>
函数功能:把主动套接字(客户端)变为被动套接字(服务器端)
函数参数:
- sockfd:socket()返回的文件描述符
- backlog:同时允许几路客户端和服务器正在连接的过程(正在3次握手)
- 一般填5,ARM最大为8
内核中服务器的套接字fd会维护两个链表:
1.正在三次握手的客户端链表(数量 = 2 * backlog + 1)
2.已经建立好连接的客户端链表(已经完成三次握手分配好了newfd)
函数返回值:成功返回0,失败返回-1
accept()
函数原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
函数头文件:
- #include <sys/types.h> /* See NOTES */
- #include <sys/socket.h>
函数功能:阻塞等待客户端的连接请求
函数参数:
- sockfd:经过socket()创建,bind(),listen()设置过的文件描述符
- addr:接收客户端的信息(IP地址、端口号)
- addrlen:
函数返回值:成功返回已经建立好连接的一个新fd(newfd),然后就可以进行具体的读和写,失败返回-1
本身的fd可以继续等待要与服务器建立连接的客户端
connect()
函数原型:int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
函数头文件:
- #include <sys/types.h> /* See NOTES */
- #include <sys/socket.h>
函数功能:将客户端连接至服务器
函数参数:
- sockfd:由函数socket()创建的主动套接字
- addr:包含地址信息和端口信息的结构体指针
- addrlen:addr的长度
函数返回值:成功返回0,失败返回-1