MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Linux C语言TCP服务器搭建

2024-06-066.9k 阅读

1. TCP 协议基础

1.1 TCP 协议概述

TCP(Transmission Control Protocol)即传输控制协议,是一种面向连接的、可靠的、基于字节流的传输层通信协议。在 Linux C 语言开发中,搭建 TCP 服务器离不开对 TCP 协议的深入理解。

TCP 协议通过三次握手建立连接,四次挥手断开连接,以此确保数据传输的可靠性。在建立连接时,客户端发送 SYN 包到服务器,服务器收到后回复 SYN + ACK 包,客户端再发送 ACK 包,三次握手完成连接建立。断开连接时,客户端发送 FIN 包,服务器回复 ACK 包,服务器再发送 FIN 包,客户端回复 ACK 包,四次挥手完成连接断开。

1.2 TCP 协议的特点

  • 可靠性:TCP 协议通过校验和、重传机制等确保数据的准确传输。当发送方发送数据后,会启动一个定时器,如果在规定时间内没有收到接收方的确认(ACK),就会重传数据。
  • 面向连接:在数据传输之前,需要先建立连接,数据传输完成后再断开连接。这种方式使得通信双方可以在连接的基础上进行有序的数据传输。
  • 字节流:TCP 协议将数据看作是无结构的字节流,应用层的数据会被 TCP 协议按照一定规则进行分割和封装,然后在接收方再重新组装还原。

2. Linux 网络编程基础

2.1 套接字(Socket)

套接字是 Linux 网络编程的基础,它是一种抽象的通信端点。在 TCP 服务器搭建中,主要使用基于 IPv4 的套接字。套接字有多种类型,对于 TCP 协议,使用的是 SOCK_STREAM 类型,它提供面向连接的可靠数据传输。

在 Linux 中,通过 socket() 函数来创建套接字。其函数原型如下:

#include <sys/socket.h>
int socket(int domain, int type, int protocol);
  • domain:指定协议族,对于 IPv4 通常使用 AF_INET
  • type:指定套接字类型,对于 TCP 使用 SOCK_STREAM
  • protocol:通常设置为 0,表示使用默认协议,对于 TCP 就是 IPPROTO_TCP

2.2 地址结构

在网络编程中,需要使用地址结构来表示网络地址。对于 IPv4,使用 sockaddr_in 结构,定义如下:

#include <netinet/in.h>
struct sockaddr_in {
    sa_family_t    sin_family; /* 协议族,AF_INET */
    in_port_t      sin_port;   /* 端口号 */
    struct in_addr sin_addr;   /* IPv4 地址 */
};
struct in_addr {
    in_addr_t s_addr; /* 32 位 IPv4 地址 */
};

在使用时,需要将端口号转换为网络字节序,可以使用 htons() 函数,将 IP 地址转换为网络字节序可以使用 inet_addr() 函数或 inet_pton() 函数。

2.3 绑定(bind)

创建套接字后,需要将套接字绑定到一个本地地址和端口上,这通过 bind() 函数实现。函数原型如下:

#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • sockfd:通过 socket() 函数创建的套接字描述符。
  • addr:指向 sockaddr 结构的指针,在实际使用中通常转换为 sockaddr_in 结构。
  • addrlenaddr 结构的长度。

2.4 监听(listen)

绑定完成后,服务器需要进入监听状态,等待客户端的连接请求。这通过 listen() 函数实现。函数原型如下:

#include <sys/socket.h>
int listen(int sockfd, int backlog);
  • sockfd:套接字描述符。
  • backlog:指定等待连接队列的最大长度,它表示服务器可以同时处理的最大连接数。

2.5 接受连接(accept)

当有客户端发起连接请求时,服务器通过 accept() 函数来接受连接。函数原型如下:

#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • sockfd:监听套接字描述符。
  • addr:用于存储客户端地址的结构指针,如果不关心客户端地址可以设为 NULL
  • addrlenaddr 结构的长度指针。

3. Linux C 语言 TCP 服务器搭建步骤

3.1 创建套接字

首先,使用 socket() 函数创建一个 TCP 套接字。以下是示例代码:

#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>

int main() {
    int sockfd;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("socket creation failed");
        return -1;
    }
    printf("Socket created successfully\n");
    // 后续代码将在此处继续添加
    return 0;
}

