// 1. 创建监听的fd
int lfd = socket(AF_INET,SOCK_STREAM,0);

在 C 语言中使用套接字(socket)编程时,socket 函数是用于创建一个新的套接字描述符的关键函数。这个函数在 <sys/socket.h> 头文件中定义,并且通常需要三个参数来指定套接字的类型、地址族和协议。下面是 socket(AF_INET, SOCK_STREAM, 0) 中每个参数的详细讲解:

  1. 地址族(address family):AF_INET
    • AF_INET 表示使用 IPv4 地址族。IPv4 是目前广泛使用的互联网协议版本 4,它使用 32 位的地址来表示网络上的设备。
    • 还有其他地址族,如 AF_INET6 表示 IPv6(使用 128 位地址),AF_UNIX 表示 Unix 域套接字(用于在同一台机器上的不同进程之间的通信)。
  2. 套接字类型(socket type):SOCK_STREAM
    • SOCK_STREAM 表示创建一个面向连接的、可靠的字节流套接字。这种套接字类型通常用于 TCP(传输控制协议)通信。
    • 另一种常见的套接字类型是 SOCK_DGRAM,它表示创建一个无连接的、不可靠的数据报套接字,通常用于 UDP(用户数据报协议)通信。
  3. 协议(protocol):0
    • 第三个参数通常用于指定特定的协议。然而,当设置为 0 时,系统会根据前两个参数(地址族和套接字类型)自动选择最合适的协议。
    • 对于 AF_INET 和 SOCK_STREAM 的组合,系统通常会选择 TCP 协议。
    • 同样地,对于 AF_INET 和 SOCK_DGRAM 的组合,系统通常会选择 UDP 协议。

综上所述,socket(AF_INET, SOCK_STREAM, 0) 这行代码的作用是创建一个使用 IPv4 地址族、面向连接的 TCP 协议的套接字描述符,并将这个描述符存储在变量 lfd 中。这个套接字描述符随后可以用于绑定到特定的地址和端口、监听连接请求、接受连接以及进行数据的发送和接收。

 

// 2. 设置端口复用
    int opt = 1;
    int ret = setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt);
    if (ret == -1)
    {
        perror("setsockopt");
        return -1;
    }

知识点讲解

  1. 端口复用(SO_REUSEADDR):

    • 端口复用允许在同一端口上绑定多个套接字,前提是每次绑定时所用的地址(通常是IP地址和端口的组合)或套接字的其他属性(如协议)有所不同。

    • 当一个服务器程序终止后,操作系统通常会保留一段时间(称为“TIME_WAIT”状态)该程序所使用的端口,以防止由于网络延迟导致的迟到的数据包被新启动的、使用同一端口的程序错误地接收。

    • 通过设置SO_REUSEADDR选项,可以使得在程序终止后立即重启程序时,能够成功绑定到之前使用的端口,而不会因为端口处于“TIME_WAIT”状态而失败。

  2. setsockopt函数:

    • setsockopt函数用于设置套接字描述符的选项。

    • 语法:

      int setsockopt(int sockfd, int level, int option_name, const void *option_value, socklen_t option_len);

    • 参数:

      • sockfd:要设置选项的套接字描述符(在本例中是lfd)。

      • level:指明选项的级别。对于套接字层级的选项,通常使用SOL_SOCKET。

      • option_name:具体要设置的选项名,本例中是SO_REUSEADDR。

      • option_value:指向包含所需选项值的缓冲区。本例中是一个整数1,表示启用端口复用。

      • option_len:option_value所指缓冲区的长度,本例中是sizeof(opt),即sizeof(int),因为opt是一个整数。

  3. 错误处理:

    • setsockopt函数返回0表示成功,返回-1表示失败。

    • 如果setsockopt返回-1,则使用perror函数打印出错误信息,并返回-1以指示上层调用者发生了错误。

    • perror函数会根据全局变量errno的值打印出相应的错误信息。errno是在调用系统函数(如setsockopt)失败时被设置的,用于指示错误的具体原因。

总结

上述代码段通过设置SO_REUSEADDR选项来启用端口复用功能,以便在程序重启时能够立即绑定到之前使用的端口。如果设置选项失败,代码会打印出错误信息并返回-1。这是网络编程中常见的套接字选项设置之一,用于提高程序的健壮性和可用性。

 // 3. 绑定
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = INADDR_ANY;
    ret = bind(lfd, (struct sockaddr*)&addr, sizeof addr);
    if (ret == -1)
    {
        perror("bind");
        return -1;
    }

