一、socket

socket() 函数是进行网络编程的基础,它用于创建一个新的套接字(socket)。套接字是网络通信的端点,可以用于在不同计算机之间传输数据。下面是对 socket() 函数的详细解释:

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
参数

socket() 函数有三个参数:

  1. domain(协议域):指定套接字使用的协议族。常见的值包括:
  • AF_INET:IPv4协议
  • AF_INET6:IPv6协议
  • AF_UNIX(或 AF_LOCAL):本地通信(同一台机器上的进程间通信)
  1. type(套接字类型):指定套接字的类型。常见的值包括:
  • SOCK_STREAM:面向连接的流式套接字,使用TCP协议
  • SOCK_DGRAM:无连接的数据报套接字,使用UDP协议
  • SOCK_RAW:原始套接字,允许对底层协议直接访问
  1. protocol(协议):指定使用的协议。常用值包括:
  • 0:通常用于选择默认协议。例如,当 domainAF_INETtypeSOCK_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() 函数有三个参数:

  1. sockfd:由 socket() 函数返回的套接字描述符。
  2. addr:指向 sockaddr 结构的指针,包含了要绑定的地址和端口信息。
  3. addrlensockaddr 结构的长度。

其中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;
}

在上面的示例中,我们执行了以下步骤:

  1. 使用 socket() 函数创建一个套接字。
  2. 初始化 sockaddr_in 结构,将地址族设置为 AF_INET,IP 地址设置为 INADDR_ANY(这意味着绑定到所有可用的接口),端口号设置为 8080(使用 htons() 函数将端口号从主机字节序转换为网络字节序)。
  3. 使用 bind() 函数将套接字绑定到指定的地址和端口。
  4. 如果绑定成功,输出成功信息;否则,输出错误信息。
  5. 关闭套接字。

bind() 函数在服务器端使用较多,客户端通常不需要显式调用这个函数,因为操作系统会在 connect() 函数调用时自动选择一个合适的端口。

三、listen

listen() 函数用于将一个套接字设置为被动模式,即它将成为一个服务器套接字,可以接受来自客户端的连接请求。这个函数在服务器端使用,是建立一个TCP服务器的重要步骤之一。

#include <sys/socket.h>

int listen(int sockfd, int backlog);
参数

listen() 函数有两个参数:

  1. sockfd:由 socket() 函数返回的套接字描述符。
  2. backlog:定义了内核为此套接字排队的最大连接数。如果连接请求的数量超过此值,则新的连接请求可能会被拒绝。
返回值

listen() 函数成功时返回 0,失败时返回 -1 并设置 errno 来指示错误。

使用步骤

在服务器端,典型的步骤是:

  1. 创建套接字 (socket()).
  2. 绑定套接字到本地地址和端口 (bind()).
  3. 将套接字设置为监听模式 (listen()).
  4. 接受客户端连接 (accept()).

四、accept()

accept() 函数用于在服务器端接受一个客户端的连接请求。它从已完成连接队列中取出下一个连接,并为新的连接创建一个新的套接字。accept() 是阻塞调用,直到有新的连接进来。

#include <sys/types.h>
#include <sys/socket.h>

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

accept() 函数有三个参数:

  1. sockfd:由 socket()bind() 函数创建并由 listen() 函数设置为监听模式的套接字描述符。
  2. addr:指向 sockaddr 结构体的指针,接受连接的客户端的地址信息。可以是 sockaddr_in(对于IPv4)或 sockaddr_in6(对于IPv6)结构体。
  3. 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_DONTWAITMSG_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的关闭顺序

在网络编程中,正确关闭套接字对于释放资源和确保连接的正常终止非常重要。套接字关闭的顺序通常如下:

  1. 客户端关闭连接:客户端在完成所有数据发送和接收后,首先关闭自己的套接字。
  2. 服务器关闭连接:服务器在检测到客户端已经关闭连接之后,关闭相应的客户端套接字。

十、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:指向包含选项值的缓冲区。

  • optlenoptval 缓冲区的大小。

返回值:成功时返回 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;

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部