lighttpd以及socket和WebSocket编程-程序员宅基地

技术标签: 编程基础  lighttpd  websocket  socket  

综述

本文涉及到下图绿色背景部分的内容:

在这里插入图片描述

左侧位于Linux下,其中包括lighttpd和socket程序;右侧是WebSocket程序。两者通过网络交互。

本文介绍lighttpd的基本使用方式,并通过编程完成一个socket服务器与浏览器端的WebSocket客户端通信。

lighttpd

首先介绍lighttpd,因为它是后端(socket程序)和前端(WebSocket程序)交互的基础。

lighttpd是一款轻量级的开源Web服务器,跟Apache、Nginx功能差不多,对应的官网http://www.lighttpd.net/。

lighttpd目前只支持Linux,所以这里在虚拟机(安装Ubuntu20.04版本)上编译和使用lighttpd,对应的Linux版本:

jw@ubuntu:~/code/www/html$ uname -a
Linux ubuntu 5.15.0-82-generic #91~20.04.1-Ubuntu SMP Fri Aug 18 16:24:39 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux

下载lighttpd最新版本,获取到lighttpd-1.4.71.tar.gz。

  1. 首先解压缩源代码:
jw@ubuntu:~/code$ tar -xzvf lighttpd-1.4.71.tar.gz
  1. 安装依赖程序:
jw@ubuntu:~/code/lighttpd-1.4.71$ sudo apt install zlib1g-dev libpcre2-dev
  1. 进入解压缩得到的目录,然后进行configure:
jw@ubuntu:~/code/lighttpd-1.4.71$ ./configure --prefix=/usr/local/lighttpd
  1. 编译:
jw@ubuntu:~/code/lighttpd-1.4.71$ make
  1. 安装:
jw@ubuntu:~/code/lighttpd-1.4.71$ sudo make install

安装的位置是/usr/local/lighttpd/

w@ubuntu:~/code/lighttpd-1.4.71$ ls -al /usr/local/lighttpd/
total 20
drwxr-xr-x  5 root root 4096 Sep  3 07:03 .
drwxr-xr-x 11 root root 4096 Sep  3 07:03 ..
drwxr-xr-x  2 root root 4096 Sep  3 07:03 lib
drwxr-xr-x  2 root root 4096 Sep  3 07:03 sbin
drwxr-xr-x  3 root root 4096 Sep  3 07:03 share
  1. 进入到lighttpd所在的目录,后续以root进行操作:
root@ubuntu:/usr/local/lighttpd/sbin# ll
total 1992
drwxr-xr-x 2 root root    4096 Sep  3 07:03 ./
drwxr-xr-x 5 root root    4096 Sep  3 07:03 ../
-rwxr-xr-x 1 root root 2004088 Sep  3 07:03 lighttpd*
-rwxr-xr-x 1 root root   23048 Sep  3 07:03 lighttpd-angel*
  1. 为了使用lighttpd,需要有配置文件,下面是一个最简单的例子(test.conf):
server.document-root = "/home/jw/code/www/html"
server.port = 80
mimetype.assign = (
  ".html" => "text/html",
  ".txt" => "text/plain",
  ".jpg" => "image/jpeg",
  ".png" => "image/png"
)
index-file.names = ( "index.html" )

简单说明它们的意义:

  • server.document-root:指定了Web服务器目录,我们需要在这里放浏览器可以访问的文件,比如html文件。
  • server.port:指定端口,默认非安全的Web服务器端口是80。
  • mimetype.assign:指定支持的文件。
  • index-file.names:指定入口文件。
  1. server.document-root指定的目录中存放html文件,下面是一个例子(index.html ):
<html>
  <body>
    Hello Wolrd!
  </body>
</html>

当通过浏览器登录服务器时,首先访问到的就是这个文件。

启动lighttpd的应用程序的命令如下:

root@ubuntu:/usr/local/lighttpd/sbin# ./lighttpd -D -f test.conf 
2023-09-03 07:17:49: (server.c.1909) server started (lighttpd/1.4.71)

启动之后该服务器会持续运行,此时可以查看到网络状态:

root@ubuntu:/usr/local/lighttpd/sbin# netstat -ntlv
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:631           0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN     
tcp6       0      0 ::1:631                 :::*                    LISTEN 

这里显示的第一行对应的就是lighttpd服务器,它监听80端口,IP显示全0表示没有限制。

通过浏览器访问lighttpd服务器,输入的IP就是Linux系统的IP,端口可以不写,默认就是80。测试结果如下图所示:

在这里插入图片描述

到这里一个简单的lighttpd服务器就已经开启了。

当然这只是一个开始,此时浏览器只能访问lighttpd中的简单html文件,要想要打通后端的socket程序和前端的WebSocket程序,还需要依赖于lighttpd的ws_tunnel插件。为了使lighttpd支持WebSocket,需要修改它的配置,以下是修改之后的test.conf :

