Socket编程基础(易懂版)

本文介绍了Socket编程的基本概念,比较了TCP和UDP套接字的工作原理,并详细讲解了SocketAPI的关键步骤,如创建Socket、绑定、监听、连接和数据传输。通过门铃比喻帮助理解套接字通信机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        这篇是我在学习Socket(套接字)编程过程中的笔记整理,收集学习了一些网上的文章,但我想要用我习惯的方式(用门铃来比喻套接字)记录下学习的内容,方便日后查看。

一、Socket编程是什么?

1、概念

        Socket编程是一种实现网络通信的编程技术,它允许不同主机上的应用进程之间进行数据交换

        想象一下,家门上的门铃其实是一个Socket。每当有人(数据包)按下门铃,不管他们在哪儿,只要还在地球上,你都能知道有人来访。在网络世界里,Socket就像这个门铃,它允许两个程序不管距离多远,都能相互“拜访”和交换信息。

2、Socket的类型:流式Socket(TCP)数据报Socket(UDP)

TCP套接字:提供可靠的、面向连接的通信。(对讲机式门铃)

  1. 可靠连接:TCP套接字就像是带有对讲机的门铃。当你按下门铃,不仅能通知屋内有人来访,还能立即通过对讲机与访客对话,确认他们的身份和来意。

  2. 数据有序:使用这种门铃,你可以根据访客按门铃的顺序,一个接一个地与他们对话,不会出现混乱。

  3. 流量控制:如果一次性来的访客太多,对讲机系统会自动告诉一些访客稍等,以避免屋内变得过于拥挤。

  4. 拥塞控制:如果发现路上(网络)太拥挤,对讲机系统也会自动调整访客的到达速度,以防止过度拥堵。

  5. 错误恢复:如果对话中出现了问题(比如信号干扰),对讲机系统会尝试重新连接,确保信息准确传达。

UDP套接字:提供不可靠的、无连接的通信。(传统门铃)

  1. 无连接:UDP套接字就像是传统的门铃,只负责通知你有人来访,但不提供任何对话功能。

  2. 快速传送:因为没有对话确认的步骤,这种门铃允许访客快速按铃,适合不需要立即回复的情况。

  3. 不保证有序:由于没有对话功能,无法保证你与访客的交流顺序,可能需要你自己来维持秩序。

  4. 可能丢包:因为没有确认机制,有些按铃的信号可能因为各种原因丢失,需要你自己判断是否需要回应。

  5. 简单高效:这种门铃结构简单,使用方便,适合于不需要复杂交互的场景,如简单的信息提醒或广播通知。

  • TCP:适合于需要确保信息准确无误地传达的场合,比如正式的商务会议或者重要的信息交流。
  • UDP:适合于速度要求高、可以容忍一些误差的场合,比如实时的游戏数据传输或者电视直播。

 二、Socket API理解

Socket编程通常遵循以下步骤:

  1. 创建Socket——socket():创建一个socket对象。

  2. 绑定Socket——bind():一旦socket被创建,通常需要将其绑定到一个特定的网络地址端口上,这样它就可以监听进入的连接请求。
    绑定过程就像是给门铃安装一个门牌号码。这个号码包括了网络地址(IP地址)和门铃的“房间号”(端口号)。这样,当有人按门铃时,不仅知道有人来访,还能知道这个访客是想要访问家里的哪个“房间”(服务)。

  3. 监听连接(对于服务器端)——listen()服务器端的socket需要监听进入的连接请求。
    当门铃安装好并有了门牌号码后,就可以开始“监听”门铃了。在网络世界里,监听就像是你站在门旁,等待门铃响起,这意味着有人在请求进入。

  4. 连接到服务器(对于客户端)——connect()客户端需要使用connect()函数来连接到服务器的socket。
    对于客户端来说,连接就像是站在门外按响邻居家的门铃。当你按下门铃并等待邻居响应时,你实际上是在发送一个连接请求。

  5. 接受连接(对于服务器端)——accept():当服务器监听到连接请求时,使用accept()函数来接受连接,这将创建一个新的socket用于与客户端通信。
    当服务器“听到”门铃并“查看”门牌号码后,它会决定是否开门。接受连接就像是打开门,欢迎来访的客人进入。

  6. 数据传输——send(),recv():一旦socket连接建立,就可以使用send()recv()函数(或类似的函数,如write()read())来发送和接收数据。

  7. 关闭Socket——close():通信完成之后,需要通过close()函数关闭socket来释放资源。

三、Socket API原型

Socket编程包含的头文件:

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

1、创建Socket —socket()

原型:

int socket(int domain, int type, int protocol);

参数:

  • domain: 指定socket的协议域,常用的有:
    • AF_INET: IPv4
    • AF_INET6: IPv6
    • AF_UNIX: Unix域socket
  • type: 指定socket的通信方式,常用的有:
    • SOCK_STREAM: 面向连接的流式socket,如TCP
    • SOCK_DGRAM: 无连接的包式socket,如UDP
    • SOCK_SEQPACKET: 面向序列包的socket
  • protocol: 指定协议,通常设置为0以使用默认协议。