在这段代码中,socket(AF_INET, SOCK_STREAM, 0) 创建了一个基于 IPv4 的 TCP 套接字。如果创建失败,socket() 函数返回 -1,并通过 perror() 函数输出错误信息。

3.2 绑定地址和端口

创建套接字后,需要绑定一个本地地址和端口。假设我们要绑定到本地 IP 127.0.0.1 和端口 8888,示例代码如下:

#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <string.h>

int main() {
    int sockfd;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("socket creation failed");
        return -1;
    }
    printf("Socket created successfully\n");

    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    memset(&servaddr, 0, sizeof(servaddr));

    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    servaddr.sin_port = htons(8888);

    if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
        perror("bind failed");
        close(sockfd);
        return -1;
    }
    printf("Bind successful\n");
    // 后续代码将在此处继续添加
    return 0;
}

在上述代码中,首先初始化 servaddr 结构,设置协议族为 AF_INET,IP 地址为 127.0.0.1,端口号为 8888(经过 htons() 转换为网络字节序)。然后使用 bind() 函数将套接字 sockfd 绑定到指定的地址和端口。如果绑定失败,bind() 函数返回 -1,并通过 perror() 函数输出错误信息,同时关闭套接字。

3.3 监听连接

绑定成功后,服务器进入监听状态。示例代码如下:

#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <string.h>

int main() {
    int sockfd;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("socket creation failed");
        return -1;
    }
    printf("Socket created successfully\n");

    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    memset(&servaddr, 0, sizeof(servaddr));

    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    servaddr.sin_port = htons(8888);

    if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
        perror("bind failed");
        close(sockfd);
        return -1;
    }
    printf("Bind successful\n");

    if (listen(sockfd, 5) < 0) {
        perror("listen failed");
        close(sockfd);
        return -1;
    }
    printf("Listening for connections...\n");
    // 后续代码将在此处继续添加
    return 0;
}

这里 listen(sockfd, 5) 使服务器在套接字 sockfd 上进行监听,最大等待连接数设为 5。如果监听失败,listen() 函数返回 -1,并通过 perror() 函数输出错误信息,同时关闭套接字。

3.4 接受连接并通信

当有客户端连接时,服务器使用 accept() 函数接受连接,并与客户端进行通信。以下是完整的示例代码:

#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>

#define BUFFER_SIZE 1024

int main() {
    int sockfd;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("socket creation failed");
        return -1;
    }
    printf("Socket created successfully\n");

    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    memset(&servaddr, 0, sizeof(servaddr));

    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    servaddr.sin_port = htons(8888);

    if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
        perror("bind failed");
        close(sockfd);
        return -1;
    }
    printf("Bind successful\n");

    if (listen(sockfd, 5) < 0) {
        perror("listen failed");
        close(sockfd);
        return -1;
    }
    printf("Listening for connections...\n");

    struct sockaddr_in cliaddr;
    socklen_t clilen = sizeof(cliaddr);
    int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen);
    if (connfd < 0) {
        perror("accept failed");
        close(sockfd);
        return -1;
    }
    printf("Connection accepted from client\n");

    char buffer[BUFFER_SIZE];
    ssize_t n = read(connfd, buffer, sizeof(buffer));
    if (n < 0) {
        perror("read failed");
        close(connfd);
        close(sockfd);
        return -1;
    }
    buffer[n] = '\0';
    printf("Received from client: %s\n", buffer);

    const char *response = "Message received successfully";
    n = write(connfd, response, strlen(response));
    if (n < 0) {
        perror("write failed");
        close(connfd);
        close(sockfd);
        return -1;
    }

    close(connfd);
    close(sockfd);
    return 0;
}

在这段代码中,accept(sockfd, (struct sockaddr *)&cliaddr, &clilen) 接受客户端的连接请求,返回一个新的套接字 connfd 用于与客户端通信。然后使用 read() 函数从客户端读取数据,将读取到的数据存储在 buffer 中,并在终端输出。接着,服务器向客户端发送响应消息,使用 write() 函数将 response 发送给客户端。最后,关闭与客户端通信的套接字 connfd 和监听套接字 sockfd

4. 错误处理与优化

4.1 错误处理