server.modules += (
  "mod_wstunnel"
)

server.document-root = "/home/jw/code/www/html"
server.port = 80
mimetype.assign = (
  ".html" => "text/html",
  ".txt" => "text/plain",
  ".jpg" => "image/jpeg",
  ".png" => "image/png"
)
static-file.exclude-extensions = ( ".fcgi", ".php", ".rb", "~", ".inc" )
index-file.names = ( "index.html" )

$HTTP["url"] =~ "^/websocket.test" {
    
  wstunnel.server = (
    "" => ((
      "host" => "127.0.0.1",
      "port" => "888"
    ))
  )
  wstunnel.frame-type = "text"
}

这里的改动有以下的几个:

  • 通过server.modules引入lighttpd插件,在lighttpd中,通过插件的方式可以引入很多新的特性,比如这里的WebSocket(对应插件mod_wstunnel),还有CGI,代理,等等。

  • 配置wstunnel,所有的参数可以在Docs ConfigurationOptions - Lighttpd - lighty labs找到,这里的配置主要针对特定格式的WebSocket,其配置有两个:一个是转发的地址和端口,指向了localhost(127.0.0.1)和888端口,注意它们需要跟Linux端的服务器有相同的配置,否则无法转发到指定的处理程序;另一个是WebSocket的数据格式,这里指定的是文本格式。

配置修改之后重新打开lighttpd:

root@ubuntu:/usr/local/lighttpd/sbin# ./lighttpd -D -f test.conf 
2023-09-09 22:20:15: (server.c.1909) server started (lighttpd/1.4.71)

socket编程

编写Linux端的服务器程序,下面是一个示例:

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

// Should be same with the one in lihttpd.conf and index.html.
#define DEFAULT_PORT 888
// Should be same with the one in lihttpd.conf.
#define DEFAULT_IP "127.0.0.1"

int main(int argc, char **argv)
{
    
    int server_socket = -1;
    int client_socket = -1;
    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr;
    char received_buffer[1024]; // Buffer for received.
    int received_len = -1;
    int sended_len = -1;
    int res = -1;
    socklen_t addr_len = sizeof(struct sockaddr);
    int index;

    // Create a socket.
    server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket < 0)
    {
    
        printf("Create socket failed: %s\n", strerror(errno));
        return -1;
    }

    // Bind the created socket on special IP and port.
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(DEFAULT_PORT);
    server_addr.sin_addr.s_addr = inet_addr(DEFAULT_IP);
    if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
    {
    
        printf("Bind server failed: %s\n", strerror(errno));
        return -2;
    }
    printf("Socket[%d] has bond on port[%d] for IP address[%s]!\n",
           server_socket, DEFAULT_PORT, DEFAULT_IP);

    // Listen on the created socket.
    listen(server_socket, 10);

    while (1)
    {
    
        printf("Waiting and accept new client connect...\n");

        client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &addr_len);
        if (client_socket < 0)
        {
    
            printf("Accept client socket failed: %s\n", strerror(errno));
            return -3;
        }

        printf("Accept new client[%d] socket[%s:%d]\n", client_socket,
               inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

        while (1)
        {
    
            memset(received_buffer, 0, sizeof(received_buffer));

            received_len = read(client_socket, received_buffer, sizeof(received_buffer));
            if (received_len < 0)
            {
    
                printf("Read data from client [%d] failed: %s\n", client_socket, strerror(errno));
                close(client_socket);
                break;
            }
            else if (0 == received_len)
            {
    
                printf("Client [%d] disconnected!\n", client_socket);
                close(client_socket);
                break;
            }
            else
            {
    
                printf("Read %d bytes from client[%d] and the data is : %s\n",
                       received_len, client_socket, received_buffer);
                // Send back the received buffer to client.
                sended_len = write(client_socket, received_buffer, received_len);
                if (sended_len < 0)
                {
    
                    printf("wWite data back to client[%d] failed: %s \n", client_socket,
                           strerror(errno));
                    close(client_socket);
                    break;
                }
            }
        }
        sleep(1);
    }

    if (client_socket)
    {
    
        close(client_socket);
    }
    close(server_socket);

    return 1;
}

这里使用了socket编程,注意socket和前面提到的WebSocket虽然都用来网络通信,但是它们不是同一个东西,关于它们的具体差别涉及到socket和WebSocket的基础,这里不展开。

这个程序的实现很简单,就是将服务器获取到的数据直接返回给发送端。编译和使用该程序:

root@ubuntu:/home/jw/code/www/html# gcc websocket_server.c 
root@ubuntu:/home/jw/code/www/html# ./a.out 
Socket[3] has bond on port[888] for IP address[127.0.0.1]!
Waiting and accept new client connect...

再次查看网络状态:

root@ubuntu:/usr/local/lighttpd/sbin# netstat -ntlv
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:888           0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:631           0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN     
tcp6       0      0 ::1:631                 :::*                    LISTEN 

可以看到多监听了一个端口888,IP是localhost(127.0.0.1),由于ligtttpd的配置,前端连接过来的特定WebSocket(满足^/websocket.test的格式)就会被本程序处理。

WebSocket程序

本节介绍浏览器端代码的编写。

前面的两步都在Linux系统中(这里使用了虚拟机中的Ubuntu系统),而这里的操作可以在任意的系统中使用,只要存在浏览器,且跟Linux系统可以通过网络通信即可。不过编写的程序还是在Linux系统中,且在lighttpd指定的目录下,其示例代码(index.html):

<h1>Websocket Test</h1>
<pre id="messages" style="height: 400px; overflow: scroll"></pre>
<input type="text" id="messageBox" placeholder="Type your message here"
  style="display: block; width: 100%; margin-bottom: 10px; padding: 10px;" />
<button id="send" title="Send Message!" style="width: 100%; height: 30px;">Send Message</button>

<script>
  (function () {
      
    const sendBtn = document.querySelector('#send');
    const messages = document.querySelector('#messages');
    const messageBox = document.querySelector('#messageBox');

    let ws;

    function showMessage(message) {
      
      messages.textContent += `\nReceived: ${
        message}`;
      messages.scrollTop = messages.scrollHeight;
      messageBox.value = '';
    }

    function init() {
      
      if (ws) {
      
        ws.onerror = ws.onopen = ws.onclose = null;
        ws.close();
      }

      ws = new WebSocket("ws://" + location.host + "/websocket.test");
      ws.onopen = () => {
      
        console.log('Connection opened!');
      }
      ws.onmessage = ({
        data }) => showMessage(data);
      ws.onclose = function () {
      
        console.log('Connectino closed!');
        ws = null;
      }
    }

    sendBtn.onclick = function () {
      
      if (!ws) {
      
        showMessage("No WebSocket connection :(");
        return;
      }

      ws.send(messageBox.value);
      console.log("Sended: " + messageBox.value);
    }

    init();
  })();
</script>

注意这里的:

ws = new WebSocket("ws://" + location.host + "/websocket.test");

location.host对应的是Linux的IP,整个URL满足lighttpd中ws_tunnel的转发要求,所以会被第二步中的程序接收到。

通过浏览器访问location.host对应的地址,执行结果如下:

在这里插入图片描述

图中的虚拟机安装有Ubuntu20.04,开启两个进程,上面的是lighttpd作为Web服务器,下面是socket编写的服务器程序;虚拟机外面是浏览器,输入Ubuntu20.04系统的IP即可访问lighttpd,并显示指定目录下的index.html文件,在该界面下输入的内容会被lighttpd传递给服务器程序,而后者打印传递过来的内容然后返回,最后在浏览器显示出来。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/jiangwei0512/article/details/132784028

智能推荐

python封装接口用于调用_python接口自动化(三十五)-封装与调用--流程类接口关联(详解)...-程序员宅基地

文章浏览阅读84次。简介流程相关的接口,主要用 session 关联,如果写成函数(如上篇),s 参数每个函数都要带,每个函数多个参数,这时候封装成类会更方便。在这里我们还是以博客园为例,带着小伙伴们实践一下。接口封装大致流程1、在接口测试中,有些接口经常会被用到比如登录的接口,这时候我们可以每个接口都封装成一个方法,如:登录、保存草稿、发布随笔、删除随笔,这四个接口就可以写成四个方法2、接口封装好了后,后面我们写用..._pycharm接口自动化需封装接口内容

AngelScript -- C++程序最好的脚本语言-程序员宅基地

文章浏览阅读2.1k次。AngelScript是一门静态类型语言, C++风格. AngelScript拥有最好的原生支持bindings. 通常, 一个函数或者是类只需要注册到AS的虚拟机中, 就可以在AS脚本中使用了. 本文中的其他脚本语言都需要中间插件帮助才能实现函数的binding. 当然, 如果本地函数没有先注册的话, AS脚本也是编不过的. 这给那些想要预编译AS byte-code的人增加了一个额外步骤. AngelScript不支持table, 事实上这一点不会造成麻烦, 因为AS是静态类型脚本. .._angelscript

ORA-12154: TNS: 无法解析指定的连接标识符-程序员宅基地

文章浏览阅读93次。发生这种情况,大多是网络服务名配置错误的原因。另外,也有可能是笔者的这种情况--误删除系统默认的箭筒程序名(eg. LOCAL_LISTENER)和网络服务程序名(eg. LISTENER_ORCL)。对于第一种原因,只要修改正确网络服务的配置(特别是里面的数据库名称和IP一定要正确)就可以了。对于笔者这种情况,可以的解决方法如下。1. 重启电脑,..._sqlplus ora-12154: tns