知识点讲解

  1. 结构体 sockaddr_in:

    • sockaddr_in 是用于描述 IPv4 套接字地址的结构体。

    • 它包含了地址族(sin_family)、端口号(sin_port)和 IP 地址(sin_addr)等信息。

  2. 地址族 AF_INET:

    • addr.sin_family = AF_INET; 表示我们使用的是 IPv4 地址族。

  3. 端口号 htons(port):

    • htons 函数用于将主机字节序的端口号转换为网络字节序。

    • port 是一个整数,表示我们想要绑定的端口号。

    • 在网络通信中,端口号用于区分同一台设备上运行的不同服务。

  4. IP 地址 INADDR_ANY:

    • addr.sin_addr.s_addr = INADDR_ANY; 表示我们愿意接受来自任何 IP 地址的连接。

    • INADDR_ANY 是一个特殊的 IP 地址,它告诉系统绑定到所有可用的网络接口上。

  5. bind 函数:

    • bind 函数用于将套接字描述符(lfd)绑定到一个特定的地址和端口上。

    • 语法:

      int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

    • 参数:

      • sockfd:要绑定的套接字描述符。

      • addr:指向包含所需地址信息的缓冲区的指针。在这里,我们将其转换为 sockaddr 类型的指针,因为 bind 函数期望的是这种类型。

      • addrlen:addr 所指缓冲区的长度。

  6. 错误处理:

    • 如果 bind 函数返回 -1,表示绑定操作失败。

    • 在这种情况下,代码使用 perror 函数打印出错误信息,并返回 -1 以指示上层调用者发生了错误。

总结

上述代码段的作用是将之前创建的套接字描述符 lfd 绑定到一个特定的端口上,并接受来自任何 IP 地址的连接。这是网络服务器程序中常见的步骤之一,用于准备监听来自客户端的连接请求。如果绑定操作失败,代码会打印出错误信息并返回 -1。

 // 4. 设置监听
    ret = listen(lfd, 128);
    if (ret == -1)
    {
        perror("listen");
        return -1;
    }

 

知识点讲解

  1. listen 函数:

    • listen 函数用于将一个套接字描述符(在这里是 lfd)设置为监听模式,准备接受进来的连接请求。

    • 语法:

      int listen(int sockfd, int backlog);

    • 参数:

      • sockfd:要设置为监听模式的套接字描述符。

      • backlog:指定在套接字排队的最大连接请求数。当请求数超过这个值时,新的连接请求将被拒绝。这里的 128 表示最多可以有 128 个连接请求在排队等待处理。

  2. 监听的作用:

    • 当一个套接字被设置为监听模式后,它就可以接受来自客户端的连接请求了。

    • 在服务器程序中,这通常是在绑定套接字到特定的地址和端口之后进行的下一步操作。

  3. 错误处理:

    • 如果 listen 函数返回 -1,表示设置监听失败。

    • 在这种情况下,代码使用 perror 函数打印出错误信息,并返回 -1 以指示上层调用者发生了错误。

总结

上述代码段的作用是将套接字描述符 lfd 设置为监听模式,并指定最多可以有 128 个连接请求在排队等待处理。这是网络服务器程序中常见的步骤之一,用于准备接受来自客户端的连接请求。如果设置监听失败,代码会打印出错误信息并返回 -1。

现在,服务器已经准备好接受连接请求了。当有客户端尝试连接到服务器时,服务器可以使用 accept 函数来接受连接,并创建一个新的套接字描述符来处理与客户端的通信。

完整代码:

int initListenFd(unsigned short port)
{
    // 1. 创建监听的fd
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if (lfd == -1)
    {
        perror("socket");
        return -1;
    }
    // 2. 设置端口复用
    int opt = 1;
    int ret = setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt);
    if (ret == -1)
    {
        perror("setsockopt");
        return -1;
    }
    // 3. 绑定
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = INADDR_ANY;
    ret = bind(lfd, (struct sockaddr*)&addr, sizeof addr);
    if (ret == -1)
    {
        perror("bind");
        return -1;
    }
    // 4. 设置监听
    ret = listen(lfd, 128);
    if (ret == -1)
    {
        perror("listen");
        return -1;
    }
    // 返回fd
    return lfd;
}

 

更多推荐