在实际的 TCP 服务器开发中,错误处理至关重要。除了在上述代码中使用 perror() 函数输出错误信息外,还可以根据具体的错误码进行更细致的处理。例如,在 bind() 函数失败时,可能是端口被占用,可以通过获取错误码并进行相应处理。

#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <string.h>
#include <errno.h>

int main() {
    int sockfd;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("socket creation failed");
        return -1;
    }
    printf("Socket created successfully\n");

    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    memset(&servaddr, 0, sizeof(servaddr));

    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    servaddr.sin_port = htons(8888);

    if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
        if (errno == EADDRINUSE) {
            printf("Port is already in use\n");
        } else {
            perror("bind failed");
        }
        close(sockfd);
        return -1;
    }
    printf("Bind successful\n");
    // 后续代码与之前示例类似
    return 0;
}

通过 errno 获取错误码,当 errnoEADDRINUSE 时,表示端口被占用,进行针对性的提示。

4.2 优化措施

  • 多线程与多路复用:在处理多个客户端连接时,可以使用多线程或多路复用技术。多线程可以为每个客户端连接创建一个线程进行处理,实现并发服务。例如,使用 pthread 库创建线程。
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>

#define BUFFER_SIZE 1024

void *handle_client(void *arg) {
    int connfd = *((int *)arg);
    char buffer[BUFFER_SIZE];
    ssize_t n = read(connfd, buffer, sizeof(buffer));
    if (n < 0) {
        perror("read failed");
        close(connfd);
        pthread_exit(NULL);
    }
    buffer[n] = '\0';
    printf("Received from client: %s\n", buffer);

    const char *response = "Message received successfully";
    n = write(connfd, response, strlen(response));
    if (n < 0) {
        perror("write failed");
    }
    close(connfd);
    pthread_exit(NULL);
}

int main() {
    int sockfd;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("socket creation failed");
        return -1;
    }
    printf("Socket created successfully\n");

    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    memset(&servaddr, 0, sizeof(servaddr));

    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    servaddr.sin_port = htons(8888);

    if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
        perror("bind failed");
        close(sockfd);
        return -1;
    }
    printf("Bind successful\n");

    if (listen(sockfd, 5) < 0) {
        perror("listen failed");
        close(sockfd);
        return -1;
    }
    printf("Listening for connections...\n");

    while (1) {
        struct sockaddr_in cliaddr;
        socklen_t clilen = sizeof(cliaddr);
        int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen);
        if (connfd < 0) {
            perror("accept failed");
            continue;
        }
        pthread_t tid;
        if (pthread_create(&tid, NULL, handle_client, &connfd) != 0) {
            perror("pthread_create failed");
            close(connfd);
        }
    }
    close(sockfd);
    return 0;
}

在这段代码中,每当有新的客户端连接时,创建一个新的线程 tid 来处理该客户端的通信,handle_client 函数为线程的执行函数。

多路复用技术则可以使用 select()poll()epoll() 等函数,在一个线程中处理多个套接字的事件。以 epoll 为例,以下是简单的示例代码:

#include <sys/epoll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>

#define MAX_EVENTS 10
#define BUFFER_SIZE 1024

