LuaSocket 学习笔记

——— LUA SocketLib 和 协程php

前言:
这是一篇译文(The LUA SocketLib and the Coroutines),有删改,原文见下方连接。html

简介

目标读者:会使用 LUA SocketLib;会用协程。编程

LUA SocketLib 不只提供了 TCP-UDP/IP 的网络链接。还提供了诸如 TCP、UDP 的客户端和服务端,以及 FTP、HTTP 协议等高级对象。服务器

本教程专一于 LUA SocketLib 提供的 socket 和 TCP/IP 服务器。一旦掌握了基本的操做,这个库里面的其余组件用起来就是小菜一碟。网络

协程是 LUA 5.1 的特性会在 TCP/IP 服务器的管理中使用到。多线程

TCP/IP 和 Socket 知识回顾

首先, TCP/IP 协议的圣经在此:RFC793
TCP/IP 是许多通讯协议的一个大集合,它基于两个原始的协议:TCP 和 IP。 TCP 在 OSI 模型的第四层(传输层),IP在第三层(网络层)。TCP 协议用于从应用向网络的数据传输,可以以可靠的方式处理从源到目标地址的字节流(stream)。一个 TCP 链接由一个五件套表示{协议, IP本地地址,本地端口,IP远程地址,远程端口},它在给定的整个网络中具备惟一性。
一个链接由 2 个 半套接字(half-sockets):源(客户端)一半套接字 和 终点(服务端)一半套接字。socket

在监听一个端口的时候, TCP 服务器建立半个套接字(服务器套接字)。接收到链接时,服务器对这半个套接字进行复制并与终点的半个套接字(客户端套接字)链接,创建通讯套接字,实现信息数据流的传输。所以,服务器老是拥有一个可用的半个套接字。tcp

创建 TCP/IP 服务器

一般创建一个TCP服务器的过程能够直接用 SocketLib 的函数实现。具体流程以下:函数

  1. 创建服务端套接字
  2. 把服务端套接字附加到端口
  3. 监听这个端口

SocketLib 是 Hyperion 的一部分,它能够像 LUA 提供的其余标准库同样使用。下面给出创建 TCP 服务器的代码:测试

function createTCPServer( host, port, backlog) // host 是个啥?
    local tcp_server,err = socket.tcp();
    if tcp_server == nil then
        return nil,err;
    end

    tcp_server:setoption("reuseaddr",true);
    local res, err = tcp_server:bind(host,port);
    if res==nil then
        return nil,err;
    end

    res,err = tcp_server:listen(backlog);
    if res == nil then
        return nil,err;
    end
    
    return tcp_server;
end

socket.tcp() 建立一个 TCP 服务端对象。函数和对象的详解见本页末尾。
在建立对象以后是一些耳熟能详的步骤:setoption(), bind() 及 listen();

大多数 SocketLib 函数成功返回一个参数,失败返回两个参数。在失败的状况下,参数一返回 nil,参数二返回错误信息。

createTCPServer() 必须在 host 程序初始化的时候调用。在 Hyperion 中,咱们直接把函数及其调用都放在一个 LUA 的初始化脚本中(run_mode="INIT")。

最后一件事:注意 createTCPServer() 函数中的 host 参数。若是你用 localhost 来设置它,那么这个 TCP 服务器只能接受本地主机(localhost)传进来的链接(例如客户端和服务器端在同一个电脑里运行)。因此,若是你但愿可以连放在另外一台计算机运行的客户端程序,你须要把host设置为:host = "*";。这是一个小技巧,可是相信我,若是你不知道的话可能会浪费不少时间!

建立一个 TCP 服务器是最简单的部分,让咱们来处理有份量的那部分:传入链接的接收与管理。

接收(incoming)与发送(connecting)链接管理 -协程

处理传入链接的指导原则是把 TCP 服务器设置成一个死循环,在循环的开始处用 server:accept() 函数来等待链接。可是咱们立刻发现了问题:若是咱们进入这个死循环,那么服务器程序就会冻结在那里,这是咱们没法接受的。

对于这个尖锐的问题,其解决方案是把这个死循环转换为一个与程序主线程分离的执行路径。有两个方法:线程或者协程。

线程你们都比较了解,再也不赘述。

协程与线程同样是一个分离的执行队列,可是不禁 OS 管理,而是由主程序来控制协程的切换。主程序每隔一小段时间会遍历一遍目前存在的协程。

协程也被称做协做的多线程,意思是协程必须相互合做来使得多线程正确工做。若是一个协程由于某种缘由中止合做,整个程序都会受到影响。若是一个应用程序卡死,那么极可能不是因为操做系统的问题,而是因为应用程序中不合理的协做关系。

为了可以正确进行协做,协程中无限循环的每一次循环都必须尽快结束。协程中最关键的函数是 accept() 。这个函数在默认状况下直到新的链接到来才会执行下一步。这种行为是高度非协做性的。所以咱们须要经过 server:settimeout() 来设置一个等待的最大时间。在后面提供的 DEMO_LUA_TCP_Server_Coroutine.xml demo 中,这个时间被设置为 10ms。

故事到此尚未结束。协程须要告诉主程序何时可以出让空间给下一个协程。这个功能由 coroutine.yield() 函数提供。yield 是协程无限循环中的最后一个指令。经过这个函数,主程序会获知当前协程已经执行完毕,轮到执行下一个协程。另外一个协程的运行经过 coroutine.resume() 函数来实现。

如今,让咱们以更具体的方式来看一看。先来看看等待传入链接的核心实现部分。

function runTCPServer()
    local stop_server = 0;
    local num_cnx = 0;

    while( stop_server == 0) do
        local err = "";
        local tcp_client = 0;

        g_tcp_server:settimeout(0.01);
        tcp_client,err = g_tcp_server:accept();

        if tcp_client ~= nil then

            g_client_msg, err = tcp_client:receive('*|'); 

            if( g_client_msg ~= nil) then
                if (g_client_msg == "STOP") then
                    stop_server = 1;
                else
                    tcp_client:send("Message :"..g_client_msg);
                end
            end

            if tcp_client ~= nil then
                tcp_client:close();
            end
        end

        coroutine.yield();

    end
end

这个协程的建立是在初始化部分经过 coroutine.create() 函数实现。它的参数是等待处理的协程。这个函数会返回一个新建协程的句柄。

g_tcp_co = coroutine.create(runTCPServer);

经过这个句柄,咱们能够按固定间隔来执行对应的协程(例如每帧调用一次),调用 coroutine.resume() 函数便可。

coroutine.resume( g_tcp_co);

一旦有链接传进来,TCP 服务器经过 server:receive() 函数接收到数据。参数中 *| 表示接收到的数据以 LF\n 结尾。这里 TCP 服务器以反射模式工做:经过 server:send() 把接收到的数据原路返回给客户端。

为了测试 TCP 服务端,这里有一个TCP 客户端可供使用:

这个客户端用起来很简单。只要输入服务器名称(主机名称或者IP地址,例如:127.0.0.1)、服务器的端口号和须要传送的消息。这个客户端会自动添加消息结尾。而后点击发送就OK啦,服务器的反馈信息会在底部区域显示。


参考连接

The LUA SocketLib and the Coroutines
Tutorial:Networking with UDP
LUA 网络编程

相关文章
相关标签/搜索