LinuxC语言系统开发——网络编程
一.网络编程
1. 网络基础
1.1 网络的作用?
网络是用于远距离,跨主机的数据通讯。
1.2 网络体系结构:
网络数据的传输过程往往是比较复杂的,为了网络数据完整,一致,安全性的送达给目标主机,
计算机网络系统对数据的传输过程进行了分段,分层管理,每一层都有该层功能,同时每一层
给下一层提供服务,也可以使用上一层提供的服务,这种分层结构以及每一层所提供的功能
协议的及格,称为网络体系结构。
1.3 两种重要的网络体系结构模型
1) ISO提供的 OSI 七层协议模型:
应用层
表示层
会话层
传输层
网络层
数据链路层
物理层
2) TCP/IP 四层协议模型 (事实上的工业标准)
应用层
传输层
网络层
网络接口层
1.4 传输层的协议
1) TCP(传输控制协议)
TCP:面向连接的可靠的数据传输服务。
面向连接: 通讯双方在数据交互之前,必须要建立网络连接。
原则:网络程序实际应用, 往往是基于 C / S 架构,或者是 B / S 结构。
网络连接的建立过程:(三次握手过程)
第一次握手: 客户端发送SYN(握手信号)给服务器,客户端进入 SYN_SEND,等待服务器回应;
第二次握手: 服务器发送SYN(握手信号)+ ACK(响应信号)给客户端,服务端进入 SYN_RECV;
第三次握手: 客户端发送ACK(响应信号)给服务端,完成TCP网络连接;上方进入 ESTABLISHED状态。
网络断开的过程:(四次挥手过程)
第一次挥手: 客户端发送FIN(挥手信号)给服务器,客户端进入 FIN_WAIT1,等待服务器回应;
第二次挥手: 服务器发送ACK(响应信号)给客户端,服务端进入 CLOSE_WAIT,客户端进入 FIN_WAIT2;
第三次挥手: 服务器发送FIN(挥手信号) + ACK(响应信号)给客户端,服务端进入 LAST_ACK;
向客户端请求断开服务器到客户端方向的数据传输.
第四次挥手: 客户端发送ACK(响应信号)给服务器,客户端进入 TIME_WAIT,等待网络关闭,2MSL
后完成网络断开;服务器接收到客户端发送ACK(响应信号),连接关闭
面试题:为什么TCP建立过程需要3次握手,而断开需要4次挥手?
因为TCP的连接是双向的,在断开时,需要在每个方向都要单独进行数据关闭,连接断开。
2) UDP(用户数据报协议)
UDP:面向无连接的不可靠的数据传输服务。
UDP协议的特点:
1. 无连接,不可靠
2. 系统的资源消耗少,因为不连接就不需要系统去维护连接状态;
3. 实时性高,
4. UDP可以实现一对多通讯
2. 网络编程应用
2.1 网络专业术语
1) IP地址:网络中主机的唯一标识。本质是一个非负正整数(主机编号)
IP地址的表现形式:
1) 用二进制表示
2) 点分十进制表示的字符串 192.168.12.39
IP地址的组成:
1) 网络ID: 用于标识主机处于哪一个网络
2) 主机ID: 用于网络中主机的标识号
IP的分类:
根据网络ID与主机ID在正整数中占据的二进制位数量不同,
点分十进制第一字节取值范围
A: 1~127
B: 126 ~ 191
C: 192 ~ 223
D: 224 ~ 239 用于UDP组播通讯
E: 240 ~ 254 保留地址
特殊IP:
环回/环路IP : 127.0.0.1 INADDR_LOOPBACK
任意IP : 0.0.0.0 INADDR_ANY
本地广播IP : 255.255.255.255 INADDR_BROADCAST
2) 端口 (Port)
作用: 用于标识发送到某台主机的网络数据,应该由该主机内的哪一个网络程序来处理。
本质:16位无符号整型值
3) 网络字节序:
作用:为了避免不同主机因主机字节序不同,导致对网络数据解析不同,造成数据不一致的问题。
网络字节序规定不同主机均采用大端方式存储网络数据。
4) 套接字 (Socket):
套接字: 网络编程的接口。
类型: 1.流式套接字 (SOCK_STREAM) 基于 TCP
2.数据报套接字 (SOCK_DGRAM) 基于 UDP
3.原始套接字 (SOCK_RAW) 用于底层协议开发
...
2.2 基于UDP的网络编程
1) 基础网络编程(单播unicast)
服务器端:
1. 创建套接字
socket
函数头文件 #include <sys/socket.h>
#include <sys/types.h>
函数原型: int socket(int domain,int type,int protocol);
函数功能: 创建套接字
函数参数: domain: 协议族,常用取值
AF_INET: IPv4
AF_INET6 IPv6
type: 套接字类型,常用取值
SOCK_DGRAM
SOCK_STREAM
SOCK_RAW
protocol: 一般写0,让系统决定套接字使用的协议
函数返回值: 成功返回 套接字描述符
失败返回-1,错误码放在errno
2. 绑定自身地址信息到套接字上
bind
函数头文件 #include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
函数原型: int bind(int sockfd,struct sockaddr* addr,int addrlen);
函数功能: 绑定自身地址信息到套接字
函数参数: sockfd: 套接字描述符
addr: 地址结构体,用于存储地址信息,实际使用中用sockaddr_in 代替。
addrlen: 地址结构体长度
函数返回值: 成功返回 0
失败返回-1,错误码放在errno
引申: 网络字节序转换:
涉及的函数族:
#include <arpa/inet.h>
uint16_t htons(uint16_t port);
uint16_t ntohs(uint16_t port);
uint32_t inet_addr(const char* ip);
char* inet_ntoa(struct in_addr in);
3. 接收客户端数据
recvfrom
函数头文件 #include <sys/socket.h>
#include <sys/types.h>
函数原型: int recvfrom(int sockfd,void* buf,int len,int flags,
struct sockaddr* fromaddr,int* fromlen);
函数功能: 接收网络数据
函数参数: sockfd: 套接字描述符
buf: 用于存储接收到的网络数据的内存缓冲区
len: 内存缓冲区长度
flags: 操作方式,一般写 0
fromaddr: [out]发送方的地址信息
fromlen: [out]发送方的地址信息长度
函数返回值: 成功返回 实际接收的字节数
失败返回-1,错误码放在errno
4. 发送数据给客户端
sendto
函数头文件 #include <sys/socket.h>
#include <sys/types.h>
函数原型: int sendto(int sockfd,const void* buf,int len,int flags,
struct sockaddr* toaddr,int tolen);
函数功能: 接收网络数据
函数参数: sockfd: 套接字描述符
buf: 用于存储待发送的网络数据的内存缓冲区
len: 发送的数据实际长度
flags: 操作方式,一般写 0
toaddr: 目标主机的地址信息
tolen: 地址信息结构体长度
函数返回值: 成功返回 实际发送的字节数
失败返回-1,错误码放在errno
5. 关闭套接字
客户端:
1.创建socket;(socket)
2.设定对方的地址信息;
3.发送消息给服务端(sendto)
4.接收服务端的信息(recvfrom)
5.关闭套接字 close
2) UDP广播通讯(broacast)
注意: UDP广播通讯要借助特殊IP。
广播地址:
1. 直接广播地址:主机ID二进制位全为1 xxx.xxx.xxx.255 (c类为例)
2. 本地广播地址:网络ID,主机ID二进制位全为1 255.255.255.255
发送端:
1. 创建数据报套接字
2. 开启套接字广播属性;
setsockopt
函数头文件 #include <sys/socket.h>
#include <sys/types.h>
函数原型: int setsockopt(int sockfd,int level,int optname,const void* optval,
int optlen);
函数功能: 接收网络数据
函数参数: sockfd: 套接字描述符
level: 套接字属性层,可取值如下:
SOL_SOCKET
IPPROTO_IP
IPPROTO_TCP
optname: 属性名
flags: 操作方式,一般写 0
optval: 指向属性值的指针
optlen: 属性值的长度
函数返回值: 成功返回 实际发送的字节数
失败返回-1,错误码放在errno
例子: int enable = 1;
setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&enable,sizeof(int));
3. 设定广播地址和广播端口
4. 发送广播数据到广播地址和端口
5. 接收回复的数据
6. 关闭套接字
接收端:
1. 创建数据报套接字
2. 绑定任意地址和广播端口
3. 接收广播数据
4. 回复数据
5. 关闭套接字
3) UDP组播通讯(multicast)
注意: UDP组播通讯要借助特殊IP (D类)。
组播地址:
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.255为本地管理组播地址,仅在特定的本地范围内有效。
发送端:
1. 创建数据报套接字
2. 指定组播地址和组播端口
3. 向组播地址和组播端口发送数据,
4. 接收回复的数据
5. 关闭套接字
接收端:
1. 创建数据报套接字
2. 绑定任意地址和组播端口
3. 加入组播组
setsockopt
struct ip_mreqn reqn = {0};
reqn.imr_multiaddr.s_addr = inet_addr("224.0.2.100");
reqn.imr_address.s_addr = inet_addr("192.168.255.132");
reqn.imr_ifindex = if_nametoindex("ens33");
setsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&reqn,sizeof(reqn));
4. 接收组播数据
5. 回复数据
6. 退出组播组
setsockopt(sockfd,IPPROTO_IP,IP_DROP_MEMBERSHIP,&reqn,sizeof(reqn));
7. 关闭套接字
2.3 基于TCP协议的网络编程
服务器端:
注意: TCP服务器往往要涉及两种作用的套接字
一个用于监听客户端连接,
一个用于和客户端数据交互,
编程流程:
1. 创建流式套接字(用于监听客户端连接)
2. 绑定服务端地址信息到监听套接字上,
3. 监听客户端连接
listen
函数头文件 #include <sys/socket.h>
#include <sys/types.h>
函数原型: int listen(int sockfd,int backlog);
函数功能: 监听客户端连接
函数参数: sockfd: 监听套接字描述符
backlog: 可排队连接的最大连接数
函数返回值: 成功返回 0
失败返回-1,错误码放在errno
4. 阻塞等待接受客户端连接
accept
函数头文件 #include <sys/socket.h>
#include <sys/types.h>
函数原型: int accept((int sockfd, struct sockaddr* addr, int* addlen));
函数功能: 接受客户端连接,并生成通讯套接字
函数参数: sockfd: 监听套接字描述符
addr: [out]获取客户端地址信息的结构体指针
addlen: [out]获取客户端地址信息的结构体长度的指针
函数返回值: 成功返回 通讯套接字
失败返回-1,错误码放在errno
5. 发送网络数据
send
函数头文件 #include <sys/socket.h>
#include <sys/types.h>
函数原型: int send(int sockfd,const void* buf,int len,int flags);
函数功能: 接收网络数据
函数参数: sockfd: 套接字描述符
buf: 用于存储待发送的网络数据的内存缓冲区
len: 发送的数据实际长度
flags: 操作方式,一般写 0
函数返回值: 成功返回 实际发送的字节数
失败返回-1,错误码放在errno
6. 接收网络数据
recv
函数头文件 #include <sys/socket.h>
#include <sys/types.h>
函数原型: int recvf(int sockfd,void* buf,int len,int flags);
函数功能: 接收网络数据
函数参数: sockfd: 套接字描述符
buf: 用于存储接收到的网络数据的内存缓冲区
len: 内存缓冲区长度
flags: 操作方式,一般写 0
函数返回值: 成功返回 实际接收的字节数
返回0. 代表通讯对方断开网络连接。
失败返回-1,错误码放在errno
7. 关闭套接字
客户端
1.创建流式套接字; (socket)
2.连接服务器(connect)
connect
函数头文件 #include <sys/socket.h>
#include <sys/types.h>
函数原型: int connect((int sockfd, struct sockaddr* addr, int addlen));
函数功能: 连接服务器
函数参数: sockfd: 套接字描述符
addr: 服务器地址信息的结构体指针
addlen: 地址信息的结构体长度
函数返回值: 成功返回 0
失败返回-1,错误码放在errno
3.发送消息到服务器(send)
4.接收服务端的信息(recv)
5.关闭套接字 (close)