凌峰创科服务平台

linux c 服务器

在Linux环境下使用C语言开发服务器程序是后端开发中的核心技能之一,涉及网络编程、进程管理、并发处理、性能优化等多个关键技术点,本文将从基础架构、核心模块、性能优化及实战案例等方面展开详细说明。

服务器基础架构设计

Linux C服务器通常采用客户端/服务器(C/S)架构,核心流程包括初始化、监听、连接处理、业务逻辑执行及资源释放,以TCP服务器为例,基本步骤如下:

  1. 创建套接字:使用socket()函数创建套接字,指定协议族(AF_INET)、套接字类型(SOCK_STREAM)和协议(IPPROTO_TCP)。
  2. 绑定地址:通过bind()将套接字与IP地址和端口号绑定,确保客户端可正确访问。
  3. 监听连接:调用listen()使套接字进入监听状态,设置最大连接队列长度(如backlog=128)。
  4. 接受连接:使用accept()阻塞等待客户端连接,返回新的套接字用于数据收发。
  5. 数据收发:通过read()/write()recv()/send()函数与客户端交互。
  6. 关闭套接字:通信结束后调用close()释放资源。

核心模块实现

网络编程

Linux C服务器主要依赖伯克利套接字接口(BSD Sockets),关键函数如下表所示:

函数名 功能描述 返回值与注意事项
socket() 创建套接字 成功返回套接字描述符,失败返回-1并设置errno
bind() 绑定IP和端口 需先将地址结构体(如struct sockaddr_in)的sin_family、sin_addr、sin_port填充
listen() 开始监听连接 第二参数backlog表示最大待处理连接数,受系统SOMAXCONN限制
accept() 接受客户端连接 成功返回新套接字描述符,失败返回-1;阻塞模式下无连接时线程会挂起
recv()/send() 接收/发送数据 返回实际传输的字节数,0表示连接关闭,-1表示错误

并发处理

为支持多客户端连接,服务器需采用并发模型,常见方案包括:

  • 多进程模型:通过fork()创建子进程处理每个连接,父进程负责监听,优点是逻辑隔离,缺点是进程创建开销大,资源消耗高。
  • 多线程模型:使用pthread_create()创建线程处理连接,共享进程内存空间,需注意线程同步(如互斥锁pthread_mutex_t)和死锁问题。
  • I/O多路复用:通过select()poll()epoll()实现单线程管理多个连接,其中epoll是Linux高效方案,支持ET(边缘触发)和LT(水平触发)模式,通过epoll_create()创建实例,epoll_ctl()添加事件,epoll_wait()等待就绪事件。

高性能优化

  • 非阻塞I/O:通过fcntl()设置套接字为非阻塞模式(O_NONBLOCK),避免accept()/read()阻塞线程。
  • 零拷贝技术:使用sendfile()函数在文件描述符间直接传输数据,减少内核态与用户态的数据拷贝。
  • 内存池:预分配内存块,避免频繁malloc()/free(),降低内存碎片和延迟。
  • 事件驱动:结合epoll和状态机设计,高效处理高并发场景。

实战案例:简单回显服务器

以下为多线程回显服务器的核心代码片段:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
void* handle_client(void* arg) {
    int client_fd = *(int*)arg;
    free(arg);
    char buffer[1024] = {0};
    while (1) {
        int bytes_read = read(client_fd, buffer, 1024);
        if (bytes_read <= 0) break;
        write(client_fd, buffer, bytes_read);
    }
    close(client_fd);
    return NULL;
}
int main() {
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr = {0};
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(8080);
    bind(server_fd, (struct sockaddr*)&addr, sizeof(addr));
    listen(server_fd, 128);
    while (1) {
        struct sockaddr_in client_addr;
        socklen_t len = sizeof(client_addr);
        int* client_fd = malloc(sizeof(int));
        *client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &len);
        pthread_t tid;
        pthread_create(&tid, NULL, handle_client, client_fd);
        pthread_detach(tid);
    }
    close(server_fd);
    return 0;
}

常见问题与解决方案

  1. 端口占用问题:若端口被占用,bind()会返回EADDRINUSE错误,可通过netstat -tulpn查看端口占用情况,或修改端口号。
  2. 文件描述符耗尽:默认情况下,单个进程可打开的文件描述符数量有限(可通过ulimit -n查看),需及时关闭无用套接字,或使用setrlimit()调整限制。

相关问答FAQs

Q1: 如何处理服务器中的“惊群”问题?
A1: “惊群”指多个进程/线程在accept()时被同时唤醒,但只有一个能成功连接,解决方案包括:

  • 使用accept()SO_REUSEPORT选项(Linux 3.9+),允许多个套接字绑定同一端口,由内核均衡分配连接。
  • 采用单线程+epoll模型,避免多线程竞争。

Q2: 如何确保服务器在高并发下的数据一致性?
A2: 可通过以下方式保证数据一致性:

  • 使用互斥锁(pthread_mutex_t)保护共享资源,避免竞态条件。
  • 采用原子操作(如__sync_fetch_and_add)处理简单计数器。
  • 使用读写锁(pthread_rwlock_t)优化读多写少的场景。
  • 对于复杂事务,引入数据库事务机制或分布式锁(如Redis RedLock)。
分享:
扫描分享到社交APP
上一篇
下一篇