Linux下Socket编程中需要用到的函数以及结构体

本文讲究的就是一个实用,所以不会所有的东西都介绍,只会介绍常用的

注:阅读本文前最好还是有些网络编程的基础概念比如大端小端之类的

结构体

sockaddr_in

sockaddr_in是一个用来存放地址和端口的结构体,一般是IPV4协议使用

sin_family:存放使用的协议,必须是AF_INET(IPV4协议)

sin_port:存放的是端口

sin_addr:存放的是地址

sin_zero:我找到的解释是为了让sockaddr和sockaddr_in的大小一致,方便互相转换来占空间用的

1
2
3
4
5
6
7
8
9
10
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:地址范围的标识符,只有需要指定特定地址范围标识符时才会去显式设置其值

1
2
3
4
5
6
7
8
9
10
11
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指向的数据的长度(以字节数表示)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

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

传输协议

  1. SOCK_STREAM

    IPPROTO_TCP:TCP协议,一种面向连接的、可靠的、基于字节流的传输协议

  2. SOCK_DGRAM

    IPPROTO_UDP:UDP协议,一种无连接的、不可靠的,基于报文的传输协议

  3. SOCK_RAW

    IPPROTO_RAW:原始套接字协议,允许应用程序直接访问底层网络协议栈,可以用于构造自定义的网络协议
    IPPROTO_ICMP:互联网控制消息协议(ICMP),用于网络设备之间传递错误和控制消息
    IPPROTO_IP:IP协议,是基于IP地址的网络层协议,用于在网络中传输数据包

  4. SOCK_SEQPACKET

    IPPROTO_SCTP:流控制传输协议(SCTP)提供类似TCP的可靠性和有序性

示例

1
int sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

bind

给套接字绑定地址,如果成功返回0,发生错误就返回-1

1
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd

套接字的文件描述符

addr

指向sockaddr结构体的指针

addrlen

地址结构体的字节数

示例

1
2
3
4
5
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

1
int listen(int sockfd, int backlog);

sockfd

套接字的文件描述符

backlog

请求队列的最大数量

示例

1
listen(sockfd,128);

connect

流式套接字发送连接请求,连接到服务器就返回0,如果发生错误就返回-1

1
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd

套接字的文件描述符

addr

指向sockaddr结构体的指针

addrlen

地址结构体的长度(以字节数表示)

示例

1
2
3
4
5
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

1
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

sockfd

套接字文件描述符

addr

指向sockaddr结构体的指针,客户端的地址和端口会存储在里面

addrlen

指向地址结构体字节数的指针

示例

1
2
3
sockaddr_in clientAddr = {0};
socklen_t clientAddrLen = sizeof clientAddr;
int clientfd = accept(sockfd,(sockaddr*)&clientAddr,&clientAddrLen);

send

面向连接的套接字发送数据的函数

如果不额外设置会阻塞到消息完全发出为止

发送成功返回发送数据的大小(以字节数表示),发生错误返回-1

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 的流控制优化

示例

1
2
send(clientfd,"Hei Client",sizeof("Hei Client"),MSG_MORE|MSG_OOB);
// 使用(|)运算符设置两个标记

sendto

一般给不连接的套接字用来发送报文

默认为阻塞模式

发送成功返回发送数据的大小(以字节数表示),发生错误返回-1

1
2
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

地址结构体的长度(以字节数表示)

示例

1
2
3
4
5
6
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

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 信号,而是返回错误码,以避免由于对端关闭连接而引发的异常信号

示例

1
2
3
4
5
6
7
8
9
10
11
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

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) 这种数据具有高优先级,通常在正常数据之外进行处理

示例

1
2
char buffer[1024] = {0};
recv(sockfd,buffer,1024,0); // 0 的意思没有指定额外的选项

recvfrom

报式套接字用来接收数据的函数

默认为阻塞模式

成功则返回接收到的数据的字节数(以字节数表示),失败则返回-1

1
2
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:接收过程中一直等待请求的数据直到满足条件,如果没有足够的数据,则阻塞等待

示例

1
2
3
4
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

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:非阻塞模式 即使没有可用的数据,也立即返回结果,不会阻塞等待数据到达

示例

1
2
3
4
5
6
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);
}