socket()
函数是进行网络编程的基础,它用于创建一个新的套接字(socket)。套接字是网络通信的端点,可以用于在不同计算机之间传输数据。下面是对 socket()
函数的详细解释:
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
socket()
函数有三个参数:
AF_INET
:IPv4协议AF_INET6
:IPv6协议AF_UNIX
(或 AF_LOCAL
):本地通信(同一台机器上的进程间通信)SOCK_STREAM
:面向连接的流式套接字,使用TCP协议SOCK_DGRAM
:无连接的数据报套接字,使用UDP协议SOCK_RAW
:原始套接字,允许对底层协议直接访问0
:通常用于选择默认协议。例如,当 domain
是 AF_INET
且 type
是 SOCK_STREAM
时,默认协议是TCP。socket()
函数成功时返回一个套接字描述符(非负整数),失败时返回 -1
并设置 errno
来指示错误。
bind()
函数用于将套接字绑定到一个本地地址和端口。对于服务器端套接字,这是必需的步骤,因为它指定了服务器将在其上监听连接请求的地址和端口。
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
bind()
函数有三个参数:
socket()
函数返回的套接字描述符。sockaddr
结构的指针,包含了要绑定的地址和端口信息。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()
函数用于将一个套接字设置为被动模式,即它将成为一个服务器套接字,可以接受来自客户端的连接请求。这个函数在服务器端使用,是建立一个TCP服务器的重要步骤之一。
#include <sys/socket.h>
int listen(int sockfd, int backlog);
listen()
函数有两个参数:
socket()
函数返回的套接字描述符。listen()
函数成功时返回 0
,失败时返回 -1
并设置 errno
来指示错误。
在服务器端,典型的步骤是:
socket()
).bind()
).listen()
).accept()
).accept()
函数用于在服务器端接受一个客户端的连接请求。它从已完成连接队列中取出下一个连接,并为新的连接创建一个新的套接字。accept()
是阻塞调用,直到有新的连接进来。
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
accept()
函数有三个参数:
socket()
和 bind()
函数创建并由 listen()
函数设置为监听模式的套接字描述符。sockaddr
结构体的指针,接受连接的客户端的地址信息。可以是 sockaddr_in
(对于IPv4)或 sockaddr_in6
(对于IPv6)结构体。socklen_t
类型的变量,它在调用时指定 addr
结构的大小,并在返回时被设置为客户端地址的实际大小。accept()
函数成功时返回一个新的套接字描述符(非负整数),用于与客户端通信;失败时返回 -1
并设置 errno
来指示错误。
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);
socket()
创建的套接字文件描述符。sockaddr
结构体。成功时返回 0
,失败时返回 -1
并设置 errno
。
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
。
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
fd
:文件描述符,可以是套接字、文件、管道等。buf
:指向用于存储接收到的数据的缓冲区。count
:缓冲区的长度。成功时返回读取的字节数,失败时返回 -1
并设置 errno
。
功能范围:
recv()
专门用于套接字通信,并且可以指定额外的标志来控制接收行为。read()
是一个通用的系统调用,可以用于任何文件描述符,包括套接字、文件、管道等。标志选项:
recv()
允许使用 flags
参数来指定额外的控制选项,例如 MSG_DONTWAIT
、MSG_PEEK
等。read()
没有 flags
参数,因此不提供额外的控制选项。使用场景:
recv()
。read()
。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);
0
(默认)和 MSG_DONTWAIT
(非阻塞模式)。成功时返回发送的字节数,失败时返回 -1
并设置 errno
。
close()
函数用于关闭一个打开的文件描述符,这里包括套接字。关闭一个套接字会释放它占用的所有资源。对于网络编程来说,close()
是一个重要的步骤,因为它会终止与该套接字相关的所有网络连接。
close()
的使用close()
是一个非常简单的系统调用,用于关闭文件描述符。它的定义如下:
#include <unistd.h>
int close(int fd);
成功时返回 0
,失败时返回 -1
并设置 errno
。
在网络编程中,正确关闭套接字对于释放资源和确保连接的正常终止非常重要。套接字关闭的顺序通常如下:
setsockopt()
函数用于设置套接字选项。它可以控制套接字的行为,如允许端口复用、设置超时时间、控制数据包的发送和接收缓冲区大小等。
#include <sys/types.h>
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
SOL_SOCKET
:适用于通用套接字选项。IPPROTO_TCP
:适用于 TCP 特定选项。IPPROTO_IP
:适用于 IP 特定选项。optval
缓冲区的大小。
返回值:成功时返回 0
,失败时返回 -1
并设置 errno
。
以下是一些常用的 setsockopt()
中optname选项:
这五个常用的选项,对应的optval都是int选项SO_RCVBUF SO_SNDBUF 对应的int是缓存区的大小,其他的是1
(启用),0
(禁用)。
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;