套接字编程之接口
- 学习套接字之前你需要知道的
- 套接字编程
- 套接字
- TCP协议和UDP协议区别
- UDP协议的编写
- UDP通信两端流程
- 具体操作接口介绍
- 创建套接字
- 为套接字绑定地址信息
- 发送数据
- 接收数据
- 关闭套接字
- 字节序相关接口
📌————本章重点————📌 🔗了解套接字的概念; 🔗了解TCP协议和UDP协议区别; 🔗了解UDP协议的编写; 🔗学习具体相关套接字接口; ✨————————————✨
学习套接字之前你需要知道的
套接字编程,通俗的来说:主要讲解的是如何编写一个网络通信程序
1.网络通信的数据汇总都会包含一个完整的为五元组(sip[源端ip地址]、sport[源端端口]、dip[目的端IP地址]dport、protocol[协议]);五元组完成的描述了数据从哪里来到哪里去,用的是什么数据格式。
2.网络通信,通常讨论的是俩个主机之间的通信:客户端&服务端
客户端网络通信程序:通常指的是用户使用的一端
服务端网络通信程序:通常指的是网络编写服务端程序的工作
3.同时我们需要了解:客户端永远都是首先发起服务请求的一端,因为服务器是不知道客户端的地址的,现在用的是动态地址分配,每次上网地址可能发生改变,开发客户端程序都写入了服务端的地址和端口,因此客户端是知道服务端的地址,还有一种原因,只有客户发送了请求,服务端才能提供对应的服务。
套接字编程
套接字
套接字:socket(插座、接口)的翻译,通常表示的是系统提供给程序员实现网络通信的一套接口,为啥封装这些接口呢?因为我们知道,网络通信中有很多的协议, 所以在这套接口中就提供了足够充足的选择。我们通常使用的主要是两个协议的通信程序编写:传输层的TCP和UDP协议。
TCP协议和UDP协议区别
TCP协议 | UDP协议 |
---|---|
传输控制协议 | 用户数据报协议 |
提供的是一套面向链接,可靠,基于字节流的数据传输 | 提供的是无连接,不可靠,基于数据包的数据传输 |
面向链接:通信前先要确定双方是否在线(像打电话) | 无连接:不需要建立链接,只需要知道对方地址,就可以直接发送数据(像发短信) |
可靠传输:通过大量的一些控制机制,保证数据能够安全(有序且完整,一致)到达对端 | 不可靠:只要数据发送出去就行,不管是否可以到达对端 |
字节流:没有传输大小限制,传输比较灵活的一种传输方式(像水流,可以一点一点取,没有具体要求) | 数据报:有最大大小限制,且传输交付有大小限制的一种传输方式(像冰块,每一次要取一块,不能一点一点的取) |
适用于安全要求大于实时传输的要求的场景,比如文件传输 | 因为没有大量的控制机制,因此传输速度快,所以适用于实时性要求大于安全性要求的场景,比如视频的传输 |
UDP协议的编写
UDP通信两端流程
服务端:
- 创建套接字–建立当前进程与网卡之间的关联,在内核中,创建了一个socket结构体
- 为套接字绑定地址信息,给创建的socket结构体,内部描述源端IP地址和端口
(1) 告诉系统收到的数据中凡是地址和端口是我绑定就交给我处理
(2)发送数据的时候,源端地址就是绑定的地址信息绑定的地址,必须是当前设备所拥有的地址 - 接收数据:从内核的socket结构体接收缓冲区中取出数据
- 发送数据:将数据放到内核socket结构体的发送缓冲区中,什么时候实际发送出去,由系统决定
- 不通信了,就关闭套接字,释放资源
客户端:
- 创建套接字
- 为套接字绑定地址信息(不推荐)
一个端口只能被一个网络进程占用。因此一旦客户端程序中绑定的固定的地址这个客户端就只能运行一个了,并且还有端口
冲突的风险。 - 发送数据
如果绑定了地址信息,则源端地址就是绑定的,如果没有绑定地址信息,则系统会选择一个合适的地址和端口进行自动绑定。 - 接收数据
不需要考虑,只需要从接收缓冲区取出数据即可 - 不通信了则关闭套接字释放资源。
具体操作接口介绍
创建套接字
#include<sys/types.h>
#incldue<sys/socket.h>
int socket(int domain, int type, int protocol);
-
domain:地址域类型(具体用的是哪种方式,不同的方式有不同的地址结构:ipv4,ipv6,域间通信)
重点记住AF_INET,比较常用
-
type:套接字类型
注意:两种套接字不能混用,你创建的是哪种协议就要用哪个参数 -
protocol:协议类型
注意:默认使用0,则表示使用套接字类型对应的默认协议,注意记住以下俩种:
(1)IPPROTO_TCP: 值是6
(2)IPPROTO_UDP:值是17
-
成功返回一个套接字描述符,失败返回:-1;
为套接字绑定地址信息
int bind(int socket, const struct sockaddr *address,socklen_t address_len);
1.sockfd:返回套接字的描述符
2.addr:要绑定的地址信息(不同地址域类型有不同的地址结构,故需要一个通用的地址结构)
通用地址结构:
socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及后面要讲的UNIX Domain Socket. 然而, 各种网络协议的地址格式并不相同.
注意:
1.IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16位端口号和32位IP地址
2.IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6. 这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容.
3.socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数;
sockaddr 结构
sockaddr_in 结构
虽然socket api的接口是sockaddr, 但是我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in; 这个结构里主要有三部分信息: 地址类型, 端口号, IP地址.
in_addr结构
in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数
typedef uint32_t in_addr_t
in_addr_t inet_addr(cosnst char*ip);
功能:将点分十进制字符串ip地址转化为网络字节序的整数ip地址 ,如:将192.168.2.2->0x0202a8c0
const char *inet_ ntoa(struct in_ addr addr);
功能:将网络字节序整数P地址, 转换为一个点分 十进制字符串地址
struct in_ addr{
in_ addr_ ts _addr;
}
发送数据
ssize_ t sendto(int sockfd, void *buf, size_ _t dlen, int flag, struct sockaddr *peer,socklen_ t alen);
解释:
1.sockfd: socket返回的套接字描述符
2.buf:要发送的数据空间起始地址
3.dlen:要发送的数据长度,从buf地址开始, 发送dlen长度的数据
4.peer设置的是谁的地址,数据就发给谁,peer:对端地址信息,描述了数据要发送给谁
5.flag:默认0- 阻塞发送(发送缓冲区数据满了就等着)
6.alen:对端地址信息长度
7.**返回值:**成功返回实际发送数据字节长度;失败返回-1
接收数据
4.接收数据
ssize_ .t recvfrom(int sockfd, void *buf, size. t dlen, int flag, struct sockaddr *peer, socklen _t *alel
解释:
1.sockfd:套接字描述符
2.buf:空间起始地址,接收到的数据就被放入到buf空间中
3.peer用于获取地址信息,peer中存的是谁,数据就是谁发送给我的,peer这个参数是在recvfrom函数中设置的,我们不用设置。peer:接收数据的同时,得直到数据是谁发的,因此这里的peer是个输出参数,用于返回对端地址信息
4.dlen:想要接收的数据长度
5.flag:默认0-阻塞接收(socket接收缓冲区中没有数据就阻塞,直到有数据)
6.alen:地址信息长度,也是一个输入输出参数,表示想要接收的地址信息长度,以及实际得到的地址信息长度
7.返回值:成功返回实际接收到的数据长度;失败返回-1
*注意:凡是涉及到获取地址信息的操作,地址信息都是socklen _t 类型的
关闭套接字
int close(int fd)
字节序相关接口
字节序相关接口:
主机字节序到网络字节序的转换: uint32_ t htonl(uint32 _ t val); uint16 t htons(uint16_ t val);
网络字节序到主机字节序的转换: uint32_ t ntoh(uint32 _ t val); uint16 _ t ntohs(uint16_ t val);
注意:
这几个接口中已经进行了主机字节序的判断,因此不需要担心自己的主机字节序,32位数据转换接口,与16转换接口,不能混用
混用出错例子:
具体代码实现请看下一篇:套接字编程之简单实现