---WebCam网络摄像头10 socket




如果使用如下指令启动的mjpg_streamer

./mjpg_streamer -o "output_http.so -w ./www" -i "input_s3c2410.so -d /dev/camera"  
则在mjpg_streamer.c中的两条指令
  for (i=0; i<global.outcnt; i++) {//只指定了一个-o,global.outcnt = 1
        global.out[i].init(&global.out[i].param);
        global.out[i].run(global.out[i].param.id);
  }

分别是执行output_http.c中的

output_init(output_parameter *param)// param.parameter_string="-w ./www" 
output_run(int id) //id=0

搜索"见下面"取得线索。
***********************************************************init***************************************************************************
在output_http.c里,output_init源码如下

int output_init(output_parameter *param) {
  char *argv[MAX_ARGUMENTS]={NULL};
  int  argc=1, i;
  int  port;
  char *credentials, *www_folder;
  char nocommands;

  DBG("output #%02d\n", param->id);

  port = htons(8080);
  credentials = NULL;
  www_folder = NULL;
  nocommands = 0;

  /* convert the single parameter-string to an array of strings */
  argv[0] = OUTPUT_PLUGIN_NAME;
  if ( param->parameter_string != NULL && strlen(param->parameter_string) != 0 ) {
    char *arg=NULL, *saveptr=NULL, *token=NULL;

    arg=(char *)strdup(param->parameter_string);

    if ( strchr(arg, ' ') != NULL ) {
      token=strtok_r(arg, " ", &saveptr);
      if ( token != NULL ) {
        argv[argc] = strdup(token);
        argc++;
        while ( (token=strtok_r(NULL, " ", &saveptr)) != NULL ) {
          argv[argc] = strdup(token);
          argc++;
          if (argc >= MAX_ARGUMENTS) {
            OPRINT("ERROR: too many arguments to output plugin\n");
            return 1;
          }
        }
      }
    }
  }

  /* show all parameters for DBG purposes */
  for (i=0; i<argc; i++) {
    DBG("argv[%d]=%s\n", i, argv[i]);
  }

  reset_getopt();
  while(1) {
    int option_index = 0, c=0;
    static struct option long_options[] = \
    {
      {"h", no_argument, 0, 0},
      {"help", no_argument, 0, 0},
      {"p", required_argument, 0, 0},
      {"port", required_argument, 0, 0},
      {"c", required_argument, 0, 0},
      {"credentials", required_argument, 0, 0},
      {"w", required_argument, 0, 0},
      {"www", required_argument, 0, 0},
      {"n", no_argument, 0, 0},
      {"nocommands", no_argument, 0, 0},
      {0, 0, 0, 0}
    };

    c = getopt_long_only(argc, argv, "", long_options, &option_index);

    /* no more options to parse */
    if (c == -1) break;

    /* unrecognized option */
    if (c == '?'){
      help();
      return 1;
    }

    switch (option_index) {
      /* h, help */
      case 0:
      case 1:
        DBG("case 0,1\n");
        help();
        return 1;
        break;

      /* p, port */
      case 2:
      case 3:
        DBG("case 2,3\n");
        port = htons(atoi(optarg));
        break;

      /* c, credentials */
      case 4:
      case 5:
        DBG("case 4,5\n");
        credentials = strdup(optarg);
        break;

      /* w, www */
      case 6:
      case 7:
        DBG("case 6,7\n");
        www_folder = malloc(strlen(optarg)+2);
        strcpy(www_folder, optarg);
        if ( optarg[strlen(optarg)-1] != '/' )
          strcat(www_folder, "/");
        break;

      /* n, nocommands */
      case 8:
      case 9:
        DBG("case 8,9\n");
        nocommands = 1;
        break;
    }
  }
从此也可看出-o可以接受什么参数,一般要指定-p 8080(默认),-w /www


***********************************************************run***************************************************************************
在output_http.c里,output_run源码如下
int output_run(int id) {
  DBG("launching server thread #%02d\n", id);

  /* create thread and pass context to thread function */
  pthread_create(&(servers[id].threadID), NULL, server_thread, &(servers[id]));//见下面
  pthread_detach(servers[id].threadID);

  return 0;
}
由于在mjpg_streamer.c中是根据-o的数量使用for循环调用的output_run(),所以有几个-o就会创建几个服务线程,每个服务线程对应一个线程上下文servers[id],id是线程的序号(即-o的序号)
pthread_create的
参数1.servers[id].threadID.第id个线程对应的线程号
参数4.servers[id] 第id个线程的上下文参数,成员如下

/* context of each server thread */
typedef struct {
  int sd[MAX_SD_LEN];
  int sd_len;
  int id;
  globals *pglobal;
  pthread_t threadID;

  config conf;
} context;//httpd.h
#define MAX_OUTPUT_PLUGINS 10//mjpg-streamer.h 可知最多支持10个 -o
context servers[MAX_OUTPUT_PLUGINS];//output_http.c

然后进入线程函数
/******************************************************************************
Description.: Open a TCP socket and wait for clients to connect. If clients
              connect, start a new thread for each accepted connection.
Input Value.: arg is a pointer to the globals struct
Return Value: always NULL, will only return on exit
******************************************************************************/
void *server_thread( void *arg ) {
  int on;
  pthread_t client;
  struct addrinfo *aip, *aip2;
  struct addrinfo hints;
  struct sockaddr_storage client_addr;
  socklen_t addr_len = sizeof(struct sockaddr_storage);
  fd_set selectfds;
  int max_fds = 0;
  char name[NI_MAXHOST];
  int err;
  int i;

  context *pcontext = arg;
  pglobal = pcontext->pglobal;

  /* set cleanup handler to cleanup ressources */
  pthread_cleanup_push(server_cleanup, pcontext);

  bzero(&hints, sizeof(hints));
  hints.ai_family = PF_UNSPEC;
  hints.ai_flags = AI_PASSIVE;
  hints.ai_socktype = SOCK_STREAM;//tcp
//为调用getaddrinfo()准备hints
  snprintf(name, sizeof(name), "%d", ntohs(pcontext->conf.port));
//端口号 8080
  if((err = getaddrinfo(NULL, name, &hints, &aip)) != 0) {
//取得指定类型的socket address(addrinfo),以便后面的函数使用
//参数1 主机名或ip 
//参数2 服务名或端口号
//参数3 指定需要返回的地址类型
//参数4 返回的第一个addrinfo结构体(通过遍历addrinfo结构体的链表得到所有符合条件的addrinfo)
//hints.ai_flags = AI_PASSIVE;和主机名设为NULL,则此函数会返回本机所有ip的addrinfo,包括回环地址127.0.0.1和本地地址如192.168.1.230
//refer to man getaddrinfo , http://blog.csdn.net/lgtnt/article/details/3745194
    perror(gai_strerror(err));
    exit(EXIT_FAILURE);
  }

  for(i = 0; i < MAX_SD_LEN; i++)
    pcontext->sd[i] = -1;
//httpd.c #define MAX_SD_LEN 50
//初始化所有的套接字描述符为-1
  /* open sockets for server (1 socket / address family) */
  i = 0;
  for(aip2 = aip; aip2 != NULL; aip2 = aip2->ai_next)
  {
//遍历所有的套接字地址,为每一个地址创建一个套接字(服务器套接字)。最多可以建立MAX_SD_LEN个(50)--即最多支持本机的50个ip。但
//通过上面的getaddrinfo()返回的是两个socket地址(ip),一个是回环ip 127.0.0.1一个是本地ip比如192.168.1.230
//所以执行2次
    if((pcontext->sd[i] = socket(aip2->ai_family, aip2->ai_socktype, 0)) < 0) {
//创建socket,返回套接字描述符 pcontext->sd[i] 
//每个-o 会创建2个(也是所有了)socket,即会监视本机的所有ip的8080端口
      continue;
    }

    /* ignore "socket already in use" errors */
    on = 1;
    if(setsockopt(pcontext->sd[i], SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
      perror("setsockopt(SO_REUSEADDR) failed");
    }

    /* IPv6 socket should listen to IPv6 only, otherwise we will get "socket already in use" */
    on = 1;
    if(aip2->ai_family == AF_INET6 && setsockopt(pcontext->sd[i], IPPROTO_IPV6, IPV6_V6ONLY,
                  (const void *)&on , sizeof(on)) < 0) {
      perror("setsockopt(IPV6_V6ONLY) failed");
    }

    /* perhaps we will use this keep-alive feature oneday */
    /* setsockopt(sd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)); */

    if(bind(pcontext->sd[i], aip2->ai_addr, aip2->ai_addrlen) < 0) {
//为上面创建的socket绑定地址
      perror("bind");
      pcontext->sd[i] = -1;
      continue;
    }

    if(listen(pcontext->sd[i], 10) < 0) {
//创建一个可以容纳2个请求者的监听队列
      perror("listen");
      pcontext->sd[i] = -1;
    } else {
      i++;
      if(i >= MAX_SD_LEN) {
        OPRINT("%s(): maximum number of server sockets exceeded", __FUNCTION__);
        i--;
        break;
      }
    }
  }

  pcontext->sd_len = i;

  if(pcontext->sd_len < 1) {
    OPRINT("%s(): bind(%d) failed", __FUNCTION__, htons(pcontext->conf.port));
    closelog();
    exit(EXIT_FAILURE);
  }

  /* create a child for every client that connects */
  while ( !pglobal->stop ) {
    //int *pfd = (int *)malloc(sizeof(int));
    cfd *pcfd = malloc(sizeof(cfd));

    if (pcfd == NULL) {
      fprintf(stderr, "failed to allocate (a very small amount of) memory\n");
      exit(EXIT_FAILURE);
    }

    DBG("waiting for clients to connect\n");

    do {
      FD_ZERO(&selectfds);

      for(i = 0; i < MAX_SD_LEN; i++) {
        if(pcontext->sd[i] != -1) {
          FD_SET(pcontext->sd[i], &selectfds);
//将上面创建的socket加入selectfds描述符集合
          if(pcontext->sd[i] > max_fds)
            max_fds = pcontext->sd[i];
        }
      }

      err = select(max_fds + 1, &selectfds, NULL, NULL, NULL);
//使用select监听文件文件描述符集合,没有动静就阻塞在这里。有动静继续执行。
      if (err < 0 && errno != EINTR) {
        perror("select");
        exit(EXIT_FAILURE);
      }
    } while(err <= 0);

    for(i = 0; i < max_fds + 1; i++) {
//遍历所有的服务器套接字描述符,以便确认是哪个套接字上有链接请求
      if(pcontext->sd[i] != -1 && FD_ISSET(pcontext->sd[i], &selectfds)) {
        pcfd->fd = accept(pcontext->sd[i], (struct sockaddr *)&client_addr, &addr_len);
//accept函数会自动创建一个新的套接字于这个客户端套接字通信,并且返回新套接字的文件描述符。原有的套接字继续执行监听。
        pcfd->pc = pcontext;
/*httpd.c
typedef struct {
  context *pc;
  int fd;
} cfd;
*/
        /* start new thread that will handle this TCP connected client */
        DBG("create thread to handle client that just established a connection\n");

        if(getnameinfo((struct sockaddr *)&client_addr, addr_len, name, sizeof(name), NULL, 0, NI_NUMERICHOST) == 0) {
          syslog(LOG_INFO, "serving client: %s\n", name);
        }

        if( pthread_create(&client, NULL, &client_thread, pcfd) != 0 ) {//见下面
//为客户端创建服务线程
//参数4 pcfd
//pcfd->fd 套接字的文件描述符
//pcfd->pc 套接字上下文
          DBG("could not launch another client thread\n");
          close(pcfd->fd);
          free(pcfd);
          continue;
        }
        pthread_detach(client);
      }
    }
  }

  DBG("leaving server thread, calling cleanup function now\n");
  pthread_cleanup_pop(1);

  return NULL;
}
可以看出线程函数(对应一个-o的)server_thread里面是 为每个addrinfo(最多50个)创建一个套接字 ,然后去监听。每当一个套接字上有客户端链接请求,就会再创建一个线 程去传输数据。这里的套接字地址与beginning linux programming上讲的不太一样,在ipv6新加的吧。。。
./mjpg_streamer  -i "input_s3c2410.so -d /dev/camera"  -o "output_http.so -p 8080"  -o "output_http.so -p 8081"
这样就会创建2个线程,
一个线程里面会创建2个套接字,一个在侦听127.0.0.1:8080,一个在侦听192.168.1.230:8080
另一个线程也会创建2个套接字,一个在侦听127.0.0.1:8081,一个在侦听192.168.1.230:8081

然后在客户端的浏览器中同时访问如下两个网址
http://192.168.1.230:8080/?action=stream
http://192.168.1.230:8081/?action=stream
则服务器上监听192.168.1.230:8080和监听192.168.1.230:8081的socket就会accept()---
函数会自动创建一个新的套接字(和一个线程)与这个客户端套接字通信,并且返回新套接字的文件描述符。原有的套接字继续执行监听。所以之后再开多个浏览器去访问比如http://192.168.1.230:8080/?action=stream也可以访问得到数据。
以上是个人理解仅供参考

线程函数如下

/******************************************************************************
Description.: Serve a connected TCP-client. This thread function is called
              for each connect of a HTTP client like a webbrowser. It determines
              if it is a valid HTTP request and dispatches between the different
              response options.
Input Value.: arg is the filedescriptor and server-context of the connected TCP
              socket. It must have been allocated so it is freeable by this
              thread function.
Return Value: always NULL
******************************************************************************/
/* thread for clients that connected to this server */
void *client_thread( void *arg ) {
  int cnt;
  char buffer[BUFFER_SIZE]={0}, *pb=buffer;
  iobuffer iobuf;
  request req;
  cfd lcfd; /* local-connected-file-descriptor */

  /* we really need the fildescriptor and it must be freeable by us */
  if (arg != NULL) {
    memcpy(&lcfd, arg, sizeof(cfd));
    free(arg);
  }
  else
    return NULL;

  /* initializes the structures */
  init_iobuffer(&iobuf);
  init_request(&req);

  /* What does the client want to receive? Read the request. */
  memset(buffer, 0, sizeof(buffer));
  if ( (cnt = _readline(lcfd.fd, &iobuf, buffer, sizeof(buffer)-1, 5)) == -1 ) {
//从描述符(客户端)读取一行数据到buffer
    close(lcfd.fd);
    return NULL;
  }

  /* determine what to deliver */
  if ( strstr(buffer, "GET /?action=snapshot") != NULL ) {
    req.type = A_SNAPSHOT;
  }
  else if ( strstr(buffer, "GET /?action=stream") != NULL ) {
    req.type = A_STREAM;
//比如浏览器中输入 http://192.168.1.230:8080/?action=stream
  }
  else if ( strstr(buffer, "GET /?action=command") != NULL ) {
    int len;
    req.type = A_COMMAND;

    /* advance by the length of known string */
    if ( (pb = strstr(buffer, "GET /?action=command")) == NULL ) {
      DBG("HTTP request seems to be malformed\n");
      send_error(lcfd.fd, 400, "Malformed HTTP request");
      close(lcfd.fd);
      return NULL;
    }
    pb += strlen("GET /?action=command");

    /* only accept certain characters */
    len = MIN(MAX(strspn(pb, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-=&1234567890%./"), 0), 100);
    req.parameter = malloc(len+1);
    if ( req.parameter == NULL ) {
      exit(EXIT_FAILURE);
    }
    memset(req.parameter, 0, len+1);
    strncpy(req.parameter, pb, len);

    if ( unescape(req.parameter) == -1 ) {
      free(req.parameter);
      send_error(lcfd.fd, 500, "could not properly unescape command parameter string");
      LOG("could not properly unescape command parameter string\n");
      close(lcfd.fd);
      return NULL;
    }

    DBG("command parameter (len: %d): \"%s\"\n", len, req.parameter);
  }
  else {
    int len;

    DBG("try to serve a file\n");
    req.type = A_FILE;

    if ( (pb = strstr(buffer, "GET /")) == NULL ) {
      DBG("HTTP request seems to be malformed\n");
      send_error(lcfd.fd, 400, "Malformed HTTP request");
      close(lcfd.fd);
      return NULL;
    }

    pb += strlen("GET /");
    len = MIN(MAX(strspn(pb, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ._-1234567890"), 0), 100);
    req.parameter = malloc(len+1);
    if ( req.parameter == NULL ) {
      exit(EXIT_FAILURE);
    }
    memset(req.parameter, 0, len+1);
    strncpy(req.parameter, pb, len);

    DBG("parameter (len: %d): \"%s\"\n", len, req.parameter);
  }

  /*
   * parse the rest of the HTTP-request
   * the end of the request-header is marked by a single, empty line with "\r\n"
   */
  do {
    memset(buffer, 0, sizeof(buffer));

    if ( (cnt = _readline(lcfd.fd, &iobuf, buffer, sizeof(buffer)-1, 5)) == -1 ) {
      free_request(&req);
      close(lcfd.fd);
      return NULL;
    }

    if ( strstr(buffer, "User-Agent: ") != NULL ) {
      req.client = strdup(buffer+strlen("User-Agent: "));
    }
    else if ( strstr(buffer, "Authorization: Basic ") != NULL ) {
      req.credentials = strdup(buffer+strlen("Authorization: Basic "));
      decodeBase64(req.credentials);
      DBG("username:password: %s\n", req.credentials);
    }

  } while( cnt > 2 && !(buffer[0] == '\r' && buffer[1] == '\n') );

  /* check for username and password if parameter -c was given */
  if ( lcfd.pc->conf.credentials != NULL ) {
    if ( req.credentials == NULL || strcmp(lcfd.pc->conf.credentials, req.credentials) != 0 ) {
      DBG("access denied\n");
      send_error(lcfd.fd, 401, "username and password do not match to configuration");
      close(lcfd.fd);
      if ( req.parameter != NULL ) free(req.parameter);
      if ( req.client != NULL ) free(req.client);
      if ( req.credentials != NULL ) free(req.credentials);
      return NULL;
    }
    DBG("access granted\n");
  }

  /* now it's time to answer */
  switch ( req.type ) {
    case A_SNAPSHOT:
      DBG("Request for snapshot\n");
      send_snapshot(lcfd.fd);
      break;
    case A_STREAM:
      DBG("Request for stream\n");
      send_stream(lcfd.fd);//见下面
      break;
    case A_COMMAND:
      if ( lcfd.pc->conf.nocommands ) {
        send_error(lcfd.fd, 501, "this server is configured to not accept commands");
        break;
      }
      command(lcfd.pc->id, lcfd.fd, req.parameter);
      break;
    case A_FILE:
      if ( lcfd.pc->conf.www_folder == NULL )
        send_error(lcfd.fd, 501, "no www-folder configured");
      else
        send_file(lcfd.pc->id, lcfd.fd, req.parameter);
      break;
    default:
      DBG("unknown request\n");
  }

  close(lcfd.fd);
  free_request(&req);

  DBG("leaving HTTP client thread\n");
  return NULL;
}

下面是服务器响应客户端的?action=stream请求所发送的全部数据-----一个web服务器发送数据的实现
比如 http://192.168.1.230:8081/?action=stream
从这个函数可以看出,在运行程序时即使不使能www 路径也可以观看图像。因为它发送了完整的http标记。

/******************************************************************************
Description.: Send a complete HTTP response and a stream of JPG-frames.
Input Value.: fildescriptor fd to send the answer to
Return Value: -
******************************************************************************/
void send_stream(int fd) {
  unsigned char *frame=NULL, *tmp=NULL;
  int frame_size=0, max_frame_size=0;
  char buffer[BUFFER_SIZE] = {0};

  DBG("preparing header\n");

  sprintf(buffer, "HTTP/1.0 200 OK\r\n" \
                  STD_HEADER \
                  "Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY "\r\n" \
                  "\r\n" \
                  "--" BOUNDARY "\r\n");

  if ( write(fd, buffer, strlen(buffer)) < 0 ) {
//发送http头
    free(frame);
    return;
  }

  DBG("Headers send, sending stream now\n");

  while ( !pglobal->stop ) {
//只要没停止就一直发送图像,所以在浏览器中看到的是 视频
    /* wait for fresh frames */
    pthread_cond_wait(&pglobal->db_update, &pglobal->db);

    /* read buffer */
    frame_size = pglobal->size;

    /* check if framebuffer is large enough, increase it if necessary */
    if ( frame_size > max_frame_size ) {
      DBG("increasing buffer size to %d\n", frame_size);

      max_frame_size = frame_size+TEN_K;
      if ( (tmp = realloc(frame, max_frame_size)) == NULL ) {
        free(frame);
        pthread_mutex_unlock( &pglobal->db );
        send_error(fd, 500, "not enough memory");
        return;
      }

      frame = tmp;
    }

    memcpy(frame, pglobal->buf, frame_size);
    DBG("got frame (size: %d kB)\n", frame_size/1024);

    pthread_mutex_unlock( &pglobal->db );

    /*
     * print the individual mimetype and the length
     * sending the content-length fixes random stream disruption observed
     * with firefox
     */
    sprintf(buffer, "Content-Type: image/jpeg\r\n" \
                    "Content-Length: %d\r\n" \
                    "\r\n", frame_size);
    DBG("sending intemdiate header\n");
    if ( write(fd, buffer, strlen(buffer)) < 0 ) break;
//发送内容类型,内容大小
    DBG("sending frame\n");
    if( write(fd, frame, frame_size) < 0 ) break;
//发送内容--图像数据
    DBG("sending boundary\n");
    sprintf(buffer, "\r\n--" BOUNDARY "\r\n");
    if ( write(fd, buffer, strlen(buffer)) < 0 ) break;
//发送http尾
  }

  free(frame);
}

而如果客户端想要访问www下的文件,则在服务器端启动mjpg-streamer时需要指定www路径,发送文件的函数是send_file()
一个简单的web服务器的实现
/******************************************************************************
Description.: Send HTTP header and copy the content of a file. To keep things
              simple, just a single folder gets searched for the file. Just
              files with known extension and supported mimetype get served.
              If no parameter was given, the file "index.html" will be copied.
Input Value.: * fd.......: filedescriptor to send data to
              * parameter: string that consists of the filename
              * id.......: specifies which server-context is the right one
Return Value: -
******************************************************************************/
void send_file(int id, int fd, char *parameter) {
  char buffer[BUFFER_SIZE] = {0};
  char *extension, *mimetype=NULL;
  int i, lfd;
  config conf = servers[id].conf;

  /* in case no parameter was given */
  if ( parameter == NULL || strlen(parameter) == 0 )
    parameter = "index.html";

  /* find file-extension */
  if ( (extension = strstr(parameter, ".")) == NULL ) {
    send_error(fd, 400, "No file extension found");
    return;
  }

  /* determine mime-type */
  for ( i=0; i < LENGTH_OF(mimetypes); i++ ) {
    if ( strcmp(mimetypes[i].dot_extension, extension) == 0 ) {
      mimetype = (char *)mimetypes[i].mimetype;
      break;
    }
  }

  /* in case of unknown mimetype or extension leave */
  if ( mimetype == NULL ) {
    send_error(fd, 404, "MIME-TYPE not known");
    return;
  }

  /* now filename, mimetype and extension are known */
  DBG("trying to serve file \"%s\", extension: \"%s\" mime: \"%s\"\n", parameter, extension, mimetype);

  /* build the absolute path to the file */
  strncat(buffer, conf.www_folder, sizeof(buffer)-1);
  strncat(buffer, parameter, sizeof(buffer)-strlen(buffer)-1);

  /* try to open that file */
  if ( (lfd = open(buffer, O_RDONLY)) < 0 ) {
    DBG("file %s not accessible\n", buffer);
    send_error(fd, 404, "Could not open file");
    return;
  }
  DBG("opened file: %s\n", buffer);

  /* prepare HTTP header */
  sprintf(buffer, "HTTP/1.0 200 OK\r\n" \
                  "Content-type: %s\r\n" \
                  STD_HEADER \
                  "\r\n", mimetype);
//发送的这些数据在浏览器中看不到的,是给浏览器一个提供的一个版本识别信息
//浏览器中可以观察到的后面真正的数据(比如index.html的内容)
//上面send_stream()发送图像流也是一样,浏览器中只呈现出图像
  i = strlen(buffer);

  /* first transmit HTTP-header, afterwards transmit content of file */
  do {
    if ( write(fd, buffer, i) < 0 ) {
      close(lfd);
      return;
    }
  } while ( (i=read(lfd, buffer, sizeof(buffer))) > 0 );

  /* close file, job done */
  close(lfd);
}
可以看到此函数会按照浏览器地址指定的文件在www目录寻找这个文件,然后发送出去。所以可以按照项目要求自己加一些网页进去就可以扩增功能啦
比如在板子上

[root@FriendlyARM www]# touch a.html
[root@FriendlyARM www]# echo hhheh > a.html
然后客户端访问
http://192.168.1.230:8080/a.html

同样可以想到,如果在www目录下放一个cgi文件,是否也可以访问呢?
不支持。看上面line 36 --line39,有识别的。如果注释掉那个return,则浏览到的是乱码。
boa是支持的,可以参考一下boa的源码,修改一下send_file()估计就可以了。
http详细部分见下文。

转载于:https://www.cnblogs.com/-song/archive/2011/11/27/3331922.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值