当前位置:首页 > > 充电吧
[导读]关于State Threads的介绍可以参考:谈谈并发编程中的协程网络架构库:State ThreadsState Threads:回调终结者一.源码编译下面是在Fedora 20(装在了虚拟机中)上

关于State Threads的介绍可以参考:
谈谈并发编程中的协程
网络架构库:State Threads
State Threads:回调终结者
一.源码编译
下面是在Fedora 20(装在了虚拟机中)上的实操记录:
1.从官网http://sourceforge.net/projects/state-threads下载源码包,最新版是1.9
2.下载完st-1.9.tar.gz,然后解压
tar zxvf st-1.9.tar.gz
cd st-1.9
make

此时会提示“Please specify one of the following targets”,如下图所示:


我选择的是linux-debug。
make linux-debug
此时会在目录st-1.9中产生一个新的目录LINUX_3.11.10-301.fc20.i686_DBG,里面有生成的中间文件*.o, 头文件st.h,libst.so,libst.a和example中的三个例子:lookupdns,proxy,server。
需要注意的是st.h是动态生成的,这种方法值得学习。
二.doc目录研究
在st-1.9源码中doc目录有几个文档,可以参考:
st.html——ST库概论,翻译在网络架构库:State Threads
timeout_heap.txt——超时heap实现
notes.html——给出了编程注意点,包括移植,信号,进程内同步,进程间同步,非网络IO,超时处理,特别谈到进程内同步非常简单,不需要同步资源;非网络IO中谈到drawback和设计时需要避免的方法
reference.html——一个API接口文档介绍,需要认真阅读和熟悉,但是需要编码实战来加深理解
对于reference.html,最重要的是文尾的Program Structure部分,它给出了在一个网络应用程序中使用ST库的基本步骤:
1.如果需要,使用下面的pre-init(预初始化)函数配置ST库,设置时间,事件通知机制
st_set_utime_function()
st_set_eventsys()
2.调用初始化函数st_init()来初始化ST库
3.如果需要,调用post-init(后初始化)函数来配置ST库,设置timecache,随机化线程栈,进程resume和stop的回调函数
st_timecache_set()
st_randomize_stacks()
st_set_switch_in_cb()
st_set_switch_out_cb()
4.生成不同process之间共享的资源,创建并绑定socket,监听socket,生成共享内存段,IPC(进程内通信)channel和同步原语。
st_netfd_open_socket()
st_netfd_serialize_accept()
5.通过fork()创建多进程, 父进程退出或是watchdog
6.在每个子进程中创建thread pool来处理user connection,线程池中的每个线程可以接受客户端连接,也可以连接到其他服务器,或者执行各种network I/O等等
st_thread_create()
st_accept()
st_connect()
st_read()
st_write()
注意:在使用ST库时,只有ST库的I/O函数可以用于network I/O,其他的I/O调用(比如说fread,fwrite)都可能阻塞调用进程。
三.example目录
       首先阅读里面的README,它简单介绍了这三个例子的基本情况和用法
server包含server.c和error.c
lookupdns包含lookupdns.c和res.c
proxy包含proxy.c
       这里分析server的实现。server接受一个客户端连接,接收客户端数据并返给客户端一个简单的HTML网页(我会做适当修改,让server将接收到的内容原样返回)。以server为基础,我们可以很方便的实现其他的服务器。

     我将源码server.c中的void handle_session(long srv_socket_index, st_netfd_t cli_nfd)函数改成如下形式,这样server会将接收到的内容原样返回,方便测试多个客户端的链接。

void handle_session(long srv_socket_index, st_netfd_t cli_nfd)
{
  char buf[512]={''};
  int n = 0;
  struct in_addr *from = st_netfd_getspecific(cli_nfd);

  if (st_read(cli_nfd, buf, sizeof(buf), SEC2USEC(REQUEST_TIMEOUT)) < 0) {
    err_sys_report(errfd, "WARN: can't read request from %s: st_read",
		   inet_ntoa(*from));
    return;
  }
 n = strlen(buf);
  if (st_write(cli_nfd, buf, n, ST_UTIME_NO_TIMEOUT) != n) {
    err_sys_report(errfd, "WARN: can't write response to %s: st_write",
		   inet_ntoa(*from));
    return;
  }

  RQST_COUNT(srv_socket_index)++;
}
作为Qt的忠实粉丝,我在st-1.9源码目录中新建一个Qt工程,pro文件如下,这样就可以愉快的调试了。
TEMPLATE = app
CONFIG += console
CONFIG -= app_bundle
CONFIG -= qt
TARGET = MyServer