基于知名微服务框架go-micro开发gRPC应用程序_go-micro开发实例-程序员宅基地

文章浏览阅读647次。go-micro是golang的一个微服务框架。这篇文章将介绍使用go-micro最新版本v4开发gRPC服务的方式。_go-micro开发实例

九度OJ题目1074-对称平方数_大学oj网1074题-程序员宅基地

文章浏览阅读110次。题目描述:打印所有不超过n(n<256)的,其平方具有对称性质的数。如11*11=121输入:无任何输入数据输出:输出具有题目要求的性质的数。如果输出数据不止一组,各组数据之间以回车隔开。样例输入:样例输出:参考代码:#include<cstdio>using namespace std;int fun(int x) {//判断一个数..._大学oj网1074题

【Gradle】四、使用Gradle创建SringBoot微服务项目_spring的gradle引用-程序员宅基地

文章浏览阅读1.6k次。使用Gradle创建SringBoot微服务项目_spring的gradle引用

随便推点

jsp在线预览pdf和word文件(在浏览器中预览pdf文件和Word文件)_jsp 在线预览word 文档-程序员宅基地

文章浏览阅读8.7k次。相关jar包和插件的下载地址:在线浏览pdf的插件在线浏览word文件的插件在浏览器中预览pdf文件和Word文件,下载完这些插件,导入到自己项目中,至于导入到哪个目录,web.xml中需要配置什么,在下载的文档中有具体的文档说明,按照文档中的步骤去配置就好了!我们先看看效果图:后面直接上代码!直接上代码!pdfAndWord.jsp页面&lt;%@ ..._jsp 在线预览word 文档

深入理解 iOS 开发中的锁_深入理解 ios 中的锁-程序员宅基地

文章浏览阅读1.7k次。摘要本文的目的不是介绍 iOS 中各种锁如何使用,一方面笔者没有大量的实战经验,另一方面这样的文章相当多,比如 iOS中保证线程安全的几种方式与性能对比、iOS 常见知识点(三):Lock。本文也不会详细介绍锁的具体实现原理,这会涉及到太多相关知识,笔者不敢误人子弟。本文要做的就是简单的分析 iOS 开发中常见的几种锁如何实现,以及优缺点是什么,为什么会有性能上的差距,最终会简单的_深入理解 ios 中的锁

MSI-X总结-程序员宅基地

文章浏览阅读1.8k次。PCIe有三种中断,分别为INTx中断,MSI中断,MSI-X中断,其中INTx是可选的,MSI/MSI-X是必须实现的。MSI, message signal interrupt, 是PCI设备通过写一个特定消息到特定地址,从而触发一个CPU中断。特定消息指的是PCIe总线中的Memory Write TLP, 特定地址一般存放在MSI capability中。1、 MSI-X Capabiliity结构MSI-X中断机制提出目的是扩展PCIe设备使用的中断向量个数(次要),同时解决MSI中断要求使_msi-x

duilib 可编辑ComboBox-程序员宅基地

文章浏览阅读1.4k次。直接贴码 头文件 #ifndef__UICOMBOBOXEx_H__#define__UICOMBOBOXEx_H__#pragmaoncenamespaceDuiLib{ /*********编辑框************/ classCComboEditWnd..._duilib 可编译的combox

第二个项目---EMOS企业在线办公小程序_慕课网 emos sql-程序员宅基地

文章浏览阅读5k次,点赞2次,收藏14次。代码量:移动端:30000+后端:5000+技术栈:移动端:uni-app + Vue + JavaScript + Less + 微信小程序后端:SpringBoot + SpringMVC + MyBatis + Shiro+ JWT + Quartz + ThreadPool + RabbitMQ + Docker源码地址:移动端源码后端源码第一章 安装软件数据库:MySQL + MongoDB + Redis后端:IDEA前端:微信小程序开发工具 + HBuilderX虚拟_慕课网 emos sql

Spring Data Jpa如何实现审计和乐观锁功能_spring data jpa 如何使用mysql数据库乐观锁-程序员宅基地

文章浏览阅读572次。在具体业务中跟踪数据库数据操作记录有时是很强烈的需求:谁何时创建了这条记录,谁何时修改了这条记录,越是大点的公司这个需求越强烈。这些需求实现较为机械和简单,所以我们不想手动去做,所以很多相关框架都提供了相应的方案,今天我们就看下Spring Data JPA是如何实现的。以前一直在使用MyBatis,现在看来JPA也不赖,关键是平时用的是SpringData JPA,Spring全家桶给你安排的明明白白。这里也不是说MyBatis不好,还是的针对自己组织和项目来取舍。又要过大年了,又要长一岁了…_spring data jpa 如何使用mysql数据库乐观锁

推荐文章

热门文章

相关标签