加载中...

【C++服务器】day01笔记


server.cpp

int sockfd = socket(AF_INET, SOCK_STREAM, 0);
  • 第一个参数:地址族(Address Family),也就是 IP 地址类型。地址族的作用就是 指明使用哪一种地址类型 ,AF_LOCAL 表示使用本地地址规则来生成地址,而 AF_INET 表示使用IP地址规则来生成地址。

为什么套接字要用文件描述符来表示?

  • 根据 Unix/Linux 的设计哲学,“一切皆文件”,为了实现套接字和其他文件一样的可统一管理,设计者们选择使用文件描述符来表示套接字。使用文件描述符可以把套接字“伪装”成一个普通文件,从而使得用户能够使用熟悉的文件I/O操作来读写套接字。此外,套接字也可以通过文件描述符来进行传递,从而方便不同进程之间的数据交换。

为什么服务器端需要调用 bind 函数将套接字与服务器指定的 IP 地址和端口号绑定?

  • 服务器端需要 bind 操作是因为在建立 TCP 连接时,服务器端需要 监听指定的 IP 地址和端口号 ,从而能够接收客户端的请求并返回相应的数据。

listen 函数和 accept 函数会阻塞程序吗?

  • listen 函数本身不会阻塞程序,它通常在服务器程序中作为一个被动套接字使用。一旦调用了 listen 函数,服务器套接字就可以接受客户端连接请求。调用 listen 函数之前必须首先调用 bind 函数将服务器套接字与指定的地址和端口绑定,在 bind 函数返回成功后才能调用 listen 函数。否则,listen 函数将会返回一个错误。
  • 当客户端发起连接请求时,accept 函数会阻塞等待,直到收到客户端的连接请求后才返回一个新的套接字用于和客户端进行通信。因此,如果没有客户端连接请求到来,accept 函数会一直阻塞等待,这可能导致程序阻塞。但是,可以通过设置套接字为非阻塞模式来避免 accept 函数一直阻塞等待,同时可以通过 select、poll、epoll 等系统调用来实现多路复用,使得程序可以同时监听多个文件描述符的状态,从而更加高效地处理客户端连接请求。

代码:

注释版:

#include <sys/socket.h>
#include <arpa/inet.h>  // 该头文件包含另一个头文件 <netinet/in.h>。
#include <string.h>
#include <stdio.h>

int main() {
    // sockfd 是 socket 套接字,对外提供网络通信接口。也就是文件描述符,UNIX/Linux 中的一切都是文件,甚至网络连接也是一个文件。
    // 第一个参数是 IP 地址类型,现在表示使用 IPv4,AF_INET6 表示使用 IPv6 。
    // 第二个参数是数据传输方式,SOCK_STREAM 多用于 TCP,SOCK_DGRAM 多用于 UDP。
    // 第三个参数是协议,0 表示根据前面的两个参数自动推导协议类型。
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);

    // sockaddr_in 存网络通信地址,在头文件 <netinet/in.h> 中定义,把 port 和 addr 分开储存在两个变量中。
    struct sockaddr_in serv_addr;
    // bzero 函数将对应地址的前 n 个字节清零,在头文件 <cstring> 或 <string.h> 中定义,因为此处是写 C ,所以用后者。
    bzero(&serv_addr, sizeof(serv_addr));
    // 指代协议族,在 socket 编程中只能是 AF_INET 。
    serv_addr.sin_family = AF_INET;
    // 存储 IP 地址,使用 in_addr 这个数据结构,实际上在 in_addr 中只有 s_addr 这一个成员。
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    // 存储端口号。htons 负责将主机字节顺序转换为网络字节顺序,host to net,s 表示针对两个字节(16位),还有 l,表示针对四个字节(32位)。
    serv_addr.sin_port = htons(8888);
    // P.s. socket_in 除以上三者之外还有第四个成员,只是为了让 sockaddr 与 sockaddr_in 两个数据结构保持大小相同而保留的空字节,不使用。

    // 将 socket 地址与文件描述符 sockfd 绑定。定义时使用专用 socket 地址;为了便于绑定时转为通用 socket。
    // 专用 socket 地址表示特定的通信协议、IP 地址和端口号,而通用 socket 地址可以接收任何类型(AF_INET、AF_UNIX等)的 socket 地址。
    bind(sockfd, (sockaddr*)&serv_addr, sizeof(serv_addr));

    // 使用 listen 函数监听这个 socket 端口 sockfd。listen 函数使用主动连接套接字变为被连接套接口,使得一个进程可以接受其它进程的请求,从而成为一个服务器进程。
    // SOMAXCONN 定义了系统中每一个端口最大的监听队列的长度,这是个全局的参数,默认值为 128。
    listen(sockfd, SOMAXCONN);

    struct sockaddr_in clnt_addr;
    // socklen_t 是一种数据类型,在 32 位机下,size_t 和 int 的长度相同,都是32 bits。socket 编程中的 accept 函数的第三个参数的长度必须和 int 的长度相同。
    socklen_t clnt_addr_len = sizeof(clnt_addr);
    bzero(&clnt_addr, sizeof(clnt_addr));
    // 要接受一个客户端连接,需要使用 accept 函数,accept 需要写入客户端 socket 长度,所以需要定义一个类型为 socklen_t 的变量,并传入这个变量的地址。
    // accept 函数会阻塞当前程序,直到有一个客户端 socket 接受后程序才会往下运行。
    int clnt_sockfd = accept(sockfd, (sockaddr*)&clnt_addr, &clnt_addr_len);
    // clnt_sockfd 是通过 accept() 接受的客户端连接的文件描述符。
    // inet_ntoa函数将 (Ipv4) Internet 网络地址转换为采用 Internet 标准点十进制格式的 ASCII 字符串。成功则 inet_ntoa 返回指向静态缓冲区的字符指针,否则返回 NULL。
    // 将一个无符号短整形数从网络字节顺序转换为主机字节顺序。网络字节顺序 NBO :按从高到低的顺序存储,在网络上使用同一的网络字节顺序,可避免兼容性问题。
    printf("new client fd %d! IP: %s Port: %d\n", clnt_sockfd, inet_ntoa(clnt_addr.sin_addr), ntohs(clnt_addr.sin_port));
    return 0;
}