INCLUDEPATH += LINUX_3.11.10-301.fc20.i686_DBG

HEADERS += LINUX_3.11.10-301.fc20.i686_DBG/st.h

SOURCES += examples/server.c 
           examples/error.c

LIBS += LINUX_3.11.10-301.fc20.i686_DBG/libst.a 

1.测试非daemon模式(即interactive模式)下的网络通信

MyServer在运行时需要解析参数,所以在Qt Creator中添加参数如下:


从源码中可以看出-i用了设置interactive模式,后边的参数不管是不是1,都会设置成功。

interactive模式比较简单,不会创建虚拟处理器(VP),也不会写日志。

从static void *handle_connections(void *arg)函数中可以看出,处理完会话后,会将当前socket关闭。该函数实际上实现了一个线程池,最小线程数是max_wait_threads,最大线程数是max_threads。

static void *handle_connections(void *arg)
{
  st_netfd_t srv_nfd, cli_nfd;
  struct sockaddr_in from;
  int fromlen;
  long i = (long) arg;

  srv_nfd = srv_socket[i].nfd;
  fromlen = sizeof(from);

  while (WAIT_THREADS(i) <= max_wait_threads) {
    cli_nfd = st_accept(srv_nfd, (struct sockaddr *)&from, &fromlen,
     ST_UTIME_NO_TIMEOUT);
    if (cli_nfd == NULL) {
      err_sys_report(errfd, "ERROR: can't accept connection: st_accept");
      continue;
    }
    /* Save peer address, so we can retrieve it later */
    st_netfd_setspecific(cli_nfd, &from.sin_addr, NULL);

    WAIT_THREADS(i)--;
    BUSY_THREADS(i)++;
    if (WAIT_THREADS(i) < min_wait_threads && TOTAL_THREADS(i) < max_threads) {
      /* Create another spare thread */
      if (st_thread_create(handle_connections, (void *)i, 0, 0) != NULL)
	WAIT_THREADS(i)++;
      else
	err_sys_report(errfd, "ERROR: process %d (pid %d): can't create"
		       " thread", my_index, my_pid);
    }

    handle_session(i, cli_nfd);

    st_netfd_close(cli_nfd);//关闭socket
    WAIT_THREADS(i)++;
    BUSY_THREADS(i)--;
  }

  WAIT_THREADS(i)--;
  return NULL;
}
客户端,可以分分钟用Qt写一个,就放在windows上吧,运行效果如下所示,我启动了两个客户端:


之所以会弹提示框,是因为检测到MyServer将socket关闭了。

MytcpClient下载地址:https://download.csdn.net/download/caoshangpa/10291042

2.测试daemon模式

       daemon模式下,server创建一个固定数量的进程(“virtual processors”虚拟处理器或VP),并在它们死亡时管理它们。每个虚拟处理器管理它自己的独立的一组state threads(ST:状态线程),其数量各不相同,取决于server的负载。每个VP只监听一个套接字。最初的进程(即daemon进程)成为watchdog(监督者),等待其子进程(也就是VP)死亡或请求终止或重新启动的信号。收到重启信号(SIGHUP)后,所有VP关闭然后重新打开日志文件和重新加载配置。所有当前活动的连接都保留活性。这里假定新配置只影响请求处理而不是服务器参数——例如VP的数量,线程限制,绑定地址等。这些参数被通常被指定为命令行参数,所以服务器为了改变它们必须停止,然后再次启动。

       每个状态线程循环处理来自单个socket的监听。一次只有一个ST在VP上运行,而VP之间不共享内存,所以任何数据都不需要互斥锁,服务器可以自由使用所有的静态变量和非重入库的函数,这大大简化了编程和调试,并提高性能(例如,对于++和---全局计数是安全的或调用inet_ntoa()不需要使用互斥)。每个VP中的当前线程负责保证那个VP的均衡,该线程可以开始一个新线程或终止自身——当备用线程数量超过了上限或者下限。

       这个模式涉及到了多进程(fork)、守护进程(daemon)、进程间通信(signal)等linux知识,还是比较复杂的。在Qt Creator中使用参数“-l ./”就能以daemon模式启动MyServer,这里“-l ./”用来设置日志的路径,因为daemon模式下会打印日志。


daemon模式框架图


       这个图是值得我们学习的,在进行服务器开发的时候,通常用守护进程来管理子进程,真正干活的是子进程。这样做的好处是当一个子进程挂了,不影响服务器的功能,因为守护进程不处理事务,因而挂掉的可能性要小很多。

启动守护进程

