【计算机网络 - 基础问题】每日 3 题(四十八)

✍个人博客:https://blog.csdn.net/Newin2020?type=blog

📣专栏地址:http://t.csdnimg.cn/fYaBd

📚专栏简介:在这个专栏中,我将会分享 C++ 面试中常见的面试题给大家~

❤️如果有收获的话,欢迎点赞👍收藏📁,您的支持就是我创作的最大动力💪

📝推荐参考地址:https://www.xiaolincoding.com/(这个大佬的专栏非常有用!)

142. 针对 TCP 应该如何 Socket 编程?

  • 服务端和客户端初始化 socket,得到文件描述符;
  • 服务端调用 bind,将 socket 绑定在指定的 IP 地址和端口;
  • 服务端调用 listen,进行监听;
  • 服务端调用 accept,等待客户端连接;
  • 客户端调用 connect,向服务端的地址和端口发起连接请求;
  • 服务端 accept 返回用于传输的 socket 的文件描述符;
  • 客户端调用 write 写入数据;服务端调用 read 读取数据;
  • 客户端断开连接时,会调用 close,那么服务端 read 读取数据的时候,就会读取到了 EOF,待处理完数据后,服务端调用 close,表示连接关闭。

这里需要注意的是,服务端调用 accept 时,连接成功了会返回一个已完成连接的 socket,后续用来传输数据。

所以,监听的 socket 和真正用来传送数据的 socket,是「两个」 socket,一个叫作监听 socket,一个叫作已完成连接 socket。

成功连接建立之后,双方开始通过 read 和 write 函数来读写数据,就像往一个文件流里面写东西一样。

套接字函数:

cpp 复制代码
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>  // 包含了这个头文件,上面两个就可以省略

int socket(int domain, int type, int protocol);
    - 功能:创建一个套接字
    - 参数:
        - domain: 协议族 
            AF_INET : ipv4 
            AF_INET6 : ipv6
            AF_UNIX, AF_LOCAL : 本地套接字通信(进程间通信)
        - type: 通信过程中使用的协议类型 
            SOCK_STREAM : 流式协议 
            SOCK_DGRAM : 报式协议
        - protocol : 具体的一个协议。一般写0
            SOCK_STREAM : 流式协议默认使用TCP
            SOCK_DGRAM : 报式协议默认使用UDP
    - 返回值:
        - 成功:返回文件描述符,操作的就是内核缓冲区。
        - 失败:-1

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); // socket命名
    - 功能:绑定,将fd和本地的IP + 端口进行绑定
    - 参数:
        - sockfd : 通过socket函数得到的文件描述符
        - addr : 需要绑定的socket地址,这个地址封装了ip和端口号的信息
        - addrlen : 第二个参数结构体占的内存大小

int listen(int sockfd, int backlog);        // /proc/sys/net/core/somaxconn
    - 功能:监听这个socket上的连接
    - 参数:
        - sockfd : 通过socket()函数得到的文件描述符
        - backlog : 未连接的和已经连接的和的最大值, 5

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    - 功能:接收客户端连接,默认是一个阻塞的函数,阻塞等待客户端连接
    - 参数:
        - sockfd : 用于监听的文件描述符
        - addr : 传出参数,记录了连接成功后客户端的地址信息(ip,port)
        - addrlen : 指定第二个参数的对应的内存大小
    - 返回值:
        - 成功 :用于通信的文件描述符
        - -1 : 失败

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    - 功能:客户端连接服务器
    - 参数:
        - sockfd : 用于通信的文件描述符
        - addr : 客户端要连接的服务器的地址信息
        - addrlen : 第二个参数的内存大小
    - 返回值:成功 0, 失败 -1

ssize_t write(int fd, const void *buf, size_t count);        // 写数据
ssize_t read(int fd, void *buf, size_t count);        // 读数据

// TCP数据读写专用
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
        - send往sockfd上写入数据,成功返回实际写入的数据长度,失败返回-1并设置errno

// UDP数据读写专用
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t* addrlen);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, struct sockaddr* dest_addr, socklen_t addrlen);

案例:TCP 通信实现

服务器端 server.c

cpp 复制代码
// TCP 通信的服务器端
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main() {

    // 1.创建socket(用于监听的套接字)
    int lfd = socket(AF_INET, SOCK_STREAM, 0);

    if(lfd == -1) {
        perror("socket");
        exit(-1);
    }

    // 2.绑定
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    // inet_pton(AF_INET, "192.168.193.128", saddr.sin_addr.s_addr);
    saddr.sin_addr.s_addr = INADDR_ANY;  // 0.0.0.0
    saddr.sin_port = htons(9999);
    int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));

    if(ret == -1) {
        perror("bind");
        exit(-1);
    }

    // 3.监听
    ret = listen(lfd, 8);
    if(ret == -1) {
        perror("listen");
        exit(-1);
    }

    // 4.接收客户端连接
    struct sockaddr_in clientaddr;
    int len = sizeof(clientaddr);
    int cfd = accept(lfd, (struct sockaddr *)&clientaddr, &len);
    
    if(cfd == -1) {
        perror("accept");
        exit(-1);
    }

    // 输出客户端的信息
    char clientIP[16];
    inet_ntop(AF_INET, &clientaddr.sin_addr.s_addr, clientIP, sizeof(clientIP));
    unsigned short clientPort = ntohs(clientaddr.sin_port);
    printf("client ip is %s, port is %d\n", clientIP, clientPort);

    // 5.通信
    char recvBuf[1024] = {0};
    while(1) {
        // 获取客户端的数据
        int num = read(cfd, recvBuf, sizeof(recvBuf));
        if(num == -1) {
            perror("read");
            exit(-1);
        } else if(num > 0) {
            printf("recv client data : %s\n", recvBuf);
        } else if(num == 0) {
            // 表示客户端断开连接
            printf("clinet closed...");
            break;
        }

        char * data = "hello,i am server";
        // 给客户端发送数据
        write(cfd, data, strlen(data));
    }
   
    // 关闭文件描述符
    close(cfd);
    close(lfd);

    return 0;
}

