原文链接:C++ Linux多进程Socket通信
接口函数
套接字由于本身是为计算机网络服务的,因此相比于一般的通信方式,套接字会更复杂.
socket创建与连接
// 创建套接字
int socket(int domain, int type, int protocol);
return fd ID(成功) -1(错误)
domain协议族 AF_INET(IPV4) AF_INET6(IPV6) AF_UNIX AF_LOCAL 用于本机通信
type类型 SOCK_STREAM(TCP) SOCK_DGRAM(UDP) SOCK_RAW(原始套接字)
protocol协议 0为自动选择 有IPPROTO_TCP和IPPROTO_UDP
// 绑定计算机实际的端口
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
return 0(成功) -1(错误)
sockfd: socket返回的文件描述符
addr: IP的地址结构体(下面详细介绍)
addrlen: 由于不同的协议地址长度不同,需要指定
// 监听
int listen(int sockfd, int backlog);
return 0(成功) -1(错误)
backlog为最大连接数
// 客户端请求连接(仅针对有连接服务)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
return 0(成功) -1(错误)
sockfd: 客户端的socket
addr,addrlen:服务器IP和大小
// 接受连接(服务器接收客户端连接)
int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);
return 与客户端通信的fd(成功) -1(错误)
sock: 服务器的socket
addr,addrlen:请求连接的客户端IP和大小
地址创建与转换
套接字的IP和端口需要进行处理后才能传入
由于计算机本身字节序和网络字节序不一定一致,需要进行处理转换,都是大端不需要转,而小端字节序需要和网络字节序相互转换.
IP:PORT 以IPV4为例 每个数字1字节0-255,4个字段需要32位为无符号int,端口0-65535 16位2字节,都按照网络字节序存放.
V6由于地址更长有128位,因此结构与v4不同,并且有些服务可能没有考虑到v6用户的支持,某些服务v6会存在问题.
#include <arpa/inet.h>
IPV4的地址结构体:
struct sockaddr_in {
short int sin_family; // 地址族,通常为 AF_INET 或v6
unsigned short int sin_port; // 端口号,网络字节序
struct in_addr sin_addr; // IP 地址
};
// `struct in_addr` 定义如下:
struct in_addr {
uint32_t s_addr; // IP 地址,网络字节序,32位无符号,4字节,每个字节0-255
};
//IPV4字符串转32位网络字节序IP
in_addr_t inet_addr(const char *cp);
return 网络字节序地址(成功) INADDR_NONE(错误)
V6由于地址更长有128位,因此结构与v4不同
struct sockaddr_in6
// 端口转换
//主机转网络 host to network short
uint16_t htons(uint16_t hostshort);
return 大端序端口号(成功)
// 网络转主机
uint16_t ntohs(uint16_t netshort);
return 主机端口号(成功)
// 地址转换(包括IP)
// 主机 转 网络
int inet_pton(int af, const char *src, void *dst);
return 成功则为1,若输入不是有效的表达式则为0,若出错则为-1
// 网络 转 主机
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
return 字符指针(成功) NULL(错误)
af: 协议族AF_INET 或AF_INET6
src,dst: 原IP字符串和目的字符串
size: IP长度大小
通信接口
连接建立后可以相互发送数据,socket提供3种通信方法
注意一点,由于socket网络建立连接后本质还是通过IO读写的,可以通过fcntl控制阻塞和非阻塞特性
- 传统文件描述符通信:和读写文件一样流式读写
ssize_t write(int fd, const void *buf, size_t nbytes);
return n(字节数) -1(错误)
ssize_t read(int fd, void *buf, size_t nbytes);
return n(字节数) -1(错误)
- 更安全高效的接口通信:linux提供了结构体形式发生消息,更高效稳定安全
send和recv相比文件读写,可以参数控制并且会受协议影响实际工作方式
ssize_t send(int sockfd, const void buf[.len], size_t len, int flags);
return n(字节数) -1(错误)
ssize_t recv(int sockfd, void buf[.len], size_t len,int flags);
return n(字节数) -1(错误)
支持更复杂的消息处理和附加控制信息
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
return n(字节数) -1(错误)
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
return n(字节数) -1(错误)
flags 标志:
0:默认标志,表示普通的数据发送操作。
MSG_OOB:发送带外数据,通常用于紧急数据。
MSG_DONTROUTE:绕过路由表,直接发送数据到目标主机。
MSG_NOSIGNAL:不产生信号(在某些系统中),用于避免在发送数据时中断进程。
其中消息结构体为:
struct msghdr {
void *msg_name; /* 地址 */
socklen_t msg_namelen; /* 地址长度 */
struct iovec *msg_iov; /* 指向 iovec 结构体的指针 */
size_t msg_iovlen; /* iovec 结构体的数量 */
void *msg_control; /* 指向控制信息的指针 */
size_t msg_controllen; /* 控制信息的长度 */
int msg_flags; /* 消息标志 */
};
数据指针结构:
struct iovec {
void *iov_base; /* 数据缓冲区的起始地址 */
size_t iov_len; /* 数据缓冲区的长度 */
};
- 面向无连接的UDP通信:使用下面两个接口
ssize_t sendto(int sockfd, const void buf[.len], size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void buf[restrict .len], size_t len,int flags,struct sockaddr *_Nullable restrict src_addr,socklen_t *_Nullable restrict addrlen);
UDP存在数据丢失可能,但是客户端加入多播组,可以实现多播,适合视频流等
实例
使用tcp send/recv阻塞通信
服务器监听,客户端连接,之后相互发送一个消息
server