int main() {
    int sockfd;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("socket creation failed");
        return -1;
    }
    printf("Socket created successfully\n");

    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    memset(&servaddr, 0, sizeof(servaddr));

    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    servaddr.sin_port = htons(8888);

    if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
        perror("bind failed");
        close(sockfd);
        return -1;
    }
    printf("Bind successful\n");

    if (listen(sockfd, 5) < 0) {
        perror("listen failed");
        close(sockfd);
        return -1;
    }
    printf("Listening for connections...\n");

    int epollfd = epoll_create1(0);
    if (epollfd == -1) {
        perror("epoll_create1");
        close(sockfd);
        return -1;
    }

    struct epoll_event event;
    event.data.fd = sockfd;
    event.events = EPOLLIN;
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, sockfd, &event) == -1) {
        perror("epoll_ctl: listen_sock");
        close(sockfd);
        close(epollfd);
        return -1;
    }

    struct epoll_event events[MAX_EVENTS];
    while (1) {
        int nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
        if (nfds == -1) {
            perror("epoll_wait");
            break;
        }
        for (int i = 0; i < nfds; ++i) {
            if (events[i].data.fd == sockfd) {
                struct sockaddr_in cliaddr;
                socklen_t clilen = sizeof(cliaddr);
                int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen);
                if (connfd == -1) {
                    perror("accept");
                    continue;
                }
                event.data.fd = connfd;
                event.events = EPOLLIN | EPOLLET;
                if (epoll_ctl(epollfd, EPOLL_CTL_ADD, connfd, &event) == -1) {
                    perror("epoll_ctl: conn_sock");
                    close(connfd);
                }
            } else {
                int connfd = events[i].data.fd;
                char buffer[BUFFER_SIZE];
                ssize_t n = read(connfd, buffer, sizeof(buffer));
                if (n == -1) {
                    if (errno == EAGAIN || errno == EWOULDBLOCK) {
                        continue;
                    } else {
                        perror("read");
                        if (epoll_ctl(epollfd, EPOLL_CTL_DEL, connfd, NULL) == -1) {
                            perror("epoll_ctl: del conn_sock");
                        }
                        close(connfd);
                    }
                } else if (n == 0) {
                    if (epoll_ctl(epollfd, EPOLL_CTL_DEL, connfd, NULL) == -1) {
                        perror("epoll_ctl: del conn_sock");
                    }
                    close(connfd);
                } else {
                    buffer[n] = '\0';
                    printf("Received from client: %s\n", buffer);
                    const char *response = "Message received successfully";
                    n = write(connfd, response, strlen(response));
                    if (n == -1) {
                        perror("write");
                        if (epoll_ctl(epollfd, EPOLL_CTL_DEL, connfd, NULL) == -1) {
                            perror("epoll_ctl: del conn_sock");
                        }
                        close(connfd);
                    }
                }
            }
        }
    }
    close(sockfd);
    close(epollfd);
    return 0;
}

在这段代码中,使用 epoll 来监听套接字事件。首先通过 epoll_create1() 创建一个 epoll 实例,然后将监听套接字 sockfd 添加到 epoll 实例中进行监听。当有新的客户端连接时,将客户端连接套接字 connfd 也添加到 epoll 实例中,并在事件循环中处理读、写等事件。

  • 缓冲区优化:合理设置套接字的发送和接收缓冲区大小可以提高数据传输效率。可以使用 setsockopt() 函数来设置缓冲区大小。例如,增大接收缓冲区大小:
int recvbuf = 1024 * 1024; // 1MB
setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &recvbuf, sizeof(recvbuf));

在上述代码中,将套接字 sockfd 的接收缓冲区设置为 1MB。

5. 安全性考虑

5.1 防止缓冲区溢出

在处理客户端数据时,要注意防止缓冲区溢出。例如,在读取客户端数据时,确保读取的数据不会超过缓冲区大小。在之前的代码示例中,使用 read(connfd, buffer, sizeof(buffer)) 来读取数据,这样可以避免缓冲区溢出。但如果在处理数据过程中,进行字符串拼接等操作,要使用安全的函数,如 snprintf() 而不是 sprintf()

char buffer[BUFFER_SIZE];
char new_buffer[BUFFER_SIZE];
snprintf(new_buffer, sizeof(new_buffer), "Prefix: %s", buffer);

使用 snprintf() 函数可以确保不会因为源字符串过长而导致目标缓冲区溢出。

5.2 认证与授权

对于一些需要保护的服务器,进行认证与授权是必要的。可以在客户端连接后,要求客户端发送认证信息,如用户名和密码。服务器验证通过后,才允许客户端进行后续操作。例如,使用简单的用户名和密码验证:

#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>

#define BUFFER_SIZE 1024

