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;