服务器调制 调试 和测试

目录

  • 1:最大文件描述符数
  • 2:调整内核参数
    • /proc/sys/fs目录下面的文件
    • /proc/sys/net目录下的部分文件
  • 3:gdb调试
    • gdb调试多进程
      • 单独调试子进程
        • 方式1
        • 方式2
    • gdb调试多线程
  • 压力测试

三个方面 系统调试 服务器调试和压力测试来优化改进服务器
liunx 平台优秀特性 内核微调 可以通过修改文件的方式 来调制内核的参数

1:最大文件描述符数

文件描述符的资源很重要 几乎所有的系统调用都是和文件描述符打交道 ,但是文件描述符的数量是有限的 我们必须关闭不使用的文件描述符 以释放其资源。
比如作为守护进程运行的服务器程序 就应该总是关闭标准输入 输出 错误 这三个文件描述符


liunx对应用程序能打开的最大文件描述符数量受两个层次的限制 :用户限制和系统限制

  • 用户限制指的目标用户运行所有进程总共能够打开的文件描述符
  • 系统限制是所有用户总共能打开的文件描述符

ulimit -n 来查看用户级文件描述符数量
ulimit -SHn max-file-number 将文件描述符设置为max-file-number
这种修改是临时的 只在当前的session中有效 永久修改可以在/etc/security/limits.conf文件中加入下面两项
hard nofile max-file-number
soft nofile max-file-number
第一行表示系统硬限制 第二行表示软限制

要修改系统级的文件描述符的数量限制 可以使用下面命令
sysctl -w fs.file-max = max-file-number
这个命令也是临时修改 要想永远生效 则需要在/etc/sysctl.conf文件中添加下面一项:
fs.file-max = max-file-number
然后通过sysctl -p命令来使得更改生效

2:调整内核参数

几乎所有的内核模块都在/proc/sys/文件系统下面提供某个配置文件供用户修改 通常一个配置文件对应着一个内核参数 文件名就是参数的名字 文件的内容就是参数的值 可以通过sysctl -a来查看这些参数名称

/proc/sys/fs目录下面的文件

此目录下面的参数都和文件系统相关 最重要的是下面两个参数

  • /proc/sys/fs/file-max 系统级文件描述符数 直接修改后需要把同目录下的inode-max设置为新的file-max值的3~4倍 ,否则 可能导致i 节点数不够用
  • /proc/sys/fs/epoll/max_user_watches 用户可以往epoll内核事件表里面注册的事件总量 它是指的该用户打开所有epoll实例总共能监听的事件数目。而不是单个epoll实例能监听的事件数目,往epoll内核事件表中注册一个事件 32位系统上消耗90字节 64位系统上消耗160字节的内核空间。

/proc/sys/net目录下的部分文件

此模块下记录了网络模块相关的参数 其中TCP/IP协议相关的参数主要位于三个子目录中:core ipv4 ipv6

  • /proc/sys/net/core/smaxconn 指定listen监听队列里面 能够建立完整连接 从而进入ESTABLISHED状态的最大socket数量
  • /proc/sys/net/ipv4/tcp_max_syn_backlog 指定listen监听队列里,能够转移至ESTABLISHED或者 SYN_RCVD状态的最大socket数量
  • /proc/sys/net/ipv4/tcp_wmem 包含三个值 分别指定socket的tcp写缓冲区的最大值 最小值和默认值
  • /proc/sys/net/ipv4/tcp_rmem 包含三个值 分别指定socket的tcp读缓冲区的最大值 最小值和默认值
  • /proc/sys/net/ipv4/tcp_syncookies 指定是否代开TCP同步标签 启动cookie来防止监听一个socket不停的重复接受来自同一个地址连接请求 从而导致listen监听队列溢出(SYN风暴)
    以上直接修改文件都是短暂有效 要想永久生效就需要修改/etc/sysctl.conf文件中加入网络相关参数及其数值.

3:gdb调试

下面将gdb如何调试多进程和多线程
后台程序不可避免而比较困难的部分

gdb调试多进程

父进程用fork创建子进程后 默认gdb继续调试父进程 那么如何切换到子进程

单独调试子进程

方式1

子进程本质也是进程 可以先运行服务器的程序 找到子进程的pid 然后将它附加attach到gdb调试器上面
在这里插入图片描述
在这里插入图片描述
(待尝试后补充截图)

方式2

使用调试器选择follow-fork-mode
gdb调试器的选项follow-fork-mode允许在程序执行的时候 是选择 调试父进程还是子进程
在这里插入图片描述
mode 可选parent 和child


在这里插入图片描述
在这里插入图片描述

gdb调试多线程

