【技术学习】网络学习--简单tcp服务器

templeD / 2023-06-17 / 原文

这是一个用AI生成的简单的tcp服务器代码,我稍微改动了一下命名啥的。

可以看到代码非常简短,不过却没什么问题,人工智能还是很强的。

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <cstring>
#include <unistd.h>

#define MAXLNE  4096

int main() {
    int listenfd, connfd;
    struct sockaddr_in servaddr{};
    char buffer[MAXLNE]{};

    // 创建套接字
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd == -1) {
        std::cerr << "Failed to create socket." << std::endl;
        return -1;
    }

    // 设置服务器地址
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(8080); // 指定端口号(这里使用8080)
    servaddr.sin_addr.s_addr = INADDR_ANY;

    // 绑定套接字到指定地址和端口
    if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
        std::cerr << "Failed to bind socket." << std::endl;
        return -1;
    }

    // 监听连接请求
    if (listen(listenfd, 5) < 0) { // 允许同时处理最多5个连接请求
        std::cerr << "Error in listening." << std::endl;
        return -1;
    }

    std::cout << "Server started. Listening for incoming connections..." << std::endl;

    while (true) {
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        // 接受连接请求
        connfd = accept(listenfd, (struct sockaddr *)&client, &len);
        if (connfd < 0) {
            std::cerr << "Error in accepting connection." << std::endl;
            return -1;
        }
        
        // 读取客户端发来的数据
        bzero(buffer, sizeof(buffer));
        if (read(connfd, buffer, sizeof(buffer)) < 0) {
            std::cerr << "Error in reading data." << std::endl;
            return -1;
        }

        // 输出客户端发送的消息
        std::cout << "Client message: " << buffer << std::endl;

        // 发送响应给客户端
        const char *response = "Hello from server!";
        if (write(connfd, response, strlen(response)) < 0) {
            std::cerr << "Error in sending response." << std::endl;
            return -1;
        }

        // 关闭连接套接字
        close(connfd);
    }

    // 关闭服务器套接字
    close(listenfd);

    return 0;
}

如果我们运行这个代码,会发现它只能和client完成单次的连接和发送消息,消息发送完就会断开连接并等待client再次连接。同时,这个服务器采用的是一种基于阻塞式I/O的方式,如果有一个客户端连接上并没有关闭连接,后续连接上服务器的客户端会被阻塞在accept上,无法获得服务器的响应,直到前面的客户端关闭连接。

如果我想得到一个可以多客户端同时连接,且可以保持连接循环发消息的服务器代码的话,就可以使用多线程方式。

首先在main前写一个多线程处理客户端的函数handle_client:

void *handle_client(void *arg) 
{
int connfd = *(int *)arg; char buffer[MAXLNE]{}; while(1) { // 读取客户端发来的数据 bzero(buffer, sizeof(buffer)); if (read(connfd, buffer, sizeof(buffer)) < 0) { std::cerr << "Error in reading data." << std::endl; close(connfd); return NULL; } // 输出客户端发送的消息 std::cout << "Client message: " << buffer << std::endl; // 发送响应给客户端 const char *response = "Hello from server!"; if (write(connfd, response, strlen(response)) < 0) { std::cerr << "Error in sending response." << std::endl; } } }

然后改一下while循环多线程调起

while (1) 
{
struct sockaddr_in client; socklen_t len = sizeof(client); // 接受连接请求 connfd = accept(listenfd, (struct sockaddr *)&client, &len); if (connfd < 0) { std::cerr << "Error in accepting connection." << std::endl; return -1; } // 创建一个新的线程处理客户端连接 pthread_t threadid; pthread_create(&threadid, NULL, handle_client, (void*)&connfd); }

连接效果如图

 可以看到,多个客户端同时连接访问,并发送接收消息都是没问题的。

该方式可以应对小量连接请求,客户端退出线程退出,在预估客户端同时使用不多的情况下是可用的。但毕竟多线程内存开销还是比较大的,在面对庞大的客户端访问时是非常不友好的。