Linux网络编程-极简HTTP&UDP服务器

HTTP服务器

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

#define PORT 8080
#define BUFFER_SIZE 2048

void handle_client(int client_socket) {
    char buffer[BUFFER_SIZE];
    recv(client_socket, buffer, sizeof(buffer) - 1, 0);  
    printf("Received request:\n%s\n", buffer);

    char response[] = 
        "HTTP/1.1 200 OK\r\n"
        "Content-Type: text/plain\r\n"
        "Content-Length: 15\r\n"
        "Connection: close\r\n\r\n"
        "Hello, World!\r\n";

    send(client_socket, response, sizeof(response) - 1, 0);  
    close(client_socket);
}

int main() {
    int server_socket, client_socket;
    struct sockaddr_in server_address, client_address;
    socklen_t client_len;

    server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket == -1) {
        perror("Could not create socket");
        exit(1);
    }

    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = INADDR_ANY;
    server_address.sin_port = htons(PORT);

    if (bind(server_socket, (struct sockaddr *)&server_address, sizeof(server_address)) == -1) {
        perror("Bind failed");
        exit(1);
    }

    if (listen(server_socket, 10) == -1) {
        perror("Listen failed");
        exit(1);
    }

    printf("HTTP server is running on port %d ...\n", PORT);

    while (1) {
        client_len = sizeof(client_address);
        client_socket = accept(server_socket, (struct sockaddr *)&client_address, &client_len);
        if (client_socket == -1) {
            perror("Accept failed");
            continue;
        }

        handle_client(client_socket);
    }

    close(server_socket);
    return 0;
}

HTTP客户端

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

#define SERVER_PORT 8080
#define BUFFER_SIZE 2048

int main(int argc, char *argv[]) {
    int client_socket;
    struct sockaddr_in server_address;
    char buffer[BUFFER_SIZE];

    // 检查命令行参数
    if (argc != 2) {
        printf("Usage: %s <Server IP>\n", argv[0]);
        exit(1);
    }

    client_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (client_socket == -1) {
        perror("Could not create socket");
        exit(1);
    }

    server_address.sin_family = AF_INET;
    server_address.sin_port = htons(SERVER_PORT);
    if (inet_pton(AF_INET, argv[1], &server_address.sin_addr) <= 0) {
        perror("inet_pton() failed");
        exit(1);
    }

    // 连接到服务器
    if (connect(client_socket, (struct sockaddr *)&server_address, sizeof(server_address)) == -1) {
        perror("Connect failed");
        close(client_socket);
        exit(1);
    }

    // 向服务器发送HTTP请求
    char request[] = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n";
    send(client_socket, request, strlen(request), 0);

    // 接收服务器响应
    int bytes_received = recv(client_socket, buffer, BUFFER_SIZE - 1, 0);
    if (bytes_received == -1) {
        perror("Error receiving data");
        close(client_socket);
        exit(1);
    }

    buffer[bytes_received] = '\0';
    printf("Received from server:\n%s\n", buffer);

    close(client_socket);
    return 0;
}

或者在浏览器或者使用curl工具访问http://localhost:8080,也能够看到来自服务器的"Hello, World!"响应。

bash 复制代码
curl http://localhost:8080
curl -X POST -d "param1=value1&param2=value2" http://localhost:8080

UDP服务器

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

#define PORT 8080
#define BUFFER_SIZE 2048

void handle_client(int server_socket, struct sockaddr_in *client_address, socklen_t client_len) {
    char buffer[BUFFER_SIZE];
    int bytes_received = recvfrom(server_socket, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)client_address, &client_len);
    
    if (bytes_received == -1) {
        perror("Error receiving data");
        return;
    }

    buffer[bytes_received] = '\0';
    printf("Received request from %s:%d\n%s\n", inet_ntoa(client_address->sin_addr), ntohs(client_address->sin_port), buffer);

    char response[] = "Hello, World from UDP!";
    sendto(server_socket, response, sizeof(response) - 1, 0, (struct sockaddr *)client_address, client_len);
}

int main() {
    int server_socket;
    struct sockaddr_in server_address, client_address;
    socklen_t client_len;

    server_socket = socket(AF_INET, SOCK_DGRAM, 0);
    if (server_socket == -1) {
        perror("Could not create socket");
        exit(1);
    }

    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = INADDR_ANY;
    server_address.sin_port = htons(PORT);

    if (bind(server_socket, (struct sockaddr *)&server_address, sizeof(server_address)) == -1) {
        perror("Bind failed");
        exit(1);
    }

    printf("UDP server is running on port %d...\n", PORT);

    while (1) {
        client_len = sizeof(client_address);
        handle_client(server_socket, &client_address, client_len);
    }

    close(server_socket);
    return 0;
}

UDP客户端

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

#define SERVER_PORT 8080
#define BUFFER_SIZE 2048

int main(int argc, char *argv[]) {
    int client_socket;
    struct sockaddr_in server_address;
    char buffer[BUFFER_SIZE];

    // 检查命令行参数
    if (argc != 2) {
        printf("Usage: %s <Server IP>\n", argv[0]);
        exit(1);
    }

    client_socket = socket(AF_INET, SOCK_DGRAM, 0);
    if (client_socket == -1) {
        perror("Could not create socket");
        exit(1);
    }

    memset(&server_address, 0, sizeof(server_address));
    server_address.sin_family = AF_INET;
    server_address.sin_port = htons(SERVER_PORT);
    if (inet_pton(AF_INET, argv[1], &server_address.sin_addr) <= 0) {
        perror("inet_pton() failed");
        exit(1);
    }

    // 向服务器发送消息
    char message[] = "Hello, UDP Server!";
    sendto(client_socket, message, strlen(message), 0, (struct sockaddr *)&server_address, sizeof(server_address));

    // 接收来自服务器的响应
    int bytes_received = recvfrom(client_socket, buffer, BUFFER_SIZE - 1, 0, NULL, NULL);
    if (bytes_received == -1) {
        perror("Error receiving data");
        exit(1);
    }

    buffer[bytes_received] = '\0';
    printf("Received from server: %s\n", buffer);

    close(client_socket);
    return 0;
}