下面列举常用的gdb命令用于多线程程序调试

  • info threads 显示可调试的所有线程 gdb为每一个线程分配一个ID 可以通过ID来操作对应的线程
    Id前面有个"*"
  • thread ID调试目标ID指定的线程
  • set scgeduler-locking[off|on|step]。调试多线程程序时。默认除了被调试的线程在执行外,其他线程也在继续执行 如果只想被调试的线程运行 。可以通过这个命令来直线 设置scheduler-locking的值:off表示不锁定任何线程,即任何线程都可以继续执行,这是默认值; on表示只有当前线程会继续执行;step表示在单步执行的时候 只有当前线程会执行。
    在这里插入图片描述
    在这里插入图片描述
    (手动调试截图待补充----)

压力测试

用I/O复用方式 多线程,多进程并发编程方式。
单纯的I/O服用方式施压是最高的 现成和进程的调度本身也是要占用CPU的时间
可以使用epoll来实现一个通用的服务器压力测试程序。

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

static const char* request = "GET http://localhost/index.html HTTP/1.1\r\nConnection: keep-alive\r\n\r\nxxxxxxxxxxxx";

int setnonblocking( int fd )
{
    int old_option = fcntl( fd, F_GETFL );
    int new_option = old_option | O_NONBLOCK;
    fcntl( fd, F_SETFL, new_option );
    return old_option;
}

void addfd( int epoll_fd, int fd )
{
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLOUT | EPOLLET | EPOLLERR;
    epoll_ctl( epoll_fd, EPOLL_CTL_ADD, fd, &event );
    setnonblocking( fd );
}

bool write_nbytes( int sockfd, const char* buffer, int len )
{
    int bytes_write = 0;
    printf( "write out %d bytes to socket %d\n", len, sockfd );
    while( 1 ) 
    {   
        bytes_write = send( sockfd, buffer, len, 0 );
        if ( bytes_write == -1 )
        {   
            return false;
        }   
        else if ( bytes_write == 0 ) 
        {   
            return false;
        }   

        len -= bytes_write;
        buffer = buffer + bytes_write;
        if ( len <= 0 ) 
        {   
            return true;
        }   
    }   
}

bool read_once( int sockfd, char* buffer, int len )
{
    int bytes_read = 0;
    memset( buffer, '\0', len );
    bytes_read = recv( sockfd, buffer, len, 0 );
    if ( bytes_read == -1 )
    {
        return false;
    }
    else if ( bytes_read == 0 )
    {
        return false;
    }
	printf( "read in %d bytes from socket %d with content: %s\n", bytes_read, sockfd, buffer );

    return true;
}

void start_conn( int epoll_fd, int num, const char* ip, int port )
{
    int ret = 0;
    struct sockaddr_in address;
    bzero( &address, sizeof( address ) );
    address.sin_family = AF_INET;
    inet_pton( AF_INET, ip, &address.sin_addr );
    address.sin_port = htons( port );

    for ( int i = 0; i < num; ++i )
    {
        sleep( 1 );
        int sockfd = socket( PF_INET, SOCK_STREAM, 0 );
        printf( "create 1 sock\n" );
        if( sockfd < 0 )
        {
            continue;
        }

        if (  connect( sockfd, ( struct sockaddr* )&address, sizeof( address ) ) == 0  )
        {
            printf( "build connection %d\n", i );
            addfd( epoll_fd, sockfd );
        }
    }
}

void close_conn( int epoll_fd, int sockfd )
{
    epoll_ctl( epoll_fd, EPOLL_CTL_DEL, sockfd, 0 );
    close( sockfd );
}

int main( int argc, char* argv[] )
{
    assert( argc == 4 );
    int epoll_fd = epoll_create( 100 );
    start_conn( epoll_fd, atoi( argv[ 3 ] ), argv[1], atoi( argv[2] ) );
    epoll_event events[ 10000 ];
    char buffer[ 2048 ];
    while ( 1 )
    {
        int fds = epoll_wait( epoll_fd, events, 10000, 2000 );
        for ( int i = 0; i < fds; i++ )
        {   
            int sockfd = events[i].data.fd;
            if ( events[i].events & EPOLLIN )
            {   
                if ( ! read_once( sockfd, buffer, 2048 ) )
                {
                    close_conn( epoll_fd, sockfd );
                }
                struct epoll_event event;
                event.events = EPOLLOUT | EPOLLET | EPOLLERR;
                event.data.fd = sockfd;
                epoll_ctl( epoll_fd, EPOLL_CTL_MOD, sockfd, &event );
            }
            else if( events[i].events & EPOLLOUT ) 
            {
                if ( ! write_nbytes( sockfd, request, strlen( request ) ) )
                {
                    close_conn( epoll_fd, sockfd );
                }
                struct epoll_event event;
                event.events = EPOLLIN | EPOLLET | EPOLLERR;
                event.data.fd = sockfd;
                epoll_ctl( epoll_fd, EPOLL_CTL_MOD, sockfd, &event );
            }
            else if( events[i].events & EPOLLERR )
            {
                close_conn( epoll_fd, sockfd );
            }
        }
    }
}

如果websrv服务器程序足够稳定 那服务器程序就不会崩溃 一直运行下去。

上一篇:Linux-1.常见指令以及权限理解


下一篇:正式发布:VitePress 1.0 现代化静态站点生成器!