网络编程(套接字)
目录
一、套接字
1、套接字的作用
2、关于TCP和UDP协议
1. TCP协议
2. UDP协议
3. 两者的区别
2、套接字函数
1)函数 socket(创建套接字同文件描述符)
2)准备套接字用结构体
1. 套接字的结构体
2. 客户端的套接字:
3. 服务器的套接字:
3)bind(以结构体向套接字中写入 ip、port)
4)send / write(通过套接字发送数据)
5)recv / read(通过套接字接收数据)
6)fcntl(变更函数状态:阻塞/非阻塞)
一、套接字
套接字在最初只是一种IPC通讯手段,在TCP协议出现,TCP协议使用套接字进行通信,因此我们常说的套接字都是应用于网络通信,而早期只用于进程间通讯的则称为域套接字
1、套接字的作用
专门用于网络间通信的一种文件形式
该文件,包含了要交换的数据和通信双方的 ip地址和 port端口号
ip地址和 port端口号需要我们自己添加到套接字中,并且不同的协议需要使用不同类型的套接字
2、关于TCP和UDP协议
TCP协议和UDP协议的套接字就是不同类型的
1. TCP协议
TCP协议是一个可靠的,连续的,基于字节流的协议,TCP具有流量控制功能,顺序控制功能,应答重发功能,以确保在网络不拥堵的时候,所有的数据都可以正确的发送
2. UDP协议
UDP协议由于非连续性,且没有有效的应答手段,所以不可靠,很容易丢包
向前纠错技术(后来各大厂商对UDP协议的更进,添加了校验的方法)
udp协议在发送数据的时候,会选择在发送 n 个数据的时候,匹配发送 m 个校验包
比如说发送 3个数据的时候,匹配发送1个校验包
最简单的校验包 = 1#数据报 + 2#数据报 + 3#数据报
比如说:发送了 1 ,2,3 这3个数据,此时,再发送一个校验包,校验包的值是 4
很明显,在丢包率为 25% 的时候,3个包配一个校验包,就能完全避免丢包
因为4个包里面丢一个,丢了校验包完全无所谓
丢的是任意一个数据包,可以通过校验包和其他数据包,恢复出丢失的数据是多少
依此类推:如果网络波动变大,我们就需要配更多的校验包,以保证丢包率减低,恢复率提高
3. 两者的区别
TCP协议稳定性高,传输速度慢,UDP协议稳定性差,传输速度快
2、套接字函数
1)函数 socket(创建套接字同文件描述符)
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
功能:创建不同类型的套接字用文件描述符
参数 domain:套接字所依赖的网络介质如果是 ipv4 就填入 AF_INET如果是 ipv6 就填入 AF_INET6如果是 域套接字 就填入 AF_LOCAL / AF_UNIX
参数 type:选择套接字的类型SCOK_STREAM:字节流套接字传输数据,连续,可靠,双向,数据量大SOCL_DGRAM:数据包套接字传输数据,不连续,不可靠,有长度要求,双向
参数 protocol:选择套接字所依赖的通信协议0:自动匹配,会根据参数 type 和参数 protocol 自动选择合适的通信协议
返回值:返回创建套接字文件描述符一般来说:AF_INET + SOCK_STREAM + 0 ,最终创建的是 TCP 协议的套接字AF_INET + SOCK_DGRAM + 0 ,最终创建的是 UDP 协议的套接字
字节流的优点:允许发送无穷大的数据,会在内核中对数据做分割处理,但是由于连续发送,数据依然粘连在一起
字节流的缺点:由于数据是粘连的,因此接收端需要花费大量的时间处理数据
数据包的优点:数据不会粘连,发送几次数据就是几个数据包
数据包的缺点:发送的数据大小有上限,过大的数据需要手动分批次发送,还有丢包的风险
2)准备套接字用结构体
1. 套接字的结构体
struct sockaddr_in {__kernel_sa_family_t sin_family; /* 依赖的网络介质 */__be16 sin_port; /* port端口号 */struct in_addr sin_addr; /* ip地址 */
};
127.0.0.1 是一个本地回环地址,不会经过网卡,专门用来在无网络的情况下做本地测试,
抓包工具无法抓到这个 ip地址 的数据
2. 客户端的套接字:
写入 ip地址 的目的 是为了通过 ip地址,找到该客户端想要连接的服务器
写入 port端口号 的目的 是为了通过端口号,知道数据需要发送到服务器的哪个进程
3. 服务器的套接字:
写入 ip地址 的目的 是过滤掉不想接收的客户端,选择想要连接的客户端
写入 port端口号 的目的 是为了让服务器知道要去哪个进程读取接收到的数据
3)bind(以结构体向套接字中写入 ip、port)
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:往套接字中写入 ip地址 和 port端口号 (该操作被称为为套接字命名)
参数 sockfd:填写 套接字用文件描述法
参数 addr:通用套接字结构体指针,需要提前准备 struct sockaddr_in 类型的结构体
参数 addrlen:参数 addr 的字节长度
4)send / write(通过套接字发送数据)
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:通过套接字,向套接字中指向的ip地址发送数据
参数 sockfd:填入 socket创建的套接字文件描述符
参数 buf:填入 要发送的数据的地址
参数 len:填入 要发送的字节的长度
参数 flag:设置函数的状态 阻塞 / 非阻塞0 :默认阻塞,发送数据给目标,目标的接收区满了,就会发送阻塞MSG_DONTWAIT:非阻塞,发送数据给目标,接收区满了,丢弃新发送的数据
=======================================================================
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
用法基本一致,IO篇也有详细介绍
5)recv / read(通过套接字接收数据)
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能:通过套接字,读取套接字中ip地址所发送的数据
参数 sockfd:由socket创建的套接字文件描述符
参数 buf:将读取到的数据存入buf所指向的连续地址(所指向的数组)
参数 len:所读取数据的字节长度
参数 flags:设置函数的状态 阻塞 / 非阻塞0 :默认阻塞,没有接收到数据就阻塞MSG_DONTWAIT:非阻塞,没有读取到数据直接返回 0
返回值:阻塞模式:返回接收到数据的字节数,若套接字损坏 返回 -1若服务器与客户端连接断开,则有阻塞函数变为非阻塞函数,并返回 0非阻塞模式:返回接收到数据的字节数,若服务器与客户端断开,返回 -1未接收到参数,则返回 0
=====================================================================
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
用法基本一致,IO篇也有详细介绍
6)fcntl(变更函数状态:阻塞/非阻塞)
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
功能:设置指定文件描述符的状态,也就是flags属性
参数 cmd:设置函数的功能 设置 flags 或 读取 flagsF_SETFL:设置 fd 文件的 flags 属性F_GETFL:获取 fd 文件的 flags 属性
不定参 ···:此处只有第一个数据是有效的仅当 cmd == F_SETFL 时,需要传一个不定参作为 flags
返回值:当 cmd == F_SETFL 时,成功返回 0 ,失败返回 -1当 cmd == F_GETFL 时,返回 flags ,失败返回 -1
使用时要注意不要变更原有的状态,因此尽可能追加flags值(若有需要替换原先的值,也可以换)
使用流程:
1、先获取 fd 原有的 flags 属性
2、对获取到的 flags 属性追加
3、将追加好的 flags 属性,设置为 fd 的 flags 属性
以标准输入流为例