或者使用以下命令向UDP服务器发送数据:

bash 复制代码
echo "Your message here" | nc -u -w1 localhost 8080

为什么 send()recv() 适用于TCP而不是UDP

TCP (Transmission Control Protocol) 是一个面向连接的协议。当我们谈论TCP连接时,我们指的是一个由客户端套接字和服务器套接字组成的持久的网络连接。这意味着,一旦建立了TCP连接,双方都知道对方的IP地址和端口号,因此不需要在每次数据交换时重新指定。

由于这种固定的连接特性,当我们想要通过已经建立的TCP连接发送或接收数据时,不需要再次指定远程的地址和端口。这就是为什么 send()recv() 适用于TCP:它们只发送和接收数据,不涉及地址信息。

另一方面,UDP (User Datagram Protocol) 是一个无连接的协议。这意味着每次数据包的发送都是独立的,没有固定的连接或对端信息与其相关联。因此,当我们使用UDP发送数据时,需要指定要将数据发送到的地址和端口。这就是 sendto()recvfrom() 的用途。

总结:

  • TCP是面向连接的,一旦连接建立,系统就知道数据将被发送到哪里,因此只需要 send()recv()
  • UDP是无连接的,所以每次发送数据都需要指定目的地,这就是为什么需要 sendto()recvfrom()

当然,技术上可以为UDP使用 connect() 来设置默认的远程地址和端口,然后使用 send()recv(),但这样做是非典型的,并可能导致与UDP的无连接特性不一致的行为。


当我们说SOCK_STREAM(如TCP)为我们提供了一个连续的字节流,并且没有内在的消息边界,这是什么意思呢?

  1. 连续的字节流

    • 当我们使用TCP发送数据,是在向一个开放的连接发送一个连续的字节流。例如,如果连续调用三次send()函数,发送三个字符串,它们可能会被接收端作为一个连续的字节流接收。
    • 不存在固定的消息大小或结构,我们可以任意发送任何大小的数据。
  2. 没有内在的消息边界

    • 在TCP中,数据的发送和接收是流式的,没有固定的消息边界。这意味着,如果发送方连续发送了两个数据块,接收方没有办法仅通过TCP来知道这两个数据块是如何分隔的。接收方可能在一个recv()调用中接收到两个数据块的部分或全部。
    • 例如,如果发送方连续执行两次send(),分别发送字符串"HELLO"和"WORLD",接收方可能需要多次recv()调用来接收这些数据,或者可能在一个recv()调用中接收到字符串"HELLOWORLD"。

这种没有消息边界的特性与SOCK_DGRAM(如UDP)形成了对比,在UDP中,每个sendto()调用发送的数据都被视为一个独立的消息,且在接收端通过一个recvfrom()调用完整地接收。

这就是为什么在使用TCP时,应用程序往往需要自己实现某种消息边界的机制,例如通过使用特定的分隔符、固定长度的头部或其他协议来定义消息的开始和结束。


SOCK_DGRAM 提供了一个有边界的服务。这意味着,当我们使用 SOCK_DGRAM(如UDP)发送一个数据报,接收端会将这个数据报作为一个整体进行处理,不会将它与其他数据报合并或分割。每个 sendto() 调用对应一个完整且单独的消息,该消息在接收端使用一个 recvfrom() 调用完整接收。

换句话说,数据报协议保留了消息边界。例如,如果发送方调用了两次 sendto(),接收方也必须调用两次 recvfrom() 来接收这两条消息。这与 SOCK_STREAM(如TCP)不同,在流式协议中,数据是一个连续的字节流,没有内在的消息边界。


关于curl命令和nc命令的详细介绍以及网络编程中常用的函数说明,读者可移步至以下博客:

Linux- curl命令

Linux中的nc命令

Linux网络编程- recvfrom() & sendto()

Linux- 网络编程初探

相关推荐
Orange_sparkle几秒前
Windows/Linux离线部署IndexTTS2
linux·运维·服务器
..空空的人5 分钟前
C++基于protobuf实现仿RabbitMQ消息队列---技术认识1
服务器·网络·个人开发·protobuf·muduo
番茄灭世神10 分钟前
升级新版arm-none-eabi-gcc的方法
linux·运维·arm开发
神秘的土鸡23 分钟前
openEuler 安全加固与性能实测: SELinux,防火墙等多维防护实践
网络·安全·apache·openeuler
hjs_deeplearning25 分钟前
应用篇#4:Qwen2视觉语言模型(VLM)的服务器部署
服务器·人工智能·python·深度学习·语言模型
Ronin30525 分钟前
【Linux网络】数据链路层
linux·网络·数据链路层·arp协议·局域网通信
Aze..27 分钟前
PVE安装 Ubuntu 服务器版
运维·服务器·数据库
星梦客33 分钟前
FRP 内网穿透工具部署教程
网络·经验分享·笔记
zt1985q43 分钟前
fnOS 飞牛云 NAS 本地部署开源 TTS 文本转语音工具 EasyVoice 并实现外部访问
运维·服务器·网络协议·开源
TroubleBoy丶43 分钟前
Docker可用镜像
java·linux·jvm·docker