Socket 函数及其配套函数详解
在C语言的网络编程中,套接字(Socket) 是实现进程间通信的核心机制。除了 socket()
函数外,还有一系列配套函数用于管理和操作套接字,如 bind()
、listen()
、accept()
、connect()
、send()
、recv()
、sendto()
、recvfrom()
、setsockopt()
、getsockopt()
等。本文将详细讲解这些函数的用途、参数及其使用方法,通过代码片段辅助说明,帮助您全面理解套接字编程。
目录
socket()
函数详解bind()
函数详解listen()
函数详解accept()
函数详解connect()
函数详解send()
和recv()
函数详解sendto()
和recvfrom()
函数详解setsockopt()
和getsockopt()
函数详解close()
函数详解- 常见套接字选项
- 总结
1. socket()
函数详解
函数原型
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
参数详解
domain
:协议族,用于指定通信协议的地址族。- 常用值:
AF_INET
:IPv4协议族。AF_INET6
:IPv6协议族。AF_UNIX
(或AF_LOCAL
):本地通信协议族,适用于同一主机内的进程间通信(IPC)。AF_PACKET
:用于访问数据链路层,主要用于Linux。
- 常用值:
type
:套接字类型,定义了套接字的通信模式。- 常用值:
SOCK_STREAM
:流套接字,提供面向连接、可靠的字节流服务,通常用于TCP。SOCK_DGRAM
:数据报套接字,提供无连接、不可靠的消息传输,通常用于UDP。SOCK_RAW
:原始套接字,允许直接访问网络层协议,适用于高级网络应用和工具。SOCK_SEQPACKET
:顺序数据包套接字,提供面向连接的可靠消息传输。SOCK_RDM
:可靠数据报套接字,提供可靠但无序的数据传输。
- 常用值:
protocol
:具体协议,通常可以设为0以自动选择。- 常用值:
IPPROTO_TCP
:传输控制协议(TCP)。IPPROTO_UDP
:用户数据报协议(UDP)。IPPROTO_ICMP
:互联网控制消息协议(ICMP)。IPPROTO_RAW
:原始IP协议,用于原始套接字。0
:让系统自动选择合适的协议。
- 常用值:
返回值
- 成功:返回一个非负的套接字描述符(文件描述符),用于后续的网络操作。
- 失败:返回
-1
,并设置errno
以指示错误原因。
示例片段
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
2. bind()
函数详解
函数原型
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数详解
sockfd
:套接字描述符,由socket()
函数返回。addr
:指向sockaddr
结构体的指针,包含要绑定的地址信息。- 具体协议族对应的结构体:
- 对于
AF_INET
,使用struct sockaddr_in
。 - 对于
AF_INET6
,使用struct sockaddr_in6
。
- 对于
- 具体协议族对应的结构体:
addrlen
:地址结构体的长度,以字节为单位。
返回值
- 成功:返回
0
。 - 失败:返回
-1
,并设置errno
。
示例片段
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY; // 绑定到所有可用接口
server_addr.sin_port = htons(8080); // 端口号
if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("bind failed");
close(sockfd);
exit(EXIT_FAILURE);
}
3. listen()
函数详解
函数原型
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
参数详解
sockfd
:已绑定地址的套接字描述符。backlog
:指定在套接字接受队列中的最大连接数。- 作用:定义在套接字被
accept()
调用前可以排队的最大连接数。 - 注意:实际允许的连接数可能受操作系统限制。
- 作用:定义在套接字被
返回值
- 成功:返回
0
。 - 失败:返回
-1
,并设置errno
。
示例片段
if (listen(sockfd, 10) == -1) { // 允许最多10个排队连接
perror("listen failed");
close(sockfd);
exit(EXIT_FAILURE);
}
4. accept()
函数详解
函数原型
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数详解
sockfd
:正在监听连接的套接字描述符。addr
:指向sockaddr
结构体的指针,用于存储连接的客户端地址信息。- 具体协议族对应的结构体:
- 对于
AF_INET
,使用struct sockaddr_in
。 - 对于
AF_INET6
,使用struct sockaddr_in6
。
- 对于
- 可选:如果不需要客户端地址信息,可以设置为
NULL
。
- 具体协议族对应的结构体:
addrlen
:指向socklen_t
变量的指针,表示addr
结构体的大小。- 可选:如果
addr
为NULL
,可以设置为NULL
。
- 可选:如果
返回值
- 成功:返回一个新的套接字描述符,用于与客户端通信。
- 失败:返回
-1
,并设置errno
。
示例片段
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int connfd = accept(sockfd, (struct sockaddr *)&client_addr, &client_len);
if (connfd == -1) {
perror("accept failed");
close(sockfd);
exit(EXIT_FAILURE);
}
// 可选:获取客户端IP和端口
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);
int client_port = ntohs(client_addr.sin_port);
printf("Connection from %s:%d\n", client_ip, client_port);
5. connect()
函数详解
函数原型
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数详解
sockfd
:已创建的套接字描述符。addr
:指向sockaddr
结构体的指针,包含要连接的服务器地址信息。- 具体协议族对应的结构体:
- 对于
AF_INET
,使用struct sockaddr_in
。 - 对于
AF_INET6
,使用struct sockaddr_in6
。
- 对于
- 具体协议族对应的结构体:
addrlen
:地址结构体的长度,以字节为单位。
返回值
- 成功:返回
0
。 - 失败:返回
-1
,并设置errno
。
示例片段
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080);
if (inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr) <= 0) {
fprintf(stderr, "Invalid address/ Address not supported\n");
close(sockfd);
exit(EXIT_FAILURE);
}
if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("connect failed");
close(sockfd);
exit(EXIT_FAILURE);
}
printf("Connected to the server.\n");
6. send()
和 recv()
函数详解
send()
函数
函数原型
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数详解
sockfd
:套接字描述符,通常是已连接的套接字。buf
:指向要发送数据的缓冲区。len
:要发送的数据长度,以字节为单位。flags
:发送标志,控制发送行为。- 常用值:
0
:默认,无特殊标志。MSG_DONTWAIT
:非阻塞发送。MSG_CONFIRM
:用于确认目的地。MSG_MORE
:表示数据后面还有更多数据。
- 常用值:
返回值
- 成功:返回实际发送的字节数。
- 失败:返回
-1
,并设置errno
。
示例片段
const char *message = "Hello, Server!";
ssize_t bytes_sent = send(sockfd, message, strlen(message), 0);
if (bytes_sent == -1) {
perror("send failed");
} else {
printf("Sent %zd bytes to server.\n", bytes_sent);
}
recv()
函数
函数原型
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数详解
sockfd
:套接字描述符,通常是已连接的套接字。buf
:指向接收数据的缓冲区。len
:缓冲区的大小,以字节为单位。flags
:接收标志,控制接收行为。- 常用值:
0
:默认,无特殊标志。MSG_DONTWAIT
:非阻塞接收。MSG_PEEK
:窥视数据,不从缓冲区移除。MSG_WAITALL
:等待所有数据到达。
- 常用值:
返回值
- 成功:返回实际接收的字节数。
0
表示对方关闭了连接。 - 失败:返回
-1
,并设置errno
。
示例片段
char buffer[1024];
ssize_t bytes_received = recv(sockfd, buffer, sizeof(buffer)-1, 0);
if (bytes_received == -1) {
perror("recv failed");
} else if (bytes_received == 0) {
printf("Server closed the connection.\n");
} else {
buffer[bytes_received] = '\0'; // 确保字符串终止
printf("Received from server: %s\n", buffer);
}
7. sendto()
和 recvfrom()
函数详解
这些函数通常用于无连接的套接字(如UDP),允许在发送和接收数据时指定目标地址或获取源地址。
sendto()
函数
函数原型
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
参数详解
sockfd
:套接字描述符,通常是UDP套接字。buf
:指向要发送数据的缓冲区。len
:要发送的数据长度,以字节为单位。flags
:发送标志,控制发送行为。dest_addr
:指向目标地址的sockaddr
结构体指针。addrlen
:地址结构体的长度,以字节为单位。
返回值
- 成功:返回实际发送的字节数。
- 失败:返回
-1
,并设置errno
。
示例片段
struct sockaddr_in dest_addr;
memset(&dest_addr, 0, sizeof(dest_addr));
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &dest_addr.sin_addr);
const char *message = "Hello, UDP Server!";
ssize_t bytes_sent = sendto(sockfd, message, strlen(message), 0,
(struct sockaddr *)&dest_addr, sizeof(dest_addr));
if (bytes_sent == -1) {
perror("sendto failed");
} else {
printf("Sent %zd bytes to server.\n", bytes_sent);
}
recvfrom()
函数
函数原型
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
参数详解
sockfd
:套接字描述符,通常是UDP套接字。buf
:指向接收数据的缓冲区。len
:缓冲区的大小,以字节为单位。flags
:接收标志,控制接收行为。src_addr
:指向源地址的sockaddr
结构体指针,用于存储发送方的地址信息。addrlen
:指向socklen_t
变量的指针,表示src_addr
结构体的大小。
返回值
- 成功:返回实际接收的字节数。
- 失败:返回
-1
,并设置errno
。
示例片段
char buffer[1024];
struct sockaddr_in src_addr;
socklen_t src_len = sizeof(src_addr);
ssize_t bytes_received = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0,
(struct sockaddr *)&src_addr, &src_len);
if (bytes_received == -1) {
perror("recvfrom failed");
} else {
buffer[bytes_received] = '\0';
char src_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &src_addr.sin_addr, src_ip, INET_ADDRSTRLEN);
int src_port = ntohs(src_addr.sin_port);
printf("Received from %s:%d - %s\n", src_ip, src_port, buffer);
}
8. setsockopt()
和 getsockopt()
函数详解
这些函数用于设置和获取套接字选项,控制套接字的行为和性能。
setsockopt()
函数
函数原型
#include <sys/types.h>
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
参数详解
sockfd
:套接字描述符。level
:设置选项的协议层级。- 常用值:
SOL_SOCKET
:套接字层选项。IPPROTO_TCP
:TCP协议层选项。IPPROTO_IP
:IP协议层选项。
- 常用值:
optname
:选项名称,指定要设置的选项。- 常用选项:
SO_REUSEADDR
:允许重用本地地址。SO_KEEPALIVE
:启用心跳检测。SO_BROADCAST
:允许发送广播消息。TCP_NODELAY
:禁用Nagle算法,减少延迟。
- 常用选项:
optval
:指向包含新选项值的缓冲区的指针。optlen
:选项值的大小,以字节为单位。
返回值
- 成功:返回
0
。 - 失败:返回
-1
,并设置errno
。
示例片段
启用地址重用:
int opt = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
perror("setsockopt SO_REUSEADDR failed");
close(sockfd);
exit(EXIT_FAILURE);
}
禁用Nagle算法:
int flag = 1;
if (setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(int)) < 0) {
perror("setsockopt TCP_NODELAY failed");
close(sockfd);
exit(EXIT_FAILURE);
}
getsockopt()
函数
函数原型
#include <sys/types.h>
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname,
void *optval, socklen_t *optlen);
参数详解
sockfd
:套接字描述符。level
:获取选项的协议层级。optname
:选项名称,指定要获取的选项。optval
:指向用于接收选项值的缓冲区的指针。optlen
:指向一个变量的指针,该变量应包含optval
缓冲区的大小。成功后,该变量将包含实际的选项值长度。
返回值
- 成功:返回
0
。 - 失败:返回
-1
,并设置errno
。
示例片段
获取套接字选项:
int opt;
socklen_t optlen = sizeof(opt);
if (getsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, &optlen) == -1) {
perror("getsockopt SO_REUSEADDR failed");
} else {
printf("SO_REUSEADDR is %s\n", (opt ? "ON" : "OFF"));
}
9. close()
函数详解
函数原型
#include <unistd.h>
int close(int fd);
参数详解
fd
:要关闭的文件描述符,包括套接字描述符。
返回值
- 成功:返回
0
。 - 失败:返回
-1
,并设置errno
。
示例片段
if (close(sockfd) == -1) {
perror("close failed");
}
10. 常见套接字选项
了解常用的套接字选项有助于更好地控制套接字行为和性能。
SO_REUSEADDR
描述:允许重新使用本地地址,尤其是在服务器重启后,可以立即重新绑定到相同的端口。
类型:布尔型(
int
)。设置示例:
int opt = 1; setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
SO_KEEPALIVE
描述:启用或禁用心跳检测,确保连接的有效性。
类型:布尔型(
int
)。设置示例:
int opt = 1; setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt));
TCP_NODELAY
描述:禁用Nagle算法,减少延迟,适用于实时应用。
类型:布尔型(
int
)。设置示例:
int flag = 1; setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(int));
SO_BROADCAST
描述:允许发送广播消息。
类型:布尔型(
int
)。设置示例:
int opt = 1; setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt));
SO_RCVBUF
和 SO_SNDBUF
描述:设置接收和发送缓冲区的大小。
类型:整数(
int
)。设置示例:
int rcvbuf = 65536; // 64KB setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)); int sndbuf = 65536; // 64KB setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf));
SO_LINGER
描述:控制套接字关闭时的行为,决定是否等待未发送的数据发送完成。
类型:
struct linger
。设置示例:
struct linger so_linger; so_linger.l_onoff = 1; // 启用 linger so_linger.l_linger = 10; // 超时时间,单位秒 setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof(so_linger));
IP_TTL
描述:设置IP数据包的生存时间(TTL),控制数据包在网络中的跳数。
类型:整数(
int
)。设置示例:
int ttl = 64; setsockopt(sockfd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl));
IP_MULTICAST_TTL
描述:设置多播数据包的TTL。
类型:整数(
int
)。设置示例:
int multicast_ttl = 1; setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_TTL, &multicast_ttl, sizeof(multicast_ttl));
SO_RCVTIMEO
和 SO_SNDTIMEO
描述:设置接收和发送操作的超时时间。
类型:
struct timeval
。设置示例:
struct timeval timeout; timeout.tv_sec = 5; // 5秒 timeout.tv_usec = 0; setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
11. 总结
套接字编程是C语言中实现网络通信的基础,通过 socket()
函数创建套接字,并结合一系列配套函数(如 bind()
、listen()
、accept()
、connect()
、send()
、recv()
等)实现不同类型的网络应用。以下是关键要点的总结:
- 创建套接字:使用
socket(domain, type, protocol)
,指定协议族、套接字类型和协议。 - 绑定地址:服务器端使用
bind()
将套接字与特定IP地址和端口号关联。 - 监听连接:服务器端使用
listen()
准备接受客户端的连接请求。 - 接受连接:服务器端使用
accept()
接受客户端的连接,返回新的套接字描述符用于通信。 - 连接到服务器:客户端使用
connect()
连接到服务器的套接字。 - 数据传输:
- 使用
send()
和recv()
在已连接的套接字上发送和接收数据。 - 使用
sendto()
和recvfrom()
在无连接的套接字(如UDP)上发送和接收数据,指定目标或获取源地址。
- 使用
- 设置套接字选项:使用
setsockopt()
和getsockopt()
控制套接字行为,如缓冲区大小、超时、重用地址等。 - 关闭套接字:使用
close()
释放套接字资源。
选择合适的套接字类型
SOCK_STREAM
(TCP):- 适用于需要可靠、顺序传输的数据,如HTTP、FTP、SMTP等。
- 面向连接,确保数据的完整性和顺序。
SOCK_DGRAM
(UDP):- 适用于对实时性要求高、能容忍数据丢失的应用,如视频流、在线游戏、DNS查询等。
- 无连接,不保证数据的可靠传输。
SOCK_RAW
(原始套接字):- 适用于需要直接访问网络层协议的数据包,如开发网络工具(ping、traceroute)、网络监控、安全应用等。
- 需要超级用户权限,涉及高级网络编程和安全性考虑。
错误处理与资源管理
- 错误检查:每次调用套接字函数后,应检查返回值是否为错误,并根据
errno
进行相应处理。 - 资源释放:使用完套接字后,务必调用
close()
释放资源,避免资源泄漏。 - 权限管理:操作原始套接字等需要高权限的功能时,确保应用程序的安全性和权限控制。
安全性考虑
- 数据验证:确保所有接收的数据经过严格验证,防止缓冲区溢出和注入攻击。
- 权限控制:限制应用程序的权限,避免未经授权的网络访问。
- 使用安全的套接字选项:如启用
SO_REUSEADDR
、禁用TCP_NODELAY
等,根据应用需求进行优化。
高级应用
- 多套接字管理:在需要同时处理多个套接字时,使用
select()
、poll()
、epoll()
等I/O复用机制高效管理。 - 非阻塞模式:将套接字设置为非阻塞模式,适用于需要并发处理的应用,如高性能服务器。
通过深入理解和正确使用这些套接字函数及其参数,您可以构建功能丰富、高效且安全的网络应用程序。掌握套接字编程不仅有助于开发网络服务和客户端,还为进一步学习网络协议和系统编程奠定坚实基础。
如果您有更多关于套接字编程或其他网络编程相关的问题,欢迎继续提问!