目录
sockaddr结构体介绍 -- 套接字介绍,sockaddr结构(为什么要有,sockaddr_in和sockaddr_un的介绍+参数),ifconfig命令(本地环回地址)-CSDN博客
socket编程接口
通用
socket()
函数原型
创建套接字
- tcp/udp协议均适用
domain
- 确定套接字使用的协议家族(网络协议/本地通信)
- 可以是AF_INET,AF_INET6,AF_UNIX(在前面讲sockaddr结构中有介绍)
type
- 确定套接字类型(流式/用户数据报)
- SOCK_STREAM(tcp协议) 或 SOCK_DGRAM(udp协议)
protocol
- 一般为0即可
- 前两个参数已经可以明确该套接字提供什么服务了
返回值
返回新套接字的文件描述符
- 也就是说,其实我们创建套接字,就是创建了个文件
- 只不过,之前的文件指向的是显示器/磁盘/键盘等设备,这里的文件指向网卡
之后进行操作时,参数都必须带上这个返回值
- 就相当于创建文件后返回了文件描述符fd,之后的io等操作都需要带上这个fd
- 所以,该返回值可以叫做网络文件描述符
如果创建失败,返回-1,并设置errno
bind()
函数原型
将一个套接字与特定的地址(IP地址和端口号)关联起来
- 通常在服务器端使用,用于指定服务器应该监听哪个地址和端口
- tcp/udp协议均适用
socket
要绑定的套接字的文件描述符
address
包含地址信息的结构体指针
address_len
结构体的长度
返回值
- 如果成功,返回0
- 失败,返回-1,且设置错误码
close()
函数原型
关闭文件描述符(在之前的文件io中也使用过)
返回值
- 0表示成功关闭
- -1为关闭失败
udp协议的收发
recvfrom()
函数原型
用于接收数据
sockfd
指定使用哪个套接字接收数据
buf , len
接收数据的缓冲区地址和缓冲区大小
flags
指定接收数据的附加选项,通常为0
src_addr , addrlen
- 两个输出型参数
- src_addr 存放发送端的地址信息(用于指导响应数据的发送位置)
- addrlen存放发送端地址信息(也就是那个结构体)的长度
返回值
- 成功,返回接收到的数据的字节数
- 失败,返回-1,并设置错误码
sendto
函数原型
向指定的目标地址发送UDP数据报
sockfd
指定用于发送数据的套接字文件描述符
buf , len
要发送数据的缓冲区的指针,发送数据的长度
flags
指定发送数据的附加选项,通常为0
dest_addr , addrlen
- 传入数据报的目的地址信息(一个结构体指针)
- 结构体的长度
返回值
- 成功,返回发送数据的字节数
- 失败,返回-1,并设置错误码
tcp协议的建立联系
tcp协议是面向连接的,客户端在与它通信前,需要先建立连接
listen
函数原型
在套接字上开始监听传入的连接请求(服务端使用)
sockfd
要开始监听的套接字文件描述符
backlog
指定了在内核中等待接受连接的队列的最大长度
返回值
- 如果成功,返回0
- 失败,返回-1,且设置错误码
accept
函数原型
阻塞式获取连接+连接方信息(一般是服务端使用)
- 它会从挂起的连接队列中接受一个连接,并创建一个新的套接字来处理该连接
sockfd
正在监听的套接字文件描述符
addr
(和udp中使用的recvfrom的参数一样,输出型参数)
用于存储连接方的地址信息
addrlen
addr的大小(同上)
返回值
- 如果成功,返回一个新的套接字文件描述符,用于之后和连接方的通信
- 如果错误,返回-1,并设置errno
connect
函数原型
将客户端的套接字连接到服务器端的套接字,以便在它们之间进行通信(一般用于客户端)
sockfd
客户端的套接字文件描述符
addr
包含要连接的远程服务器的地址信息
addrlen
addr的大小
返回值
- 如果成功,返回0
- 失败,返回-1,且设置错误码
tcp协议的收发
文件io接口
tcp是面向字节流的,其收发数据可以使用文件接口
- 提到字节流,我们应该会想起管道文件和以读/写方式打开的普通文件都是面向字节流的
- 而他们都使用的是文件io接口
- 所以,这里网络数据的io也可以用文件接口(也就是read和write)进行
- 注意,在这两个函数中使用的文件描述符,应该是connect函数的返回值(这个用于实际通信),而不是socket函数的返回值(这个只用于建立连接)
recv
函数原型
用于从套接字接收数据
前三个参数
和read中的参数作用相同
flags
可选的标志参数,用于控制接收操作的行为
- 如果为0,和read功能相同
返回值
和read一样
send
函数原型
前三个参数
和write中的参数作用相同
flags
可选的标志参数,用于控制发送操作的行为
- 如果为0,和write功能相同
返回值
数据转换函数
除了这些,还有一些辅助函数,帮助我们进行一些数据的转换
网络字节序和主机字节序的转换(端口号)
自己实现
其实就是调整赋值的顺序
先明确两个前提:
- 我们的大小端顺序是以字节为单位的
- 一般我们的端口号是16位的
我们将它拆成2个部分,每个部分8个bit:
- x & 0xFF00 取到原来的高8位
- x & 0x00FF 取到原来的低8位
然后就可以开始拼接了(part1 part2):
- 如果part2=高8位,就是大端(小地址放大权重)
- 反过来,part2=低8位,就是小端(小地址放小权重)
接口
一般我们的端口号是16位的,但在某些特殊情况下,也可能会出现32位的端口号,所以有两种接口:
字符串和整数类型的转换(ip地址)
自己实现
首先明确:
- 字符串形式的ip地址 -- "xxx.xxx.xxx.xxx"
- 整数形式的ip地址 -- 32位整数
所以我们可以定义一个结构体,存储ip地址的4个部分(按照.分隔,它每个部分都是8bit大小):
字符串 -> 整数
- 使用substr函数,将它分为4个子串
- 然后将其分别赋值给结构体的4个成员,注意:
- part1是最先定义的,所以按照栈的结构特点,它是低地址;而最后定义的part4是高地址
- 所以让part1=子串1(这样符合大端的高位放在低地址)
- 依次赋值后,将结构体类型转换为32位整数即可
整数->字符串
- 将整数类型强转为结构体类型,这样自动就可以将它分为4部分
- 然后拼接在一起就行:
n的含义 -- network
字符串转整数
cp
- 传入的IP地址字符串
inp
- 输出型参数
- 将转换后的ip地址赋值进结构体中
af
- 用于指定地址的类型
- AF_INET 或 AF_INET6
src
- 要转换的字符串类型的ip地址的指针
dst
- 指向用于存储转换后的二进制格式地址的缓冲区的指针
- 比如 in_addr结构体的指针
cp
- 要转换的ip地址
结果以返回值的形式获得
整数转字符串
将IPv4地址从二进制格式转换为点分十进制的字符串表示形式
in
- 存放ip地址的结构体
字符串结果以返回值的形式给出
注意点
这个函数返回的是字符串的起始地址
- 说明它帮助我们在内部创建好了字符串 -> 这块存放字符串的空间,即使函数结束也没有被销毁
- 而且这个指针不需要我们手动释放(仔细想想,介绍这个函数的时候,完全没提到我们该这样)
- 所以,这个字符串的空间是静态分配的,他存放在全局区
- 所以它的值在main函数之前就已经确定好了
- 那么就会导致,这块区域会被重复使用,他的值会被更新为最后一次调用它时转换成的字符串
- 所以,如果我们需要存放多个ip地址,就不能使用这个函数,他会让最后一次的值覆盖掉前面的数据 / 如果一定要使用它,可以每次复制一下返回的字符串,让它在栈区/堆区存在一份
将IPv4或IPv6地址从二进制格式转换为字符串形式
af
- 用于指定地址的类型
- AF_INET 或 AF_INET6
src
- 包含二进制格式的ip地址的指针
- 比如 in_addr结构体的指针
dst
- 存储结果的缓冲区的指针
size
- 缓冲区的大小
注意点
这个函数所用的字符串缓冲区是用户传入的,就可以有效避免ntoa函数的覆盖问题