一、socket
socket()
函数是进行网络编程的基础,它用于创建一个新的套接字(socket)。套接字是网络通信的端点,可以用于在不同计算机之间传输数据。下面是对 socket()
函数的详细解释:
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
参数
socket()
函数有三个参数:
- domain(协议域):指定套接字使用的协议族。常见的值包括:
AF_INET
:IPv4协议AF_INET6
:IPv6协议AF_UNIX
(或AF_LOCAL
):本地通信(同一台机器上的进程间通信)
- type(套接字类型):指定套接字的类型。常见的值包括:
SOCK_STREAM
:面向连接的流式套接字,使用TCP协议SOCK_DGRAM
:无连接的数据报套接字,使用UDP协议SOCK_RAW
:原始套接字,允许对底层协议直接访问
- protocol(协议):指定使用的协议。常用值包括:
0
:通常用于选择默认协议。例如,当domain
是AF_INET
且type
是SOCK_STREAM
时,默认协议是TCP。
返回值
socket()
函数成功时返回一个套接字描述符(非负整数),失败时返回 -1
并设置 errno
来指示错误。
bind()
bind()
函数用于将套接字绑定到一个本地地址和端口。对于服务器端套接字,这是必需的步骤,因为它指定了服务器将在其上监听连接请求的地址和端口。
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数
bind()
函数有三个参数:
- sockfd:由
socket()
函数返回的套接字描述符。- addr:指向
sockaddr
结构的指针,包含了要绑定的地址和端口信息。- addrlen:
sockaddr
结构的长度。
其中sockaddr
结构
在 IPv4 中,sockaddr
结构通常是 sockaddr_in
结构,它定义如下:
struct sockaddr_in {
sa_family_t sin_family; // 地址族 (AF_INET)//IPv4
in_port_t sin_port; // 端口号 (使用 htons() 转换)
struct in_addr sin_addr; // IP 地址
};
struct in_addr {
uint32_t s_addr; // 地址 (使用 inet_addr() 或 INADDR_ANY(它的值是 0.0.0.0,表示所有的 IPv4 地址。))
};
返回值
bind()
函数成功时返回 0
,失败时返回 -1
并设置 errno
来指示错误。
示例
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
#define PORT 8080
int main() {
int sockfd;
struct sockaddr_in address;
// 创建一个 IPv4 的 TCP 套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket creation failed");
return -1;
}
// 初始化地址结构
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY; // 绑定到所有可用接口
address.sin_port = htons(PORT); // 将端口号转换为网络字节序
// 绑定套接字到指定地址和端口
if (bind(sockfd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
close(sockfd);
return -1;
}
std::cout << "Bind successful" << std::endl;
// 关闭套接字
close(sockfd);
return 0;
}
在上面的示例中,我们执行了以下步骤:
- 使用
socket()
函数创建一个套接字。 - 初始化
sockaddr_in
结构,将地址族设置为AF_INET
,IP 地址设置为INADDR_ANY
(这意味着绑定到所有可用的接口),端口号设置为8080
(使用htons()
函数将端口号从主机字节序转换为网络字节序)。 - 使用
bind()
函数将套接字绑定到指定的地址和端口。 - 如果绑定成功,输出成功信息;否则,输出错误信息。
- 关闭套接字。
bind()
函数在服务器端使用较多,客户端通常不需要显式调用这个函数,因为操作系统会在 connect()
函数调用时自动选择一个合适的端口。
三、listen
listen()
函数用于将一个套接字设置为被动模式,即它将成为一个服务器套接字,可以接受来自客户端的连接请求。这个函数在服务器端使用,是建立一个TCP服务器的重要步骤之一。
#include <sys/socket.h>
int listen(int sockfd, int backlog);
参数
listen()
函数有两个参数:
- sockfd:由
socket()
函数返回的套接字描述符。- backlog:定义了内核为此套接字排队的最大连接数。如果连接请求的数量超过此值,则新的连接请求可能会被拒绝。
返回值
listen()
函数成功时返回 0
,失败时返回 -1
并设置 errno
来指示错误。
使用步骤
在服务器端,典型的步骤是:
- 创建套接字 (
socket()
).- 绑定套接字到本地地址和端口 (
bind()
).- 将套接字设置为监听模式 (
listen()
).- 接受客户端连接 (
accept()
).
四、accept()
accept()
函数用于在服务器端接受一个客户端的连接请求。它从已完成连接队列中取出下一个连接,并为新的连接创建一个新的套接字。accept()
是阻塞调用,直到有新的连接进来。
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数
accept()
函数有三个参数:
- sockfd:由
socket()
和bind()
函数创建并由listen()
函数设置为监听模式的套接字描述符。- addr:指向
sockaddr
结构体的指针,接受连接的客户端的地址信息。可以是sockaddr_in
(对于IPv4)或sockaddr_in6
(对于IPv6)结构体。- addrlen:指向一个
socklen_t
类型的变量,它在调用时指定addr
结构的大小,并在返回时被设置为客户端地址的实际大小。
返回值
accept()
函数成功时返回一个新的套接字描述符(非负整数),用于与客户端通信;失败时返回 -1
并设置 errno
来指示错误。
五、connect()
connect()
函数在客户端编程中起着关键作用。它用于将客户端的套接字连接到服务器的地址和端口。connect()
通过向服务器发送连接请求,并在服务器接受连接请求后,建立一个双向的通信通道。
connect()
的使用
connect()
函数通常在客户端使用,它将客户端的套接字连接到指定的服务器地址和端口。调用 connect()
时,客户端的套接字必须已经使用 socket()
函数创建。
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockfd:通过
socket()
创建的套接字文件描述符。- addr:指向包含服务器地址信息的
sockaddr
结构体。- addrlen:地址结构体的长度。
返回值
成功时返回 0
,失败时返回 -1
并设置 errno
。
六、recv
recv()
函数用于在连接建立后从套接字接收数据。它通常用于从服务器或客户端接收数据,可以在服务器端和客户端的通信中使用。
recv()
的使用
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
(非阻塞模式)。
返回值
成功时返回接收到的字节数,失败时返回 -1
并设置 errno
。
七、read
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
参数:
fd
:文件描述符,可以是套接字、文件、管道等。buf
:指向用于存储接收到的数据的缓冲区。count
:缓冲区的长度。
返回值
成功时返回读取的字节数,失败时返回 -1
并设置 errno
。
read与recv的区别
功能范围:
recv()
专门用于套接字通信,并且可以指定额外的标志来控制接收行为。read()
是一个通用的系统调用,可以用于任何文件描述符,包括套接字、文件、管道等。标志选项:
recv()
允许使用flags
参数来指定额外的控制选项,例如MSG_DONTWAIT
、MSG_PEEK
等。read()
没有flags
参数,因此不提供额外的控制选项。使用场景:
- 如果需要使用额外的控制选项或明确表示这是一个网络操作,通常使用
recv()
。- 如果只需要简单地从文件描述符读取数据且不需要额外控制选项,通常使用
read()
。
八、send
send()
函数用于向套接字发送数据。它与 recv()
对应,通常在服务器端和客户端的通信中使用。
send()
的使用
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
(非阻塞模式)。
返回值
成功时返回发送的字节数,失败时返回 -1
并设置 errno
。
九、close
close()
函数用于关闭一个打开的文件描述符,这里包括套接字。关闭一个套接字会释放它占用的所有资源。对于网络编程来说,close()
是一个重要的步骤,因为它会终止与该套接字相关的所有网络连接。
close()
的使用
close()
是一个非常简单的系统调用,用于关闭文件描述符。它的定义如下:
#include <unistd.h>
int close(int fd);
- fd:要关闭的文件描述符。
返回值
成功时返回 0
,失败时返回 -1
并设置 errno
。
close的关闭顺序
在网络编程中,正确关闭套接字对于释放资源和确保连接的正常终止非常重要。套接字关闭的顺序通常如下:
- 客户端关闭连接:客户端在完成所有数据发送和接收后,首先关闭自己的套接字。
- 服务器关闭连接:服务器在检测到客户端已经关闭连接之后,关闭相应的客户端套接字。
十、setsockopt()
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 表示通用套接字选项。常见的级别包括:
SOL_SOCKET
:适用于通用套接字选项。IPPROTO_TCP
:适用于 TCP 特定选项。IPPROTO_IP
:适用于 IP 特定选项。optname:需要设置的选项名称。
optval:指向包含选项值的缓冲区。
optlen:
optval
缓冲区的大小。
返回值:成功时返回 0
,失败时返回 -1
并设置 errno
。
常用选项
以下是一些常用的 setsockopt()
中optname选项:
- SO_REUSEADDR:允许重用本地地址和端口。
- SO_REUSEPORT:允许多个套接字绑定到同一个端口(在某些系统中可用)。
- SO_RCVBUF:设置接收缓冲区的大小。
- SO_SNDBUF:设置发送缓冲区的大小。
- SO_KEEPALIVE:启用保活机制,以检测断开的连接。
这五个常用的选项,对应的optval都是int选项SO_RCVBUF SO_SNDBUF 对应的int是缓存区的大小,其他的是1
(启用),0
(禁用)。
十一、fcntl
fcntl
函数在 Unix 系统中用于对文件描述符进行各种控制操作,包括设置非阻塞模式、获取和设置文件描述符标志等。在网络编程中,它通常用于设置套接字的非阻塞模式。
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
fd:文件描述符,即要进行操作的套接字或文件的句柄。
cmd:操作命令,指定要执行的操作,可以是以下之一:
F_GETFL
:获取文件状态标志。(此时第三个参数不是必需的,可以传递0
或者NULL
。)
F_SETFL
:设置文件状态标志。常见的有
非阻塞模式 (
O_NONBLOCK
):
- 作用:将套接字设置为非阻塞模式,使得读写操作不会阻塞进程,而是立即返回。适用于需要异步操作的场景,如同时处理多个连接或超时控制。
- 使用方式:
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
异步输入输出模式 (
O_ASYNC
):
- 作用:允许套接字接收到信号通知,表明有数据可读或写入完成。
- 使用方式:
fcntl(sockfd, F_SETFL, flags | O_ASYNC);
关闭非阻塞模式 (
O_NONBLOCK
的反操作):
- 作用:关闭套接字的非阻塞模式,使得读写操作会阻塞进程直到操作完成。
- 使用方式:
fcntl(sockfd, F_SETFL, flags & ~O_NONBLOCK);
示例代码
// 设置套接字为非阻塞模式
int flags = fcntl(sockfd, F_GETFL, 0);
if (flags == -1) {
perror("fcntl F_GETFL failed");
close(sockfd);
return -1;
}
if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) == -1) {
perror("fcntl F_SETFL failed");
close(sockfd);
return -1;