客户端 client.c

cpp 复制代码
// TCP通信的客户端

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main() {

    // 1.创建套接字
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if(fd == -1) {
        perror("socket");
        exit(-1);
    }

    // 2.连接服务器端
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    inet_pton(AF_INET, "192.168.193.128", &serveraddr.sin_addr.s_addr);
    serveraddr.sin_port = htons(9999);
    int ret = connect(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));

    if(ret == -1) {
        perror("connect");
        exit(-1);
    }
    
    // 3. 通信
    char recvBuf[1024] = {0};
    while(1) {

        char * data = "hello,i am client";
        // 给客户端发送数据
        write(fd, data , strlen(data));

        sleep(1);
        
        int len = read(fd, recvBuf, sizeof(recvBuf));
        if(len == -1) {
            perror("read");
            exit(-1);
        } else if(len > 0) {
            printf("recv server data : %s\n", recvBuf);
        } else if(len == 0) {
            // 表示服务器端断开连接
            printf("server closed...");
            break;
        }

    }

    // 关闭连接
    close(fd);

    return 0;
}

143. socket 底层的关键字你有使用过吗,协议怎么设计的?

Socket 是应用层与 TCP/IP 协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket 其实就是一个门面模式,它把复杂的 TCP/IP 协议族隐藏在 Socket 接口后面,对用户来说,一组简单的接口就是全部。Socket 是对 TCP/IP 协议的封装,Socket 本身并不是协议,而是一个调用接口(API),通过 Socket,我们才能使用 TCP/IP 协议。

在 socket 底层中,关键字有:socket_state(socket 状态)、type(socket 类型)、flags(socket 的标志位)、wq(等待队列指针)、file(与 socket 关联的文件指针)、sk(套接字的核心,面向底层网络具体协议)等。这些关键字都是用来描述和控制 socket 的行为和状态的。

所谓的协议族(Protocol Family),就是一组协议(多个协议)的统称。最常用的是 TCP/IP 协议族,它包含了 TCP、IP、UDP、Telnet、FTP、SMTP 等上百个互为关联的协议,由于 TCP、IP 是两种常用的底层协议,所以把它们统称为 TCP/IP 协议族,用作数据传输。

至于协议的设计,通常是基于 TCP/IP 协议族来进行的。每一种协议都有其特定的功能和应用场景,比如 TCP 协议主要负责数据的可靠传输,而 UDP 协议则主要用于快速数据传输。

144. Socket 函数返回值代表什么意思?

在编程中,socket 函数通常用于在网络编程中创建套接字(sockets),它们是通信的基础设施,可以用来发送和接收数据。具体来说,socket 函数在很多编程语言中都有实现,如 Python、C、C++ 等。每个编程语言可能有一些微小的语法差异,但基本的函数结构和返回值通常是一样的。

在 socket 函数中,返回值通常用于指示操作是否成功。如果返回值为 0 或正值,则表示操作成功。如果返回值为负值,则表示发生了错误。具体错误信息可以通过操作系统提供的错误处理函数或 socket 函数的特定错误返回值来获取。

通常,以下是一些可能的返回值解释:

  • 返回值 >0:表示操作成功
  • -1(没有具体的错误码):表示请求失败,但是因为无法识别具体错误(如找不到 socket 地址等),可能需要查阅特定的文档或者运行更多的错误检查
  • 其他负数:表示具体的错误类型,比如 ENOMEM 表示内存不足,EACCES 表示权限问题等

在编程时,应仔细查看函数的文档或示例代码以了解其具体含义和用法。

相关推荐
ALex_zry2 小时前
C++26 std::complex 结构化绑定详解:auto [re, im] = c
c语言·开发语言·c++
谭光志3 小时前
工具塞满上下文窗口怎么办?深度拆解 AI Agent Tool Search 按需加载实现原理
前端·后端·ai编程
她说..3 小时前
Java 默认值设置方式
java·开发语言·后端·springboot
foggyprojects3 小时前
从0开始,一句话启动AI DataAgent
后端·数据分析·ai编程
郡杰3 小时前
一些基础和问题解决
后端
dexi.Chi 攻城狮3 小时前
SQL层次查询语法
经验分享·笔记·sql
忧郁的紫菜3 小时前
基础实现:单篇 Markdown 转 Word
开发语言·c#·word
陈随易3 小时前
前端项目部署只要30秒
前端·后端·程序员
甜美的小天鹅3 小时前
Swifter C#之inline还是不inline,这是个问题
开发语言·c#
YIAN3 小时前
从零手写文件读取 MCP 服务:一文吃透 Model Context Protocol 全链路通信原理
前端·后端·mcp