int main() {
    int sockfd;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("socket creation failed");
        return -1;
    }
    printf("Socket created successfully\n");

    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    memset(&servaddr, 0, sizeof(servaddr));

    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    servaddr.sin_port = htons(8888);

    if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
        perror("bind failed");
        close(sockfd);
        return -1;
    }
    printf("Bind successful\n");

    if (listen(sockfd, 5) < 0) {
        perror("listen failed");
        close(sockfd);
        return -1;
    }
    printf("Listening for connections...\n");

    struct sockaddr_in cliaddr;
    socklen_t clilen = sizeof(cliaddr);
    int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen);
    if (connfd < 0) {
        perror("accept failed");
        close(sockfd);
        return -1;
    }
    printf("Connection accepted from client\n");

    char buffer[BUFFER_SIZE];
    ssize_t n = read(connfd, buffer, sizeof(buffer));
    if (n < 0) {
        perror("read failed");
        close(connfd);
        close(sockfd);
        return -1;
    }
    buffer[n] = '\0';

    const char *expected_username = "admin";
    const char *expected_password = "password";
    char *username = strtok(buffer, ":");
    char *password = strtok(NULL, ":");

    if (strcmp(username, expected_username) == 0 && strcmp(password, expected_password) == 0) {
        const char *response = "Authentication successful";
        n = write(connfd, response, strlen(response));
        if (n < 0) {
            perror("write failed");
        }
    } else {
        const char *response = "Authentication failed";
        n = write(connfd, response, strlen(response));
        if (n < 0) {
            perror("write failed");
        }
        close(connfd);
    }

    close(connfd);
    close(sockfd);
    return 0;
}

在上述代码中,客户端发送格式为 “用户名:密码” 的认证信息,服务器通过 strtok() 函数解析用户名和密码,并与预定义的用户名和密码进行比较,根据验证结果发送相应的响应。

5.3 加密通信

为了防止数据在传输过程中被窃取或篡改,可以使用加密通信。常见的加密库有 OpenSSL。使用 OpenSSL 进行简单的 TLS 加密通信示例如下:

#include <openssl/ssl.h>
#include <openssl/err.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>

#define BUFFER_SIZE 1024

void handleErrors() {
    ERR_print_errors_fp(stderr);
    abort();
}

int main() {
    int sockfd;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("socket creation failed");
        return -1;
    }
    printf("Socket created successfully\n");

    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    memset(&servaddr, 0, sizeof(servaddr));

    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    servaddr.sin_port = htons(8888);

    if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
        perror("bind failed");
        close(sockfd);
        return -1;
    }
    printf("Bind successful\n");

    if (listen(sockfd, 5) < 0) {
        perror("listen failed");
        close(sockfd);
        return -1;
    }
    printf("Listening for connections...\n");

    SSL_library_init();
    SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
    if (!ctx) {
        handleErrors();
    }

    if (SSL_CTX_use_certificate_file(ctx, "server.crt", SSL_FILETYPE_PEM) <= 0) {
        handleErrors();
    }

    if (SSL_CTX_use_PrivateKey_file(ctx, "server.key", SSL_FILETYPE_PEM) <= 0) {
        handleErrors();
    }

    struct sockaddr_in cliaddr;
    socklen_t clilen = sizeof(cliaddr);
    int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen);
    if (connfd < 0) {
        perror("accept failed");
        close(sockfd);
        return -1;
    }
    printf("Connection accepted from client\n");

    SSL *ssl = SSL_new(ctx);
    if (!ssl) {
        handleErrors();
    }

    if (SSL_set_fd(ssl, connfd) != 1) {
        handleErrors();
    }

    if (SSL_accept(ssl) != 1) {
        handleErrors();
    }

    char buffer[BUFFER_SIZE];
    ssize_t n = SSL_read(ssl, buffer, sizeof(buffer));
    if (n < 0) {
        handleErrors();
    }
    buffer[n] = '\0';
    printf("Received from client: %s\n");

    const char *response = "Message received successfully";
    n = SSL_write(ssl, response, strlen(response));
    if (n < 0) {
        handleErrors();
    }

    SSL_free(ssl);
    SSL_CTX_free(ctx);
    close(connfd);
    close(sockfd);
    return 0;
}

在这段代码中,首先初始化 OpenSSL 库,创建一个 SSL 上下文 ctx,并加载服务器证书和私钥。在接受客户端连接后,创建一个 SSL 对象 ssl,并将其与连接套接字 connfd 关联。通过 SSL_accept() 进行 SSL 握手,之后使用 SSL_read()SSL_write() 进行加密数据的读取和写入。最后,释放 SSL 资源并关闭套接字。

通过上述步骤和技术,可以搭建一个功能较为完善、安全可靠的 Linux C 语言 TCP 服务器。在实际应用中,还需要根据具体需求进一步优化和扩展。