——— LUA SocketLib 和 协程

前言:

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

简介

目标读者:会使用 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):源(客户端)一半套接字 和 终点(服务端)一半套接字。

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

建立 TCP/IP 服务器

通常建立一个TCP服务器的过程可以直接用 SocketLib 的函数实现。具体流程如下:

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

SocketLib 是 Hyperion 的一部分,它可以像 LUA 提供的其他标准库一样使用。下面给出建立 TCP 服务器的代码:

  1. function createTCPServer( host, port, backlog) // host 是个啥?
  2. local tcp_server,err = socket.tcp();
  3. if tcp_server == nil then
  4. return nil,err;
  5. end
  6. tcp_server:setoption("reuseaddr",true);
  7. local res, err = tcp_server:bind(host,port);
  8. if res==nil then
  9. return nil,err;
  10. end
  11. res,err = tcp_server:listen(backlog);
  12. if res == nil then
  13. return nil,err;
  14. end
  15. return tcp_server;
  16. 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() 函数来实现。

现在,让我们以更具体的方式来看一看。先来看看等待传入连接的核心实现部分。

  1. function runTCPServer()
  2. local stop_server = 0;
  3. local num_cnx = 0;
  4. while( stop_server == 0) do
  5. local err = "";
  6. local tcp_client = 0;
  7. g_tcp_server:settimeout(0.01);
  8. tcp_client,err = g_tcp_server:accept();
  9. if tcp_client ~= nil then
  10. g_client_msg, err = tcp_client:receive('*|');
  11. if( g_client_msg ~= nil) then
  12. if (g_client_msg == "STOP") then
  13. stop_server = 1;
  14. else
  15. tcp_client:send("Message :"..g_client_msg);
  16. end
  17. end
  18. if tcp_client ~= nil then
  19. tcp_client:close();
  20. end
  21. end
  22. coroutine.yield();
  23. end
  24. end

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

  1. g_tcp_co = coroutine.create(runTCPServer);

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

  1. 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 网络编程

LuaSocket 学习笔记的更多相关文章

  1. js学习笔记:webpack基础入门(一)

    之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...

  2. PHP-自定义模板-学习笔记

    1.  开始 这几天,看了李炎恢老师的<PHP第二季度视频>中的“章节7:创建TPL自定义模板”,做一个学习笔记,通过绘制架构图.UML类图和思维导图,来对加深理解. 2.  整体架构图 ...

  3. PHP-会员登录与注册例子解析-学习笔记

    1.开始 最近开始学习李炎恢老师的<PHP第二季度视频>中的“章节5:使用OOP注册会员”,做一个学习笔记,通过绘制基本页面流程和UML类图,来对加深理解. 2.基本页面流程 3.通过UM ...

  4. 2014年暑假c#学习笔记目录

    2014年暑假c#学习笔记 一.C#编程基础 1. c#编程基础之枚举 2. c#编程基础之函数可变参数 3. c#编程基础之字符串基础 4. c#编程基础之字符串函数 5.c#编程基础之ref.ou ...

  5. JAVA GUI编程学习笔记目录

    2014年暑假JAVA GUI编程学习笔记目录 1.JAVA之GUI编程概述 2.JAVA之GUI编程布局 3.JAVA之GUI编程Frame窗口 4.JAVA之GUI编程事件监听机制 5.JAVA之 ...

  6. seaJs学习笔记2 – seaJs组建库的使用

    原文地址:seaJs学习笔记2 – seaJs组建库的使用 我觉得学习新东西并不是会使用它就够了的,会使用仅仅代表你看懂了,理解了,二不代表你深入了,彻悟了它的精髓. 所以不断的学习将是源源不断. 最 ...

  7. CSS学习笔记

    CSS学习笔记 2016年12月15日整理 CSS基础 Chapter1 在console输入escape("宋体") ENTER 就会出现unicode编码 显示"%u ...

  8. HTML学习笔记

    HTML学习笔记 2016年12月15日整理 Chapter1 URL(scheme://host.domain:port/path/filename) scheme: 定义因特网服务的类型,常见的为 ...

  9. DirectX Graphics Infrastructure(DXGI):最佳范例 学习笔记

    今天要学习的这篇文章写的算是比较早的了,大概在DX11时代就写好了,当时龙书11版看得很潦草,并没有注意这篇文章,现在看12,觉得是跳不过去的一篇文章,地址如下: https://msdn.micro ...

随机推荐

  1. linux mysql,tomcat与java的安装

    先将服务器的安全组设置为 把所有端口或者所需要的端口开放 然后测试远程访问 ssh 用户@ip                       登录 输入密码 service iptables stop  ...

  2. 《项目实战》从Spring开始说起

    引导 从今天开始,我们正式进入项目实战系列,我们会从项目架构的搭建,以及如何解决三高问题(高并发.高可用.高性能),源码会同步进行更新,欢迎大家持续关注 https://gitee.com/liupa ...

  3. 【算法随记六】一段Matlab版本的Total Variation(TV)去噪算法的C语言翻译。

    最近看到一篇文章讲IMAGE DECOMPOSITION,里面提到了将图像分为Texture layer和Structure layer,测试了很多方法,对于那些具有非常强烈纹理的图像,总觉得用TV去 ...

  4. Day 04 作业

    目录 作业 简述Python的五大数据类型的作用.定义方式.使用方法: 数字类型 字符串类型 列表 字典 布尔型 一行代码实现下述代码实现的功能: 写出两种交换x.y值的方式: 一行代码取出nick的 ...

  5. 牛客NOIP暑期七天营-提高组5+普及组5

    ————提高组———— 第一题:deco的abs 题目链接:https://ac.nowcoder.com/acm/contest/934/A 因为每个数都可以加任意次 d ,所以可以推出 0 < ...

  6. 《Java练习题》习题集一

    编程合集: https://www.cnblogs.com/jssj/p/12002760.html Java总结:https://www.cnblogs.com/jssj/p/11146205.ht ...

  7. 《Java练习题》习题集二

    编程合集: https://www.cnblogs.com/jssj/p/12002760.html Java总结:https://www.cnblogs.com/jssj/p/11146205.ht ...

  8. js问题记录(一) -- 关于for in, sort(), 及prototype

    1.关于for in for in : 遍历对象中的可枚举的属性 例子1:for in 遍历对象的键为String类型,所以调用时用Object[key]形式,而不用Object.key形式 < ...

  9. 终极CURD-4-java8新特性

    目录 1 概述 2 lambda表达式 2.1 lambda重要知识点总结 2.2 java内置函数接口 2.3 方法引用 2.4 构造器引用 2.5 数组引用 2.6 lambda表达式的陷阱 3 ...

  10. python操作文件——序列化pickling和JSON

    当我们在内存中定义一个dict的时候,我们是可以随时修改变量的内容的: >>> d=dict(name='wc',age=28) >>> d {'name': 'w ...