本文讲究的就是一个实用,所以不会所有的东西都介绍,只会介绍常用的
注:阅读本文前最好还是有些网络编程的基础概念比如大端小端之类的
结构体
sockaddr_in
sockaddr_in是一个用来存放地址和端口的结构体,一般是IPV4协议使用
sin_family:存放使用的协议,必须是AF_INET(IPV4协议)
sin_port:存放的是端口
sin_addr:存放的是地址
sin_zero:我找到的解释是为了让sockaddr和sockaddr_in的大小一致,方便互相转换来占空间用的
struct sockaddr_in {
short sin_family; /* AF_INET */
unsigned short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
struct in_addr {
unsigned long s_addr; /* 地址 */
};
sockaddr_in6
sockaddr_in6和sockaddr_in都是用来存放地址和端口的结构体,不同的地方就是sockaddr_in6是提供给IPV6协议使用的
sin6_family:存放使用的协议,必须是AF_INET6(IPV6协议)
sin6_port:存放的是端口
sin6_flowinfo:流信息,一般情况下不需要显式的设置或使用sin6_flowinfo
sin6_addr:存放的是地址,是一个结构体
sin6_scope_id:地址范围的标识符,只有需要指定特定地址范围标识符时才会去显式设置其值
struct sockaddr_in6 {
sa_family_t sin6_family; /* AF_INET6 */
in_port_t sin6_port;
uint32_t sin6_flowinfo;
struct in6_addr sin6_addr;
uint32_t sin6_scope_id;
};
struct in6_addr {
unsigned char s6_addr[16]; /* 地址 */
};
msghdr
msg_name:存放地址结构体(sockaddr、sockaddr_in、sockaddr_in6)
msg_namelen:地址结构体的长度(以字节数表示)
msg_iov:指向iovec结构体的指针,里面存放着消息
msg_iovlen:msg_iov这个数组的长度
msg_control:辅助数据,一般传递其他与消息相关的信息,比如带外数据(Out-of-band-data)
msg_controllen:辅助数据的长度(以字节数表示)
iov_base:存放着数据
iov_len:存放着iov_base指向的数据的长度(以字节数表示)
struct msghdr {
void *msg_name; /* Optional address */
socklen_t msg_namelen; /* Size of address */
struct iovec *msg_iov; /* Scatter/gather array */
size_t msg_iovlen; /* # elements in msg_iov */
void *msg_control; /* Ancillary data, see below */
size_t msg_controllen; /* Ancillary data buffer len */
int msg_flags; /* Flags on received message */
};
struct iovec { /* Scatter/gather array items */
void *iov_base; /* Starting address */
size_t iov_len; /* Number of bytes to transfer */
};
函数
socket
这个函数是用来创建套接字的,创建成功会返回一个文件描述符,如果创建失败便会返回-1
int socket(int domain, int type, int protocol)
domain
地址族
AF_INET
:IPV4地址族,用于支持IPV4协议AF_INET6
:IPV6地址族,用于支持IPV6协议AF_UNIX
and AF_LOCAL:Unix域套接字地址族,用于同一台主机上通信
type
套接字的类型
SOCK_STREAM
:面向连接的可靠流式套接字SOCK_DGRAM
:面向消息的报式套接字SOCK_SEQPACKET
:面向连接的有序数据套接字SOCK_RAW
:网络层原始套接字
protocol
传输协议
SOCK_STREAM
IPPROTO_TCP
:TCP协议,一种面向连接的、可靠的、基于字节流的传输协议SOCK_DGRAM
IPPROTO_UDP
:UDP协议,一种无连接的、不可靠的,基于报文的传输协议SOCK_RAW
IPPROTO_RAW
:原始套接字协议,允许应用程序直接访问底层网络协议栈,可以用于构造自定义的网络协议IPPROTO_ICMP
:互联网控制消息协议(ICMP),用于网络设备之间传递错误和控制消息IPPROTO_IP
:IP协议,是基于IP地址的网络层协议,用于在网络中传输数据包SOCK_SEQPACKET
IPPROTO_SCTP
:流控制传输协议(SCTP)提供类似TCP的可靠性和有序性
示例
int sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
bind
给套接字绑定地址,如果成功返回0,发生错误就返回-1
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd
套接字的文件描述符
addr
指向sockaddr结构体的指针
addrlen
地址结构体的字节数
示例
sockaddr_in addr = {0};
addr.sin_addr = INADDR_ANY; // 这里的INADDR_ANY代表 0.0.0.0 可以理解成本地的意思
addr.sin_port = honts(8080); // 使用honts函数从主机字节序转换到网络字节序,下一篇文章会讲
addr.sin_family = AF_INET;
bind(sockfd,(sockaddr*)&addr,sizeof addr); // 这里使用了C风格的强转将sockaddr_in指针转换成sockaddr
listen
将套接字设置成监听(被动)模式
如果成功返回0,发生错误就返回-1
int listen(int sockfd, int backlog);
sockfd
套接字的文件描述符
backlog
请求队列的最大数量
示例
listen(sockfd,128);
connect
流式套接字发送连接请求,连接到服务器就返回0,如果发生错误就返回-1
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd
套接字的文件描述符
addr
指向sockaddr结构体的指针
addrlen
地址结构体的长度(以字节数表示)
示例
sockaddr_in addr = {0};
addr.sin_addr = inet_addr("117.21.32.217"); // 该ip为随机生成
addr.sin_port = honts(8080);
addr.sin_family = AF_INET;
connect(sockfd,(sockaddr*)&addr,sizeof addr);
accept
面向连接的套接字用来同意客户端发送的连接请求
会阻塞到有客户端发送连接请求
如果成功便会返回一个与客户端通信的套接字文件描述符,如果失败返回-1
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd
套接字文件描述符
addr
指向sockaddr结构体的指针,客户端的地址和端口会存储在里面
addrlen
指向地址结构体字节数的指针
示例
sockaddr_in clientAddr = {0};
socklen_t clientAddrLen = sizeof clientAddr;
int clientfd = accept(sockfd,(sockaddr*)&clientAddr,&clientAddrLen);
send
给面向连接的套接字发送数据的函数
如果不额外设置会阻塞到消息完全发出为止
发送成功返回发送数据的大小(以字节数表示),发生错误返回-1
ssize_t send(int sockfd, const void *buf, size_t len, int flags)
sockfd
套接字的文件描述符
buf
指向要发送的数据的缓冲区的指针
len
要发送的数据的长度(以字节数表示)
flags
发送数据时标记
MSG_DONTROUTE
:不查找路由表,直接发送数据MSG_OOB
:发送带外数据(Out-of-band data)MSG_NOSIGNAL
:忽略SIGPIPE
信号,如果发送的数据超出接收端的缓冲区大小,不会导致程序终止MSG_MORE
:表示还有更多数据待发送,用于 TCP 的流控制优化
示例
send(clientfd,"Hei Client",sizeof("Hei Client"),MSG_MORE|MSG_OOB);
// 使用(|)运算符设置两个标记
sendto
一般给不连接的套接字用来发送报文
默认为阻塞模式
发送成功返回发送数据的大小(以字节数表示),发生错误返回-1
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
sockfd
套接字的文件描述符
buf
指向要发送的数据的缓冲区指针
len
要发送的数据的长度(以字节数表示)
flags
发送数据时的标记
MSG_DONTROUTE
:不查找路由表,直接发送数据MSG_OOB
:发送带外数据(Out-of-band data)MSG_NOSIGNAL
:忽略SIGPIPE
信号,如果发送的数据超出接收端的缓冲区大小,不会导致程序终止MSG_DONTWAIT
:非阻塞模式发送,函数调用将立即返回,而不管是否能够发送全部数据,如果不能立即发送,则可能会发送部分数据
dest_addr
指向sockaddr结构体的指针,结构体内存放的是数据的目标地址与端口
addrlen
地址结构体的长度(以字节数表示)
示例
sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_port = htons(8989);
addr.sin_addr = inet_addr("143.131.180.94");
sendto(sockfd,"Hei Udp Client",sizeof("Hei Udp Client"),0,(sockaddr*)&addr,sizeof addr);
// 0 代表不使用任何标志参数
sendmsg
用于套接字发送数据
默认为阻塞模式
发送成功返回发送数据的大小(以字节数表示),发生错误返回-1
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
sockfd
套接字的文件描述符
msg
指向要发送的msghdr结构体的指针
flags
发送数据时的标记
MSG_DONTWAIT
:以非阻塞方式发送消息,即使发送缓冲区已满也立即返回MSG_EOR
:表示消息中的最后一个缓冲区的结束MSG_MORE
:表示消息中还有更多的数据要发送MSG_NOSIGNAL
:禁止在发送失败时产生SIGPIPE
信号,而是返回错误码,以避免由于对端关闭连接而引发的异常信号
示例
msghdr msg = {0};
iovec iov[2];
char message1[12] = "Hei Client!";
char message2[23] = "Hi Message from server";
iov[0].iov_base = message1;
iov[1].iov_base = message2;
iov[0].iov_len = sizeof message1;
iov[1].iov_len = sizeof message2;
msg.msg_iov = iov;
msg.msg_iovlen = 2;
sendmsg(sockfd, &msg, 0);
recv
用于套接字接收数据
如果不额外设置会阻塞到接收到数据
成功则返回接收到的数据的字节数(以字节数表示),失败则返回-1
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
sockfd
套接字的文件描述符
buf
指向用来接收数据的缓冲区指针
len
缓冲区的大小或可接收的最大字节数
flags
指定接收时的额外选项
MSG_DONTWAIT
:非阻塞模式 即使没有可用的数据,也立即返回结果,不会阻塞等待数据到达MSG_PEEK
:表示从接收队列中查看而不移除数据,可以用来检查接收缓冲区中的数据而不实际读取它们MSG_WAITALL
:接收过程中一直等待请求的数据直到满足条件,如果没有足够的数据,则阻塞等待MSG_OOB
:表示接收或发送带外数据(Out-of-Band) 这种数据具有高优先级,通常在正常数据之外进行处理
示例
char buffer[1024] = {0};
recv(sockfd,buffer,1024,0); // 0 的意思没有指定额外的选项
recvfrom
报式套接字用来接收数据的函数
默认为阻塞模式
成功则返回接收到的数据的字节数(以字节数表示),失败则返回-1
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
sockfd
套接字的文件描述符
buf
指向接收数据的缓冲区的指针
len
缓冲区的长度或可接收的最大字节数
flags
指定接收时的额外选项
MSG_TRUNC
:如果接收缓冲区的大小小于接收到的数据报的大小,则截断数据MSG_ERRQUEUE
:从错误队列接收错误消息MSG_CMSG_CLOEXEC
:设置接收的辅助数据(控制消息)以FD_CLOEXEC
标志关闭MSG_DONTWAIT
:非阻塞模式 即使没有可用的数据,也立即返回结果,不会阻塞等待数据到达MSG_PEEK
:表示从接收队列中查看而不移除数据,可以用来检查接收缓冲区中的数据而不实际读取它们MSG_WAITALL
:接收过程中一直等待请求的数据直到满足条件,如果没有足够的数据,则阻塞等待
示例
char buffer[1024] = {0};
sockaddr_in addr = {0};
socklen_t addrLen = sizeof addr;
recvfrom(sockfd,addr,1024,MSG_WAITALL|MSG_PEEK,(sockaddr*)&addr,&addrLen);
recvmsg
用于套接字接收数据
默认为阻塞模式
成功返回接收到的数据的长度(以字节数表示),不成功返回-1
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
sockfd
套接字的文件描述符
msg
指向用来接收数据的msghdr结构体的指针
flags
指定接收时的额外选项
MSG_CMSG_CLOEXEC
:在接收到控制消息(cmsg)时自动将其标记为FD_CLOEXEC
,使得接收的文件描述符在执行exec
系列函数时会被关闭MSG_DONTWAIT
:设置非阻塞模式,即使没有可用的数据,也立即返回MSG_ERRQUEUE
:接收错误消息队列中的消息 这个标志通常用于接收一些与套接字操作相关的错误信息,例如 ICMP 错误消息MSG_OOB
:表示接收或发送带外数据(Out-of-Band) 这种数据具有高优先级,通常在正常数据之外进行处理MSG_PEEK
:表示从接收队列中查看而不移除数据,可以用来检查接收缓冲区中的数据而不实际读取它们MSG_TRUNC
:如果接收缓冲区大小小于接收到的消息的大小,则将该标志设置为指示消息被截断MSG_WAITALL
:接收过程中一直等待请求的数据直到满足条件,如果没有足够的数据,则阻塞等待MSG_DONTWAIT
:非阻塞模式 即使没有可用的数据,也立即返回结果,不会阻塞等待数据到达
示例
msghdr msg = {0};
recvmsg(sockfd, &msg, MSG_OOB);
printf("%s\n",(char*)msg.msg_control);
for (int i = 0; i < msg.msg_iovlen; i++) {
printf("%s\n",(char*)msg.msg_iov[i].iov_base);
}
About this Post
This post is written by JinHong Zeng, licensed under CC BY-NC 4.0.