返回值:

  • 成功: 新创建的socket的文件描述符(fd)
  • 失败: -1,并设置errno以指示错误类型

2. 绑定Socket —bind()

原型:

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

参数:

  • sockfd: 由socket()创建的socket文件描述符。
  • addr: 指向sockaddr结构体的指针,该结构体包含了要绑定的地址信息。
  • addrlensockaddr结构体的大小。

返回值:

  • 成功: 0
  • 失败: -1,并设置errno

3. 监听连接 —listen()

原型:

int listen(int sockfd, int backlog);

参数:

  • sockfd: 已绑定到地址的socket文件描述符。
  • backlog: 指定内核用于存放未连接但已排队的连接请求的最大数量。

返回值:

  • 成功: 0
  • 失败: -1,并设置errno

4. 接受连接 —accept()

原型:

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

参数:

  • sockfd: 监听socket的文件描述符。
  • addr: 如果非NULL,接受连接后,该参数将被填充为客户端的地址信息。
  • addrlen: 传入时,指向存放addr地址结构体长度的变量的指针;传出时,返回实际的地址长度。

返回值:

  • 成功: 一个新的socket文件描述符,用于与客户端通信。
  • 失败: -1,并设置errno

5. 建立连接 —connect()

原型:

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

参数:

  • sockfd: 客户端socket文件描述符。
  • addr: 指向包含服务器地址信息的sockaddr结构体的指针。
  • addrlensockaddr结构体的大小。

返回值:

  • 成功: 0
  • 失败: -1,并设置errno

6. 数据传输

发送数据 —send()

原型:

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

参数:

  • sockfd: socket文件描述符。
  • buf: 指向要发送数据的缓冲区的指针。
  • len: 要发送数据的长度。
  • flags: 控制发送操作的标志位,通常为0。

返回值:

  • 成功: 发送的字节数
  • 失败: -1,并设置errno
接收数据 —recv()

原型:

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

参数:

  • sockfd: socket文件描述符。
  • buf: 接收数据的缓冲区。
  • len: 缓冲区的长度。
  • flags: 控制接收操作的标志位,通常为0。

返回值:

  • 成功: 接收的字节数
  • 失败: -1,并设置errno
  • 0: 对应的socket连接已关闭

7. 关闭Socket —close()

原型:

int close(int sockfd);

参数:

  • sockfd: 要关闭的socket文件描述符。

返回值:

  • 成功: 0
  • 失败: -1,并设置errno

8. 关闭连接方向 —shutdown()

原型:

int shutdown(int sockfd, int how);

参数:

  • sockfd: socket文件描述符。
  • how: 指定如何关闭socket:
    • SHUT_RD: 关闭接收方向,不再读取数据。
    • SHUT_WR: 关闭发送方向,不再发送数据。
    • SHUT_RDWR: 关闭接收和发送方向。

返回值:

  • 成功: 0
  • 失败: -1,并设置errno

四、示例 

         下面是一个使用TCP协议的socket服务器端的示例,主要是创建一个简单的echo服务器,该服务器接收客户端发送的数据并将其回传给客户端。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

#define PORT 8080  // 服务器监听的端口号

int main() {
    int server_fd, new_socket;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);
    char buffer[1024] = {0};

    // 创建socket(socket)
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == -1) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    // 初始化服务器地址结构体(bind之前的准备)
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;  // 地址族
    server_addr.sin_addr.s_addr = INADDR_ANY;  // 服务器IP地址(任意)
    server_addr.sin_port = htons(PORT);  // 服务器端口

    // 将socket绑定到地址(bind)
    if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // 开始监听传入连接(listen)
    if (listen(server_fd, 5) < 0) {
        perror("listen failed");
        exit(EXIT_FAILURE);
    }

    printf("Server is listening on port %d...\n", PORT);

    // 接受客户端连接(accept)
    new_socket = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);
    if (new_socket < 0) {
        perror("accept failed");
        exit(EXIT_FAILURE);
    }

    // 接收客户端发送的数据(recv)
    int valread = recv(new_socket, buffer, 1024, 0);
    if (valread < 0) {
        perror("recv failed");
        close(new_socket);
        exit(EXIT_FAILURE);
    }

    // 发送数据回客户端(send)
    send(new_socket, buffer, strlen(buffer), 0);

    // 关闭新的socket(close)
    close(new_socket);

    // 关闭监听socket
    close(server_fd);

    return 0;
}

        在这个例子当中,服务器首先创建了一个socket,然后将其绑定到本地的8080端口。接着,服务器进入监听状态,等待客户端的连接请求。当客户端连接时,服务器接受这个连接,创建一个新的socket用于与客户端通信。服务器接收客户端发送的消息,然后使用相同的消息进行回应。最后,服务器关闭用于通信的socket以及监听socket。

        需要注意的是,这个例子是一个简化的版本,仅仅是为了展示Socket API 的使用,没有包含错误处理和多线程/异步处理的逻辑,这些在实际的服务器应用中是必需要有的。而且,服务器在接收到客户端的消息后立即关闭了连接,实际的服务器可能会维护一个持久的连接或同时处理多个连接。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值