static void start_daemon(void)
{
  pid_t pid;

  /* Start forking */
  if ((pid = fork()) < 0)
    err_sys_quit(errfd, "ERROR: fork");
  if (pid > 0)
    exit(0);                  /* parent */

  /* First child process */
  setsid();                   /* become session leader */

  if ((pid = fork()) < 0)
    err_sys_quit(errfd, "ERROR: fork");
  if (pid > 0)                /* first child */
    exit(0);

  umask(022);

  if (chdir(logdir) < 0)
    err_sys_quit(errfd, "ERROR: can't change directory to %s: chdir", logdir);
}
创建并管理子进程
static void start_processes(void)
{
  int i, status;
  pid_t pid;
  sigset_t mask, omask;

  if (interactive_mode) {
    my_index = 0;
    my_pid = getpid();
    return;
  }
 
  for (i = 0; i < vp_count; i++) {
    if ((pid = fork()) < 0) {
      err_sys_report(errfd, "ERROR: can't create process: fork");
      if (i == 0)
	exit(1);
      err_report(errfd, "WARN: started only %d processes out of %d", i,
		 vp_count);
      vp_count = i;
      break;
    }
    if (pid == 0) {
      my_index = i;
      my_pid = getpid();
      /* Child returns to continue in main() */
      return;
    }
    vp_pids[i] = pid;
  }

  /*
   * Parent process becomes a "watchdog" and never returns to main().
   */

  /* Install signal handlers */
  Signal(SIGTERM, wdog_sighandler);  /* terminate */
  Signal(SIGHUP,  wdog_sighandler);  /* restart   */
  Signal(SIGUSR1, wdog_sighandler);  /* dump info */

  /* Now go to sleep waiting for a child termination or a signal */
  for ( ; ; ) {
    if ((pid = wait(&status)) < 0) {
      if (errno == EINTR)
	continue;
      err_sys_quit(errfd, "ERROR: watchdog: wait");
    }
    /* Find index of the exited child */
    for (i = 0; i < vp_count; i++) {
      if (vp_pids[i] == pid)
	break;
    }

    /* Block signals while printing and forking */
    sigemptyset(&mask);
    sigaddset(&mask, SIGTERM);
    sigaddset(&mask, SIGHUP);
    sigaddset(&mask, SIGUSR1);
    sigprocmask(SIG_BLOCK, &mask, &omask);

    if (WIFEXITED(status))
      err_report(errfd, "WARN: watchdog: process %d (pid %d) exited"
		 " with status %d", i, pid, WEXITSTATUS(status));
    else if (WIFSIGNALED(status))
      err_report(errfd, "WARN: watchdog: process %d (pid %d) terminated"
		 " by signal %d", i, pid, WTERMSIG(status));
    else if (WIFSTOPPED(status))
      err_report(errfd, "WARN: watchdog: process %d (pid %d) stopped"
		 " by signal %d", i, pid, WSTOPSIG(status));
    else
      err_report(errfd, "WARN: watchdog: process %d (pid %d) terminated:"
		 " unknown termination reason", i, pid);

    /* Fork another VP */
    if ((pid = fork()) < 0) {
      err_sys_report(errfd, "ERROR: watchdog: can't create process: fork");
    } else if (pid == 0) {
      my_index = i;
      my_pid = getpid();
      /* Child returns to continue in main() */
       err_report(errfd, "Test Information");
      return;
    }
    vp_pids[i] = pid;

    /* Restore the signal mask */
    sigprocmask(SIG_SETMASK, &omask, NULL);
  }
}
       这个函数中先创建了vp_count个子进程,然后主进程进入一个for循环中,在循环中等待进程的信号(wait(&status)),wait函数值阻塞的。如果子进程接收到SIGTERM信号时,在static void child_sighandler(int signo)中会退出该子进程。同时程序走到wait函数之后,创建出一个新的子进程,这样子进程的个数始终维持在vp_count个。

       如果是主进程接收到SIGTERM信号,在static void wdog_sighandler(int signo)中会先kill所有的子进程,然后再退出主进程。此时,所有进程退出。我的电脑是四核,vp_count=4,所以一共有五个进程,如下图所示:

       前四列依次是父进程ID(PPID),进程ID(PID),进程组ID(PGID)和会话ID(SID)。第一个进程的父进程ID是1,这个进程就是守护进程,下面四个进程都是子进程,它们的父进程都是第一个进程。

关于SIGTERM信号,可以通过下列指令测试:

kill -SIGTERM 进程ID号

   State Threads在开源流媒体服务器Simple-RTMP-Server(SRS)中已经有了教科书般的应用,详见:https://github.com/winlinvip/srs


参考链接:https://blog.csdn.net/tao_627/article/details/45788013

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