什么是Socket与Socket编程?

0 阅读5分钟

什么是Socket与Socket编程?

Socket 是网络编程的 “抽象接口”;

  • 从程序角度看,socket 是一个文件描述符;
  • 从系统角度看,它是网络协议栈中的一个通信端点。

本质上,socket 也是一个文件描述符。

网络通信的唯一标识由四元组构成:(本地IP, 本地端口, 远端IP, 远端端口)

TCP 协议中,该四元组唯一确定一个网络连接

Socket 编程的核心逻辑围绕该通信端点的创建、绑定、连接建立、数据传输、连接关闭展开。

  • TCP 编程核心流程为:socket() → bind() → listen() → accept() → send()/recv() → close()

  • TCP 面向连接、可靠传输、无消息边界;UDP 无连接、不可靠、保留消息边界;

Socket 编程

Socket 编程就是基于 Socket 接口,编写网络通信程序,最常见的是 C/S(客户端 - 服务器)模型

  • 服务器端:先启动,监听某个端口,等待客户端连接;
  • 客户端:主动向服务器的 IP + 端口 发起连接,连接成功后双方收发数据。
sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

这个方法会返回socket_fd,它是socket文件的句柄,是个数字,相当于socket的身份证号。

类型协议特点适用场景
SOCK_STREAMTCP面向连接、可靠传输(数据不丢、不重复、有序)、基于 “字节流”网页、文件传输、微信
SOCK_DGRAMUDP无连接、不可靠传输(不保证数据到达)、基于 “数据报”、速度快直播、游戏、DNS 解析

得到了socket_fd之后,对于服务端,就可以依次执行bind()listen()accept()方法,然后坐等客户端的连接请求。

对于客户端,得到socket_fd之后,你就可以执行connect()方法向服务端发起建立连接的请求,此时就会发生TCP三次握手。

连接建立完成后,客户端可以执行send() 方法发送消息,服务端可以执行recv()方法接收消息,反过来,服务器也可以执行send()客户端执行recv()方法。

服务器端核心步骤客户端核心步骤
1. 创建 Socket 套接字1. 创建 Socket 套接字
2. 绑定 IP 地址与端口(bind)2. 向服务器发起连接(connect)
3. 开启端口监听(listen)3. 双向收发数据(send/recv)
4. 等待客户端连接(accept)4. 关闭套接字,释放资源
5. 双向收发数据(recv/send)
6. 关闭套接字,释放资源

在Linux中实现Socket编程

服务器端
#include <iostream>
#include <sys/socket.h>   // Socket核心API头文件
#include <netinet/in.h>   // IPv4地址结构体头文件
#include <arpa/inet.h>    // 字节序转换、IP地址转换函数
#include <unistd.h>       // close、read、write函数
#include <cstring>        // memset字符串操作

// 定义端口号和缓冲区大小
const int PORT = 8080;
const int BUF_SIZE = 1024;

int main() {
    // ===================== 1. 创建Socket套接字 =====================
    // AF_INET: 使用IPv4协议;SOCK_STREAM: 使用TCP流式套接字;0: 自动匹配默认协议
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd < 0) {
        std::cerr << "创建Socket失败" << std::endl;
        return -1;
    }
    std::cout << "Socket创建成功" << std::endl;

    // ===================== 2. 配置地址结构体,绑定IP与端口 =====================
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));  // 清空结构体
    server_addr.sin_family = AF_INET;               // 地址族:IPv4
    server_addr.sin_port = htons(PORT);             // 端口号:主机字节序转网络字节序
    server_addr.sin_addr.s_addr = INADDR_ANY;       // 监听本机所有网卡的IP地址

    // 绑定Socket到指定IP+端口
    int ret = bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
    if (ret < 0) {
        std::cerr << "绑定端口失败" << std::endl;
        close(server_fd);
        return -1;
    }
    std::cout << "端口绑定成功,监听端口: " << PORT << std::endl;

    // ===================== 3. 开启端口监听 =====================
    // 第二个参数backlog: 内核中已完成三次握手的等待队列最大长度
    ret = listen(server_fd, 5);
    if (ret < 0) {
        std::cerr << "开启监听失败" << std::endl;
        close(server_fd);
        return -1;
    }
    std::cout << "服务器启动成功,等待客户端连接..." << std::endl;

    // ===================== 4. 阻塞等待客户端连接 =====================
    struct sockaddr_in client_addr;
    socklen_t client_addr_len = sizeof(client_addr);
    // accept会阻塞,直到有客户端连接成功,返回新的Socket文件描述符,专门用于和该客户端通信
    int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_addr_len);
    if (client_fd < 0) {
        std::cerr << "接受客户端连接失败" << std::endl;
        close(server_fd);
        return -1;
    }
    std::cout << "客户端连接成功!客户端IP: " << inet_ntoa(client_addr.sin_addr) 
              << " 端口: " << ntohs(client_addr.sin_port) << std::endl;

    // ===================== 5. 双向收发数据 =====================
    char buf[BUF_SIZE] = {0};
    // 接收客户端发送的数据
    ssize_t recv_len = recv(client_fd, buf, BUF_SIZE - 1, 0);
    if (recv_len < 0) {
        std::cerr << "接收数据失败" << std::endl;
    } else if (recv_len == 0) {
        std::cout << "客户端已断开连接" << std::endl;
    } else {
        std::cout << "收到客户端数据: " << buf << std::endl;
        // 向客户端发送响应数据
        const char* response = "你好,我是C++ TCP服务器,已收到你的消息!";
        send(client_fd, response, strlen(response), 0);
        std::cout << "响应数据已发送" << std::endl;
    }

    // ===================== 6. 关闭套接字,释放资源 =====================
    close(client_fd);
    close(server_fd);
    std::cout << "服务器已关闭" << std::endl;

    return 0;
}
客户端
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>

const int PORT = 8080;
const char* SERVER_IP = "127.0.0.1";  // 服务器IP,本地测试用回环地址
const int BUF_SIZE = 1024;

int main() {
    // ===================== 1. 创建Socket套接字 =====================
    int client_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (client_fd < 0) {
        std::cerr << "创建Socket失败" << std::endl;
        return -1;
    }
    std::cout << "Socket创建成功" << std::endl;

    // ===================== 2. 配置服务器地址,发起连接 =====================
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    // 将点分十进制的IP字符串转换为网络字节序的二进制地址
    if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {
        std::cerr << "IP地址格式错误" << std::endl;
        close(client_fd);
        return -1;
    }

    // 向服务器发起TCP连接(三次握手)
    int ret = connect(client_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
    if (ret < 0) {
        std::cerr << "连接服务器失败" << std::endl;
        close(client_fd);
        return -1;
    }
    std::cout << "成功连接到服务器!" << std::endl;

    // ===================== 3. 双向收发数据 =====================
    // 向服务器发送数据
    const char* send_msg = "你好,我是C++ TCP客户端!";
    send(client_fd, send_msg, strlen(send_msg), 0);
    std::cout << "消息已发送至服务器" << std::endl;

    // 接收服务器响应
    char buf[BUF_SIZE] = {0};
    ssize_t recv_len = recv(client_fd, buf, BUF_SIZE - 1, 0);
    if (recv_len > 0) {
        std::cout << "收到服务器响应: " << buf << std::endl;
    }

    // ===================== 4. 关闭套接字,释放资源 =====================
    close(client_fd);
    std::cout << "客户端已关闭" << std::endl;

    return 0;
}