// 关于listen()和accept()这一段,可以想象有人从很远的地方通过一个你在侦听 (listen()) 的端口连接 (connect()) 到你的机器。它的连接将加入到等待接受 (accept()) 的队列 中。
// 调用 accept() 告诉它你有空闲的连接。它将返回一个新的套接字文件描述符,这样你就有两个套接字了。
// 原来的一个还在侦听你的那个端口, 新的在准备发送 (send()) 和接收 ( recv()) 数据。这就是这个过程!
//
// 服务器端:
// socket()-->bind( )-->listen()-->accept()-->read()/write()--->close()
// socket()//创建套接字
// bind()//分配套接字地址
// listen()//等待连接请求状态
// accept()//允许连接
// read()/write()//进行数据交换
// close()//断开连接

无注释版:

#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>

int main() {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in serv_addr;
    bzero(&serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    serv_addr.sin_port = htons(8888);

    bind(sockfd, (sockaddr*)&serv_addr, sizeof(serv_addr));

    listen(sockfd, SOMAXCONN);

    struct sockaddr_in clnt_addr;
    socklen_t clnt_addr_len = sizeof(clnt_addr);
    bzero(&clnt_addr, sizeof(clnt_addr));
    int clnt_sockfd = accept(sockfd, (sockaddr*)&clnt_addr, &clnt_addr_len);

    printf("new client fd %d! IP: %s Port: %d\n", clnt_sockfd, inet_ntoa(clnt_addr.sin_addr), ntohs(clnt_addr.sin_port));
    
    return 0;
}

client.cpp

关于客户端不需要 bind 函数来为创建的 sockfd 绑定特定 IP 地址和端口号。

  • 客户端调用 socket 函数创建套接字,然后调用 connect 函数连接到远程服务器的IP地址和端口号。在执行 connect 函数时操作系统会自动分配 一个 临时的本地 IP 地址和本地端口号 ,并将该套接字绑定到这个地址和端口上。这个本地 IP 地址通常是操作系统中可用的任意IP地址,而本地端口号是操作系统中未被占用的任意端口号。此时,客户端就可以使用这个新创建的套接字与服务器进行数据传输了。

connect 函数作用和用法?

  • 作用:connect 函数是用于向远程服务器建立连接的函数,它的作用是在客户端套接字中建立与服务端的连接。需要注意的是,connect 函数会 阻塞等待 直到连接建立完成或出错。因此,在客户端程序中,通常需要将套接字设置为非阻塞模式或者使用多路复用技术来提高程序的效率。connect 函数还可以用于非阻塞套接字,当套接字设置为非阻塞模式时,connect 函数会立即返回,而不会阻塞等待。此时需要使用 select、poll、epoll 等函数来等待套接字变为可写状态,表示连接建立完成或出错。

  • 用法: int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); sockfd 表示 客户端 套接字的文件描述符,addr 和 addrlen 参数表示指向 服务端 地址信息结构体(struct sockaddr)和长度的指针。

代码:

注释版:

#include <arpa/inet.h>
#include <sys/socket.h>
#include <string.h>

int main() {
    // 创建 socket 文件描述符
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);

    // 专用 socket 地址
    struct sockaddr_in serv_addr;
    // 清空
    bzero(&serv_addr, sizeof(serv_addr));
    // 设置地址族,IP 地址和端口
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    serv_addr.sin_port = htons(8888);
    
    // 与 server.cpp 监听的端口建立连接。注意将专用 socket 地址转为通用 socket 地址。
    // connect 函数建立与指定套接字的连接。如果未发生错误,连接将返回零,否则返回 SOCKET_ERROR。
    connect(sockfd, (sockaddr*)&serv_addr, sizeof(serv_addr));

    return 0;
}


// 客户端:
// socket()-->connect()-->read()/write()-->close()
// socket()//创建套接字
// connect()//请求连接
// read()/write()//进行数据交换
// close()//断开连接

无注释版:

#include <arpa/inet.h>
#include <sys/socket.h>
#include <string.h>

int main() {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in serv_addr;
    bzero(&serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    serv_addr.sin_port = htons(8888);
    
    connect(sockfd, (sockaddr*)&serv_addr, sizeof(serv_addr));

    return 0;
}

文章作者: 心意
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 心意 !
评论
  目录