wx:嵌入式工程师成长日记
(一)什么是套接字?
套接字(socket)的本意是指电源插座,这里将其引申为一个基于TCP/IP协议可实现基本网络通信功能的逻辑对象。
机器与机器的通信,或者进程与进程的通信,在这里都可以被抽象地看作是套接字与套接字的通信。
应用程序编写者无需了解网络协议的任何细节,更无需知晓系统内核和网络设备的运作机制,只要把想发送的数据写入套接字,或从套接字中读取想接收的数据即可。
套接字代表网络通信能力,类似于手机电话卡
如前所述,套接字是一个提供给程序员使用的逻辑对象,它表示对ISO/OSI网络协议模型中传输层及其以下诸层的抽象。但真正发送和接收数据的毕竟是那些实实在在的物理设备。这就需要在物理设备和逻辑对象之间建立一种关联,使后续所有针对这个逻辑对象的操作,最终都能够反映到实际的物理设备上,建立这种关联关系的过程就叫做绑定。
绑定只是把套接字对象和一个代表自己的物理设备关联起来。但为了实现通信还需要把自己的物理设备与对方的物理设备关联起来。只有这样才能建立起一种以物理设备为媒介的,跨越不同进程甚至机器的,多个套接字对象之间的联系,建立这种联系的过程就叫做连接。
套接字对应程序员来说就是一套网络通信的接口,使用这套接口就可以完成网络通信。网络通信的主体主要分为两部分:客户端和服务器端。在客户端和服务器通信的时候需要这三个概念:IP、端口、通信数据。
(二)IP地址转换
字节序
套接字接口库规定在网络传输过程中采用网络字节序,也就是大端字节序,而本机数据可能是小端字节序
小端字节序:数据的低位存放在低地址
大端字节序:数据的低位存放在高地址
头文件:#include <arpa/inet.h>
大小端地址转换:
// 主机字节序的IP地址转换为网络字节序
// 主机字节序的IP地址是字符串, 网络字节序IP地址是整形
int inet_pton(int af, const char *src, void *dst);
// 将大端的整形数, 转换为小端的点分十进制的IP地址
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
// 点分十进制IP -> 大端整形
in_addr_t inet_addr (const char *cp);
// 大端整形 -> 点分十进制IP
char* inet_ntoa(struct in_addr in);
struct sockaddr_in
{
sa_family_t sin_family; /* 地址族协议: AF_INET */
in_port_t sin_port; /* 端口, 2字节-> 大端 */
struct in_addr sin_addr; /* IP地址, 4字节 -> 大端 */
};
(三)socket函数
包含头文件:#include <sys/socket.h>
1.创建一个套接字
int socket(int domain, int type, int protocol);
domain: 使用的地址族协议
AF_INET: 使用IPv4格式的ip地址
AF_INET6: 使用IPv6格式的ip地址
type:
SOCK_STREAM: 使用流式的传输协议,流式传输默认使用的是TCP
SOCK_DGRAM: 使用报式(报文)的传输协议,报式传输默认使用的UDP
protocol: 一般写0即可, 使用默认的协议
返回值:
成功: 可用于套接字通信的文件描述符
失败: -1
2.将文件描述符和本地的IP与端口进行绑定
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
sockfd: 监听的文件描述符, 通过socket()调用得到的返回值
addr: 传入参数, 要绑定的IP和端口信息需要初始化到这个结构体中,IP和端口要转换为网络字节序
addrlen: 参数addr指向的内存大小, sizeof(struct sockaddr)
返回值:成功返回0,失败返回-1
3.给监听的套接字设置监听
int listen(int sockfd, int backlog);
参数:
sockfd: 文件描述符, 可以通过调用socket()得到,在监听之前必须要绑定 bind()
backlog: 同时能处理的最大连接要求,最大值为128
返回值:函数调用成功返回0,调用失败返回 -1
4.等待并接受客户端的连接请求
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:
sockfd: 监听的文件描述符
addr: 传出参数, 里边存储了建立连接的客户端的地址信息
addrlen: 传入传出参数,用于存储addr指向的内存大小
返回值:函数调用成功,得到一个文件描述符, 用于和建立连接的这个客户端通信,调用失败返回 -1
该函数为阻塞函数,当没有新的客户端连接请求的时候,该函数阻塞;当检测到有新的客户端连接请求时,阻塞解除,新连接就建立了,得到的返回值也是一个文件描述符,基于这个文件描述符可以和客户端通信。
5.接收数据函数
// 接收数据
ssize_t read(int sockfd, void *buf, size_t size);
ssize_t recv(int sockfd, void *buf, size_t size, int flags);
参数:
sockfd: 用于通信的文件描述符, accept() 函数的返回值
buf: 指向一块有效内存, 用于存储接收是数据
size: 参数buf指向的内存的容量
flags: 特殊的属性, 一般不使用, 指定为 0
返回值:
大于0:实际接收的字节数
等于0:对方断开了连接
-1:接收数据失败了
6.发送数据函数
// 发送数据的函数
ssize_t write(int fd, const void *buf, size_t len);
ssize_t send(int fd, const void *buf, size_t len, int flags);
参数:
fd: 通信的文件描述符, accept() 函数的返回值
buf: 传入参数, 要发送的字符串
len: 要发送的字符串的长度
flags: 特殊的属性, 一般不使用, 指定为 0
返回值:
大于0:实际发送的字节数,和参数len是相等的
-1:发送数据失败了
7.连接服务器函数
// 成功连接服务器之后, 客户端会自动随机绑定一个端口
// 服务器端调用accept()的函数, 第二个参数存储的就是客户端的IP和端口信息
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
sockfd: 通信的文件描述符, 通过调用socket()函数就得到了
addr: 存储了要连接的服务器端的地址信息: iP 和 端口,这个IP和端口也需要转换为大端然后再赋值
addrlen: addr指针指向的内存的大小 sizeof(struct sockaddr)
返回值:连接成功返回0,连接失败返回-1