Linux C/C++ Socket 编程

本文目录

  • [Linux C语言 socket 编程 client 端](#Linux C语言 socket 编程 client 端)
    • [头文件 unistd.h & arpa/inet.h](#头文件 unistd.h & arpa/inet.h)
      • [1. **`unistd.h`**](#1. unistd.h)
      • [2. **`arpa/inet.h`**](#2. arpa/inet.h)
    • [socket() 创建套接字](#socket() 创建套接字)
    • [sockaddr_in 结构体](#sockaddr_in 结构体)
    • inet_pton()
    • connect()
    • send()
    • recv()
    • [send() 和 recv() 中的 flags 参数](#send() 和 recv() 中的 flags 参数)
        • [**默认行为(`flags = 0`)的特点:**](#默认行为(flags = 0)的特点:)
        • [其他 `flags` 标志:](#其他 flags 标志:)
    • close()
    • [使用 C++ 面向对象编程思想封装](#使用 C++ 面向对象编程思想封装)
  • [Linux C语言 socket 编程 server 端](#Linux C语言 socket 编程 server 端)
  • 运行
  • [Linux socket 编程在线英文文档](#Linux socket 编程在线英文文档)

进行网络套接字编程之前,需要有计算机网络相关方面的知识。

Linux C语言 socket 编程 client 端

c 复制代码
// client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define SERVER_IP "127.0.0.1"  // 服务器地址
#define SERVER_PORT 12000       // 服务器端口

int main() {
    // 创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("Socket creation failed");
        exit(EXIT_FAILURE);
    }

    // 设置服务器信息
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    
    // 将服务器的 IP 地址转换为二进制形式
    if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {
        perror("Invalid address or Address not supported");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    // 连接到服务器
    if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("Connection failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    // 发送数据
    char *message = "Hello, server!";
    if (send(sockfd, message, strlen(message), 0) == -1) {
        perror("Send failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }
    printf("Message sent to server: %s\n", message);

    // 接收数据
    char buffer[1024];
    int bytes_received = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
    if (bytes_received == -1) {
        perror("Recv failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    buffer[bytes_received] = '\0';  // 添加字符串终止符
    printf("Received from server: %s\n", buffer);

    // 关闭套接字
    close(sockfd);

    return 0;
}

编译:gcc -o client client.c

Terminal 下运行:./client

头文件 unistd.h & arpa/inet.h

这两个头文件 unistd.harpa/inet.h 都是 POSIX 标准 下的系统头文件,广泛用于 Unix/Linux 系统上的编程,尤其是网络编程和低级系统调用。

1. unistd.h

unistd.h 是一个与 Unix 系统调用相关的头文件,提供了大量与操作系统交互的功能,例如文件操作、进程管理、内存管理、IO 操作等。

常用功能:

  • 文件操作

    • read(): 读取文件描述符中的数据。
    • write(): 向文件描述符写入数据。
    • close(): 关闭文件描述符。
  • 进程控制

    • fork(): 创建一个新进程(分叉)。
    • exec(): 替换当前进程的执行映像。
    • getpid(): 获取当前进程的 PID。
    • getppid(): 获取父进程的 PID。
  • 文件描述符操作

    • dup(), dup2(): 复制文件描述符。
    • pipe(): 创建管道。
  • 时间管理

    • sleep(): 使当前进程睡眠指定的秒数。
    • usleep(): 使当前进程睡眠指定的微秒数。
  • 系统信息

    • getcwd(): 获取当前工作目录。

这些函数大多数涉及操作系统级别的基本功能,因此它们的效率高,广泛应用于各种系统编程中。

2. arpa/inet.h

arpa/inet.h 是与 Internet 地址处理网络通信 相关的头文件,提供了对 IP 地址和端口号进行转换、网络字节序与主机字节序之间转换等功能。这些函数对于进行 网络编程 ,尤其是 TCP/IP 网络通信 非常重要。

常用功能:

  • IP 地址转换

    • inet_pton(): 将 IP 地址从点分十进制字符串转换为网络字节序的二进制格式(用于 struct sockaddr_in)。
    • inet_ntop(): 将网络字节序的 IP 地址转换为点分十进制字符串。
  • 主机字节序与网络字节序转换

    • htons(): 将 16 位短整数从主机字节序转换为网络字节序(例如端口号)。
    • htonl(): 将 32 位长整数从主机字节序转换为网络字节序(例如 IP 地址)。
    • ntohs(): 将 16 位短整数从网络字节序转换为主机字节序。
    • ntohl(): 将 32 位长整数从网络字节序转换为主机字节序。

这些函数通常用于 TCP/IP 套接字编程中,帮助程序处理地址、端口号的转换和网络字节序问题。

socket() 创建套接字

cpp 复制代码
// 创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
    perror("Socket creation failed");
    exit(EXIT_FAILURE);
}

在 linux 系统上,可以使用 man 命令(Linux Manual Pages (man pages))来查看大部分 Linux 系统调用的文档,man 2 socket:查看 socket() 函数的详细文档:

$ man 2 socket
-----------------------------
socket(2)                                                           System Calls Manual                                                          socket(2)

NAME
       socket - create an endpoint for communication

LIBRARY
       Standard C library (libc, -lc)

SYNOPSIS
       #include <sys/socket.h>

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

DESCRIPTION
       socket()  creates an endpoint for communication and returns a file descriptor that refers to that endpoint.  The file descriptor returned by a suc‐
       cessful call will be the lowest-numbered file descriptor not currently open for the process.

       The domain argument specifies a communication domain; this selects the protocol family which will be used for communication.   These  families  are
       defined in <sys/socket.h>.  The formats currently understood by the Linux kernel include:
       Name         Purpose                                    Man page
       AF_UNIX      Local communication                        unix(7)
       AF_LOCAL     Synonym for AF_UNIX
       AF_INET      IPv4 Internet protocols                    ip(7)
       AF_AX25      Amateur radio AX.25 protocol               ax25(4)
       AF_IPX       IPX - Novell protocols
... ...
... ...
... ...

socket() 是一个系统调用,用于创建一个新的套接字。套接字是进行网络通信的基础,通过它可以实现数据的发送和接收。socket() 函数会返回一个 套接字描述符,它是一个整数值,后续的网络操作(如连接、发送、接收等)都需要通过这个描述符进行。

socket() 函数第一个参数常用的有 AF_INET (IPv4 地址族)和 AF_INET6 (IPv6 地址族)。第二个参数常用的有 SOCK_STREAM (TCP)和 SOCK_DGRAM (UDP)。第三个参数-通常指定套接字使用的协议。对于 SOCK_STREAM 类型,协议值通常设置为 0,表示使用默认协议。在 IPv4 上,默认协议就是 TCP ,在 SOCK_DGRAM 类型下,默认协议是 UDP 。如果使用 AF_INET6,则可以使用 IPPROTO_TCPIPPROTO_UDP 来指定具体协议。

返回值:如果成功,则返回新套接字的文件描述符。如果失败,则返回 -1,并 适当设置 errno 。

sockaddr_in 结构体

struct sockaddr_in 是一个结构体,用来表示 IPv4 地址和端口信息。它定义在 <netinet/in.h> 头文件中,通常用于套接字编程中存储 IP 地址和端口信息。

cpp 复制代码
// definition
struct sockaddr_in
{
  short   sin_family; /* must be AF_INET */
  u_short sin_port;
  struct  in_addr sin_addr;
  char    sin_zero[8]; /* Not used, must be zero */
};
cpp 复制代码
    // 设置服务器信息
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);

memset() 的作用是将指定的内存区域填充为特定的值。这里将 server_addr 结构体的所有字节设置为 0,这样可以确保结构体中的所有字段都初始化为 0,防止未初始化的字段造成意外行为。

  • htons() 是一个用于 字节序转换 的函数。它的作用是将 主机字节序 (即本机的字节序)转换为 网络字节序(即大端字节序)。

    • 网络字节序:大端字节序(big-endian),即高位字节存储在低地址。
    • 主机字节序:通常是小端字节序(little-endian),但这取决于机器架构。
  • 端口号是 16 位的整数,通常我们使用 htons() 来确保在网络传输时,端口号以网络字节序的格式进行传输。

inet_pton()

inet_pton - convert IPv4 and IPv6 addresses from text to binary form(IP 地址转换成二进制形式)。

definition:

cpp 复制代码
#include <arpa/inet.h>

int inet_pton(int af, const char *src, void *dst);

返回值:成功返回 1 。src IP 地址格式错误返回 0 。af 不合法 返回 -1 。

connect()

cpp 复制代码
#include <sys/socket.h>
int connect(int socket, const struct sockaddr *address, socklen_t address_len);

返回值:成功返回0,其他返回 -1 。

send()

cpp 复制代码
#include <sys/socket.h>
ssize_t send(int socket, const void *buffer, size_t length, int flags);

返回值:发送成功,返回发送数据的字节数;失败,返回 -1 。

recv()

cpp 复制代码
#include <sys/socket.h>
ssize_t recv(int socket, void *buffer, size_t length, int flags);

返回值:接收数据成功返回接收数据的字节数;如果没有数据接收且对方已有序关闭连接返回 0;其他错误情况返回 -1 。

send() 和 recv() 中的 flags 参数

send() 和 recv() 的 flags 参数通常设置为 0,表示使用默认的行为模式。

默认行为(flags = 0)的特点:
  • 阻塞模式send()recv() 默认都处于阻塞模式,意味着:
    • send():如果数据无法立刻发送(比如网络缓冲区已满),它会阻塞,直到有足够的空间可以发送数据。
    • recv():如果没有数据可读,它会阻塞,直到接收到至少一个字节的数据,或者对方关闭了连接。
  • 按字节顺序发送/接收send() 会按顺序发送数据,recv() 会按顺序接收数据,返回已接收到的字节数。如果请求的数据量比接收到的少,recv() 会返回已接收到的字节数,剩余的数据需要在后续调用中接收。
  • 正常的数据传输:不进行任何特殊处理,如不启用带外数据、不使用非阻塞模式等。
其他 flags 标志:
  • MSG_OOB:

    • 发送 带外数据(Out-of-Band Data)。通常用于传输紧急数据,但对于大多数应用程序来说,带外数据并不常用。
    • 在 TCP 中,带外数据和普通数据并没有严格的区分,标记为带外数据的行为在很多情况下不起作用。
  • MSG_PEEK:

    • 使 recv() 函数或 recvfrom() 函数可以预读取数据,但数据并不会从缓冲区中被移除。即使数据被读取,下一次调用 recv()recvfrom() 仍然会返回相同的数据。
    • 这种标志在 recv() 上比较常用,但在 send() 上没有直接用途。
  • MSG_DONTROUTE:

    • 发送数据时, 不经过路由表。这个标志告诉内核,不要尝试寻找默认路由,而是直接将数据发送到目标地址。通常用于开发中的调试或非常规的发送需求。
  • MSG_NOSIGNAL:

    • 当使用该标志时, send() 不会在发送过程中因信号的产生而导致 SIGPIPE 信号(即破损的管道错误)。通常与管道或套接字连接相关,如果尝试向已关闭的连接发送数据,默认会收到 SIGPIPE 信号,而通过 MSG_NOSIGNAL 可以避免这种情况。
  • MSG_WAITALL:

    • 用于 recv()recvfrom() ,告知内核等待直到接收到指定长度的数据。它确保不会返回少于 length 字节的数据,除非连接关闭。
    • 这对于一些需要完整接收数据的场景非常有用,尤其是在接收固定大小的数据时。

close()

释放之前创建的套接字的资源。

使用 C++ 面向对象编程思想封装

cpp 复制代码
// client.cpp
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>

class TcpClient {
private:
    int sockfd;
    struct sockaddr_in server_addr;

public:
    // 构造函数
    TcpClient(const std::string& server_ip, int server_port) {
        // 创建套接字
        sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd == -1) {
            perror("Socket creation failed");
            exit(EXIT_FAILURE);
        }

        // 设置服务器地址结构
        memset(&server_addr, 0, sizeof(server_addr));
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(server_port);

        if (inet_pton(AF_INET, server_ip.c_str(), &server_addr.sin_addr) <= 0) {
            perror("Invalid address or Address not supported");
            close(sockfd);
            exit(EXIT_FAILURE);
        }
    }

    // 连接服务器
    void connectToServer() {
        if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
            perror("Connection failed");
            close(sockfd);
            exit(EXIT_FAILURE);
        }
        std::cout << "Connected to server\n";
    }

    // 发送数据
    void sendData(const std::string& message) {
        if (send(sockfd, message.c_str(), message.length(), 0) == -1) {
            perror("Send failed");
            close(sockfd);
            exit(EXIT_FAILURE);
        }
        std::cout << "Message sent: " << message << std::endl;
    }

    // 接收数据
    void receiveData() {
        char buffer[1024];
        int bytes_received = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
        if (bytes_received == -1) {
            perror("Recv failed");
            close(sockfd);
            exit(EXIT_FAILURE);
        }

        buffer[bytes_received] = '\0'; // Add null terminator
        std::cout << "Received from server: " << buffer << std::endl;
    }

    // 关闭套接字
    void closeConnection() {
        close(sockfd);
    }

    // 析构函数
    ~TcpClient() {
        closeConnection();
    }
};

int main() {
    TcpClient client("127.0.0.1", 12000);

    client.connectToServer();
    client.sendData("Hello, server!");
    client.receiveData();

    return 0;
}

编译:g++ -o client client.cpp

Linux C语言 socket 编程 server 端

cpp 复制代码
// server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>    // 提供 close、read 和 write (这里通过 send 间接使用,因为 send 是 write 的一个更高级别的封装)
#include <arpa/inet.h>   // 提供了用于网络地址转换的函数声明,如将点分十进制格式的 IP 地址转换为网络字节序,以及将端口号从主机字节序转换为网络字节序。

#define PORT 12000    // socket 绑定的端口好
#define BUFFER_SIZE 1024       // 缓存大小

int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;     // 结构体,用于存储 IPv4 地址和端口号的信息。
    int opt = 1;
    int addrlen = sizeof(address);
    char buffer[BUFFER_SIZE] = {0};
    const char *hello = "Hello from server";

    // 创建socket
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
    /*
    AF_INET:指定地址族为 IPv4。
    SOCK_STREAM:指定套接字类型为 TCP(面向连接的字节流)。
    0:指定协议为 0,通常对于 SOCK_STREAM 和 AF_INET,这个参数为 0,意味着使用 TCP。
    */
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 绑定socket到端口
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }
    address.sin_family = AF_INET; // IPv4
    address.sin_addr.s_addr = INADDR_ANY; // 任何地址
    address.sin_port = htons(PORT); // 端口

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

    // 监听连接
    if (listen(server_fd, 3) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    // 接受连接
    while (1) {
        printf("等待连接...\n");
        if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
            perror("accept");
            exit(EXIT_FAILURE);
        }

        // 接收数据
        read(new_socket, buffer, BUFFER_SIZE);
        printf("收到消息: %s\n", buffer);

        // 发送数据
        send(new_socket, hello, strlen(hello), 0);
        printf("欢迎消息已发送\n");

        // 关闭当前连接
        close(new_socket);
    }

    // 关闭服务器socket
    close(server_fd);
    return 0;
}

编译:gcc -o server server.c

setsockopt()

cpp 复制代码
#include <sys/socket.h>
int setsockopt(int socket, int level, int option_name,
    const void *option_value, socklen_t option_len);
cpp 复制代码
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }

setsockopt() 函数,用于设置套接字的选项。是在创建一个服务器套接字时,配置套接字的一些行为或属性。SO_REUSEADDR 选项的设置允许在套接字关闭后,立即重用相同的地址(IP 和端口),而不必等到操作系统回收该端口。

level :指定选项所在的协议层,通常是 SOL_SOCKET,表示设置的是套接字级别的选项。

optval:指向一个存储选项值的内存区域。这个值会根据不同的选项而变化,通常是一个整数或布尔值。

optlenoptval 指向的内存区域的大小(字节数)。

int opt = 1; opt 是 SO_REUSEADDR 选项的值通常是 1(启用)或 0(禁用)。sizeof(opt) 用来确定 opt 变量的大小。

setsockopt() 返回值:设置成功返回 0,否则返回 -1 。

address.sin_addr.s_addr = INADDR_ANY; INADDR_ANY 是一个特殊的常量,值是 0.0.0.0,它代表任何可用的网络接口地址(所有的 IPv4 地址),通常用于绑定套接字到所有本地可用的网络接口,可以使得服务器程序更灵活地接受来自不同网络接口的请求。

bind()

cpp 复制代码
#include <sys/socket.h>
int bind(int socket, const struct sockaddr *address,
    socklen_t address_len);

返回值:绑定成功成功返回 0,否则返回 -1 。

listen()

cpp 复制代码
#include <sys/socket.h>
int listen(int socket, int backlog);

返回值:监听成功返回 0,否则返回 -1 。

accept()

cpp 复制代码
#include <sys/socket.h>
int accept(int socket, struct sockaddr *restrict address,
    socklen_t *restrict address_len);

返回值:接受失败返回 -1,成功则返回接受套接字的非负文件描述。

read()

从套接字接收缓存中读取收到的数据。

cpp 复制代码
#include <unistd.h>
ssize_t read(int fildes, void *buf, size_t nbyte);

成功完成后,将返回一个非负整数,表示实际读取的字节数。否则,函数将返回 -1 并设置 errno 以指示错误。

server 端 C++

cpp 复制代码
// server.cpp
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define PORT 12000     // 端口
#define BUFFER_SIZE 1024

class Server {
public:
    Server() {
        server_fd = socket(AF_INET, SOCK_STREAM, 0);
        if (server_fd < 0) {
            std::cerr << "Socket creation failed" << std::endl;
            exit(EXIT_FAILURE);
        }

        sockaddr_in address;
        int addrlen = sizeof(address);
        address.sin_family = AF_INET;
        address.sin_addr.s_addr = INADDR_ANY;
        address.sin_port = htons(PORT);

        if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
            std::cerr << "Bind failed" << std::endl;
            close(server_fd);
            exit(EXIT_FAILURE);
        }

        if (listen(server_fd, 3) < 0) {
            std::cerr << "Listen failed" << std::endl;
            close(server_fd);
            exit(EXIT_FAILURE);
        }

        std::cout << "Server is listening on port " << PORT << std::endl;
    }

    ~Server() {
        close(server_fd);
    }

    void acceptConnection() {
        sockaddr_in address;
        int addrlen = sizeof(address);
        int new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen);
        if (new_socket < 0) {
            std::cerr << "Accept failed" << std::endl;
            return;
        }

        char buffer[BUFFER_SIZE];
        int valread = read(new_socket, buffer, BUFFER_SIZE);
        std::cout << "Message from client: " << buffer << std::endl;

        const char *response = "Hello from server";
        send(new_socket, response, strlen(response), 0);
        std::cout << "Response sent to client" << std::endl;

        close(new_socket);
    }

private:
    int server_fd;
};

int main() {
    Server server;
    while (true) {
        server.acceptConnection();
    }
    return 0;
}

编译:g++ -o server server.cpp

运行

Linux socket 编程在线英文文档

点击跳转

相关推荐
zx_zx_1232 小时前
多态的学习
c++·学习
想见感3 小时前
自定义集成ESXI网卡驱动
linux
初晴~4 小时前
【动态规划】打家劫舍类问题
java·数据结构·c++·python·算法·leetcode·动态规划
IT19954 小时前
Linux笔记-对Linux环境变量的进一步认识(2024-08-09)
linux·运维·笔记·运维开发
cleveryuoyuo4 小时前
红黑树分析
c++
zkf01000076 小时前
ISAAC SIM踩坑记录--ROS2相机影像发布
linux
六月悉茗7 小时前
【C语言 - 简易架构】
c语言·开发语言
qq_1873526347 小时前
c++基础34转义字符和字符函数
c++·转义字符和字符函数
風清掦7 小时前
C/C++每日一练:查找链表的中间节点
c语言·c++·链表
FFDUST7 小时前
C++ 优先算法 —— 四数之和(双指针)
c语言·开发语言·c++·算法·leetcode·1024程序员节