You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

使用Raw Socket构建客户端服务端程序遇权限问题及技术咨询

关于Raw Socket客户端-服务端实现的问题解答

别担心,第一次接触raw socket踩这些坑太正常了,我来一步步帮你拆解问题并修正代码。

先看你提供的代码和遇到的错误:

你的代码

服务端代码(server.c)

/* server.c */
#include "../cn.h"
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
int main(int argc, char const *argv[]) {
    int sfd = socket(AF_INET, SOCK_RAW, 253);
    if (sfd < 0) {
        perror("Could not create socket");
        exit(0);
    }
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    if (bind(sfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        perror("Could not bind");
    }
    int nsfd = accept(sfd, NULL, NULL);
    if (nsfd < 0) {
        perror("Could not accept");
        exit(0);
    }
    char buffer[20];
    int sz;
    while (1) {
        if ((sz = read(nsfd, buffer, 20)) < 0) {
            perror("Could not read");
        } else {
            buffer[sz] = '\0';
            strcat(buffer, " form server");
            if (write(nsfd, buffer, strlen(buffer)) < 0) {
                perror("Could not send");
            }
        }
    }
    return 0;
}

客户端代码(client.c)

#include "../cn.h"
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
int main(int argc, char const *argv[]) {
    int sfd = socket(AF_INET, SOCK_RAW, 253);
    if (sfd < 0) {
        perror("Could not create socket");
        exit(0);
    }
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    if (connect(sfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        perror("Could not connect");
        exit(0);
    }
    char buffer[20];
    int sz;
    while (1) {
        scanf("%[^\n]s", buffer);
        while (getchar() != '\n') ;
        if (write(sfd, buffer, strlen(buffer)) < 0) {
            perror("Could not write");
        } else if ((sz = read(sfd,buffer, 20)) < 0) {
            perror("Could not read");
        } else {
            buffer[sz] = '\0';
            printf("Reading: %s\n", buffer);
        }
    }
    return 0;
}

你的问题解答

1. 遗漏的配置/步骤

你主要遗漏了两个关键环节:

  • 需要root权限运行程序:raw socket属于特权资源,普通用户无法创建,必须用sudo启动你的服务端和客户端程序。
  • 错误使用了面向连接的APIaccept()connect()read()/write()这些函数是为TCP这类面向连接的协议设计的,raw socket是无连接的,不能用这些函数,需要换成recvfrom()sendto()来收发数据包。

2. 为何创建raw socket不被允许?

因为raw socket允许程序直接操作网络层(IP)甚至链路层的数据包,能够绕过内核的传输层协议栈(比如TCP/UDP的校验、重传等逻辑),属于高权限操作。为了系统安全,Linux等Unix-like系统限制只有root用户(或拥有CAP_NET_RAW能力的用户)才能创建raw socket。

3. 如何使用带有自定义协议ID的raw socket?

自定义协议ID(你用的253是IANA预留的私有协议号,合法可用)的raw socket使用步骤如下:

核心要点:

  • 内核不会帮你处理自定义协议的传输逻辑,所有数据包的构造、解析都需要自己完成。
  • recvfrom()接收数据包并获取发送方地址,用sendto()发送数据包到目标地址。
  • 可选设置IP_HDRINCL选项,手动构造IP头(默认内核会帮你添加IP头)。

修正后的示例代码:

服务端(修正版)
/* server.c */
#include "../cn.h"
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>

int main(int argc, char const *argv[]) {
    // 创建自定义协议253的raw socket
    int sfd = socket(AF_INET, SOCK_RAW, 253);
    if (sfd < 0) {
        perror("Could not create socket");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);

    // 绑定到回环地址
    if (bind(sfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        perror("Could not bind");
        close(sfd);
        exit(EXIT_FAILURE);
    }

    char buffer[1024];
    struct sockaddr_in client_addr;
    socklen_t client_len = sizeof(client_addr);
    int sz;

    while (1) {
        // 接收客户端数据包
        sz = recvfrom(sfd, buffer, sizeof(buffer)-1, 0, 
                     (struct sockaddr*)&client_addr, &client_len);
        if (sz < 0) {
            perror("Could not recvfrom");
            continue;
        }
        buffer[sz] = '\0';
        printf("Received from client: %s\n", buffer);

        // 构造回复内容
        strcat(buffer, " from server");
        // 发送回复到客户端
        sz = sendto(sfd, buffer, strlen(buffer), 0,
                   (struct sockaddr*)&client_addr, client_len);
        if (sz < 0) {
            perror("Could not sendto");
        }
    }

    close(sfd);
    return 0;
}
客户端(修正版)
/* client.c */
#include "../cn.h"
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>

int main(int argc, char const *argv[]) {
    int sfd = socket(AF_INET, SOCK_RAW, 253);
    if (sfd < 0) {
        perror("Could not create socket");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);

    char buffer[1024];
    struct sockaddr_in recv_addr;
    socklen_t recv_len = sizeof(recv_addr);
    int sz;

    while (1) {
        printf("Enter message: ");
        // 读取输入(修正原scanf的问题,避免缓冲区溢出)
        fgets(buffer, sizeof(buffer)-1, stdin);
        // 去掉换行符
        buffer[strcspn(buffer, "\n")] = '\0';

        // 发送到服务端
        sz = sendto(sfd, buffer, strlen(buffer), 0,
                   (struct sockaddr*)&server_addr, sizeof(server_addr));
        if (sz < 0) {
            perror("Could not sendto");
            continue;
        }

        // 接收服务端回复
        sz = recvfrom(sfd, buffer, sizeof(buffer)-1, 0,
                     (struct sockaddr*)&recv_addr, &recv_len);
        if (sz < 0) {
            perror("Could not recvfrom");
            continue;
        }
        buffer[sz] = '\0';
        printf("Reading: %s\n", buffer);
    }

    close(sfd);
    return 0;
}

运行方式:

# 启动服务端
sudo ./server
# 启动客户端(另一个终端)
sudo ./client

4. 优质学习资料

  • Man手册:直接查man 7 rawman 2 socket,这是最权威的官方文档,涵盖了raw socket的权限、选项、使用场景等所有细节。
  • 《UNIX网络编程 卷1:套接字联网API》:经典的网络编程书籍,有专门章节讲解raw socket的实现,包括自定义协议、数据包构造等内容。
  • Linux内核文档:查看内核源码中的Documentation/networking/raw.txt,里面详细说明了Linux下raw socket的底层实现和特殊注意事项。

内容的提问来源于stack exchange,提问作者Ricky

火山引擎 最新活动