Linux下socket网络编程

需要使用的头文件:

cpp 复制代码
#include <sys/socket.h>     // 核心 socket 函数
#include <netinet/in.h>     // IP 地址结构
#include <arpa/inet.h>      // 字节序转换函数
#include <unistd.h>         // close 函数 (Windows 下是 closesocket)
#include <string.h>         // memset 等

这里用tcp举例

服务端流程
  1. socket(): 创建一个监听套接字。
  2. bind(): 插上电话线(绑定 IP 和端口)。
  3. listen(): 开启接听模式。
  4. accept(): 等待客户打电话进来(阻塞),接通后产生一个新的套接字用于通信。
  5. recv() / send(): 收发数据。
  6. close(): 挂电话。

服务器端代码:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>

int main() {
    int server_fd, client_fd;
    struct sockaddr_in address;
    int addrlen = sizeof(address);
    char buffer[1024] = {0};
    const char *hello = "Hello from server";

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

    // 2. 配置地址结构 (IPv4, 任意IP, 端口 8080)
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡
    address.sin_port = htons(8080);       // 端口号,注意字节序转换

    // 3. 绑定
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // 4. 监听
    if (listen(server_fd, 3) < 0) { // 队列长度为3
        perror("listen");
        exit(EXIT_FAILURE);
    }
    printf("Server is listening on port 8080...\n");

    // 5. 接受连接 (阻塞等待)
    if ((client_fd = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
        perror("accept");
        exit(EXIT_FAILURE);
    }

    // 6. 读取数据
    read(client_fd, buffer, 1024);
    printf("Client says: %s\n", buffer);

    // 7. 发送数据
    send(client_fd, hello, strlen(hello), 0);
    printf("Hello message sent\n");

    // 8. 关闭
    close(client_fd);
    close(server_fd);
    return 0;
}
客户端流程
  1. socket(): 创建套接字。
  2. connect(): 拨打服务器电话(需要知道服务器 IP 和端口)。
  3. send() / recv(): 说话和听。
  4. close(): 挂电话。

客户端代码:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

int main() {
    int sock = 0;
    struct sockaddr_in serv_addr;
    char *hello = "Hello from client";
    char buffer[1024] = {0};

    // 1. 创建 Socket
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf("\n Socket creation error \n");
        return -1;
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(8080);

    // 2. 将字符串IP转成网络格式
    if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
        printf("\nInvalid address/ Address not supported \n");
        return -1;
    }

    // 3. 连接服务器
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        printf("\nConnection Failed \n");
        return -1;
    }

    // 4. 发送数据
    send(sock, hello, strlen(hello), 0);
    printf("Hello message sent\n");

    // 5. 接收回复
    read(sock, buffer, 1024);
    printf("Server reply: %s\n", buffer);

    // 6. 关闭
    close(sock);
    return 0;
}

在Linux底下使用gcc编译:

cpp 复制代码
# 编译 TCP 服务端
gcc server_tcp.c -o server_tcp

# 编译 TCP 客户端
gcc client_tcp.c -o client_tcp

# 运行 (先开服务端,再开客户端)
./server_tcp
./client_tcp

socket网络编程逻辑简单易懂,重点是明白API函数的作用,下一期会发表一篇有关三次握手四次挥手的文章重点讲解连接细节

核心API函数:

socket() ------ 创建套接字

该函数向操作系统申请创建一个网络通信的内核对象,返回一个文件描述符(File Descriptor)。你需要指定协议族(如 AF_INET 表示 IPv4)、套接字类型(如 SOCK_STREAM 表示流式 TCP 或 SOCK_DGRAM 表示数据报 UDP)以及具体的协议(通常设为 0,表示使用默认协议)。如果创建失败,返回 -1

bind() ------ 绑定地址

它将一个本地协议地址(sockaddr_in 结构体)赋予一个未命名的套接字。服务器端必须调用此函数来指定它监听的 IP 地址(如 INADDR_ANY 表示任意本地接口)和端口号(需转换为网络字节序)。对于客户端,通常可以省略此步骤,由内核自动分配临时端口

listen() ------ 监听连接

它通知内核,该套接字愿意接受连接请求。它会在内核中为该套接字维护两个队列:半连接队列 (SYN_RCVD 状态,三次握手未完成)和全连接队列 (ESTABLISHED 状态,握手已完成等待 accept 取走)。参数 backlog 指定了这两个队列长度之和的最大值

accept() ------ 接受连接

从监听套接字的已连接队列头部移除第一个连接请求,创建一个新的已连接套接字,并返回该套接字的描述符。注意: 原来的监听套接字(参数 sockfd)继续用于监听新的连接,而新返回的套接字专门用于与该特定客户端进行数据读写。如果队列为空,该函数默认会阻塞调用进程

connect() ------ 发起连接

该函数用于客户端主动向服务器发起连接请求。对于 TCP 套接字,它会触发三次握手过程;对于 UDP 套接字,它只是在内核中记录对端的 IP 和端口,使得后续的 send/recv 可以直接使用。调用成功表示连接已建立(TCP)或路由已确定(UDP)

send() / recv() ------ 数据传输

send() 将应用层缓冲区的数据拷贝到内核发送缓冲区。对于 TCP,它返回实际写入内核缓冲区的字节数(可能小于请求长度,需循环发送);对于 TCP 连接,若返回 0 或错误,通常意味着对端关闭了连接。recv() 从内核接收缓冲区读取数据到应用层缓冲区。它返回实际读取的字节数;若返回 0,表示对端已执行有序关闭(FIN 包);若返回 -1,表示出错。

注意事项:

htons() / htonl() 主机字节序转网络字节序。由于不同机器(x86 小端 vs 网络大端)存储多字节数据的方式不同,端口号和 IP 地址在传输前必须使用这些函数转换,以保证协议一致性。

struct sockaddr_in IPv4 套接字地址结构 。它是 sockaddr 的"友好版本",包含了协议族、端口和 IP 地址等字段。在调用 bindconnect 等函数时,通常需要将其强制转换为通用的 sockaddr* 类型。

相关推荐
宇钶宇夕1 小时前
CoDeSys入门实战一起学习(二十六):功能块(FBD)运算块与EN/ENO指令精讲及计数控制案例
运维·学习·自动化·软件工程
2401_858286111 小时前
OS55.【Linux】System V消息队列的简单了解
linux·运维·服务器
zdIdealism1 小时前
cnPuTTY CAC 0.83 Update 1—PuTTY CAC 0.83中文版本简单说明~~
linux·运维·服务器·ssh·putty·putty-cac
会算数的⑨1 小时前
Spring AI Alibaba学习(一)—— RAG
java·人工智能·后端·学习·spring·saa
IT 行者1 小时前
Spring Security 7 响应头配置完全指南
java·后端·spring·security
bug-0071 小时前
springboot 自定义消息处理
java·spring boot·后端
人道领域1 小时前
javaWeb从入门到进阶(SpringBoot基础案例3)
java·spring boot·后端
landonVM2 小时前
Linux VPS 怎么设置密钥登录
linux·运维·服务器
轩情吖2 小时前
数据结构-并查集
开发语言·数据结构·c++·后端··并查集