网络编程套接字
-
IP地址:
- IPV4: uint32_t ~ 43亿左右 — 在网络上唯一标识一台主机
- IPV6: uchar ip[16] — 因为不向前兼容ipv4因此没有推广起来
- DHCP: 动态地址分配技术 — 谁上网给谁分配IP地址
- NAT: 地址替换 — 实现多人使用同一地址上网
因为数字不好记忆,所以使用点分十进制形式展示ip地址
每条数据中都会包含:src ip(源ip),dest ip(目的ip) -
port端口: uint16_t – 0~65535 - 0~1025 不推荐使用 - 在主机上标识一个进程
网络程序分了客户端和服务端,主动发起请求的一方是客户端,被动在指定位置接收请求的一方是服务端(服务端被动的接收地址必须是固定不变的)
一个端口只能被一个进程占用,一个进程可以使用多个端口
每条数据中都会包含:src port(源端口),dest port(目的端口)
每条数据中都包含:sip sport dip dport proto (五元组 标识一条通信) -
网络字节序:
- 字节序: cpu在内存中对数据进行存取的顺序
- 大端字节序: 低地址存高位
- 小端字节序: 低地址存低位
- 主机字节序: 当前计算机的字节序 — 大小端取决于cpu架构
X86_64 — 小端, MIPS — 大端
如果通信两端主机字节序不同,就会造成数据二义(针对存储大于一个字节数据(short int long float double))
解决方案: 订立标准 — 网络字节序(通信双方都是用网络字节序)— 大端字节序
传输层协议:TCP/UDP
-
-
TCP: 传输控制协议 — 面向连接,可靠传输,面向字节流
- 面向连接:通信之前,先建立连接,确保双方在线
- 可靠传输:在网络正常的情况下,数据不会丢失
- 面向字节流:传输灵活,但是存在TCP粘包问题
- 使用场景:对数据安全要求高,传输文件 — 保证数据安全
- TCP为了实现可靠传输牺牲了部分传输性能
-
-
UDP: 用户数据报协议 — 无连接,不可靠,面向数据报
- 面向数据报:每条数据有长度标识,整条发,整条收;传输不够灵活,但是不会存在粘包问题
- 使用场景:对数据实时要求高,传输视频 — 保证传输速度
UDP:(实时性)
客户端 | 服务端 |
---|---|
1.创建套接字 | 1.创建套接字,通过套接字使进程与网卡建立联系,创建struct socket |
2.为套接字绑定地址信息,通常客户端不推荐用户手动绑定地址信息 | 2.为套接字绑定地址信息(ip+port) |
3.发送数据(如果socket还没有绑定地址,操作系统会选择一个合适的地址端口进行绑定) | 3.接收数据 |
4.接收数据 | 4.发送数据 |
5.关闭套接字 | 5.关闭套接字 |
-
-
创建套接字
- int socket(int domain, int type, int protocol);
- domain: 地址域
- AF_INET: IPV4网络协议地址域
- type: 套接字类型
- SOCK_STREAM 流式套接字,默认协议TCP,不支持UDP
- SOCK_DGRAM 数据报套接字,默认协议UDP,不支持TCP
- protocol: 协议类型
- 0: 使用套接字默认协议
- 6/IPPROTO_TCP — tcp协议
- 17/IPPROTO_UDP — udp协议
- 返回值: 套接字操作句柄 — 文件描述符, 失败: -1
-
-
为套接字绑定地址 — bind()
- int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
- sockfd: 创建套接字返回的描述符
- addr: 地址信息
- addrlen: 地址信息长度
- 返回值: 0, 失败: -1
-
-
接收数据 — recvfrom
- ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr * saddr, socklen_t *addrlen);
- sockfd: 操作句柄,套接字描述符
- buf: 用buf存储接收的数据
- len: 想要接收的数据长度
- flags: 0 — 默认阻塞接收
- saddr: 发送端的地址信息
- addrlen: 地址信息长度(输入输出型参数)不但要指定想要接收多长还要保存实际接收了多长
- 返回值: 实际接收的数据长度 , 失败:-1
-
-
发送数据 — sendto
- ssize_t sendto(int sockfd, void *buf, size_t len, int flags, struct sockaddr *daddr, socklen_t addrlen);
- sockfd: 套接字描述符
- buf: 要发送的数据
- len: 要发送的数据长度
- flags: 0 — 默认阻塞发送
- daddr: 目的段地址信息 — 标识数据要发送到哪里去
- addrlen: 地址信息长度
- 返回值: 实际发送的数据长度 , 失败: -1
-
-
关闭套接字 — close
- int close(int fd);
TCP:(安全性)
客户端 | 服务端 |
---|---|
1.创建套接字 | 1.创建套接字 |
2.不推荐手动绑定地址 | 2.绑定地址信息 |
3.向服务端发起连接请求 connect(sockfd, srvaddr, len) | 3.开始监听:告诉系统可以接收客户端的连接请求 |
4.发送数据 send(sockfd, data, len, flag) | 4.获取已经连接成功的客户端新建socket |
5.接收数据 recv(sockfd, buf, len, flag) | __ |
6.关闭套接字 close(fd) | __ |
- 如何快速判断连接是否已经断开:
原理:
tcp的连接管理中,内建有保活机制;
当长时间没有数据往来的时候,每隔一段时间都会向对方发送一个保活探测包,要求对方回复,当多次发送的保活探测包都没有响应,则认为连接断开;
若连接断开,则recv会返回0;send会触发异常SIGPIPE(导致进程退出)
recv返回0,不是没有数据的意思,而指的是连接断开 - 因为TCP面向字节流,有可能接收到半条数据,因此一定要对返回值进行判断,判断是否数据已经完全接收或完全发送
- 基本的tcp服务端程序只能与一个客户端通信一次,无法实现同时与多个客户端多次通信
- 因为不知道客户端(连接请求+数据)什么时候来,因此写死的程序很容易造成阻塞