lua socket 在ios14+版本连接一直提示no route to host
按照网上的教程也加了配置,根本没卵用
Bonjour,原名Rendezvous,是苹果电脑公司在其开发的操作系统 Mac OS X10.2版本之后引入的服务器搜索协议所使用的一个商标名。
Apple 官方文档,在5:54有解释这个value的含义
每一个服务类型都是一个唯一的字符串,通过IANA注册,这用来标识你的应用程序协议
官网提到,如果看到这个日志
才需要使用Bonjour服务
直接追源代码
require("socket.core").tcp();
static luaL_Reg luasocket_scripts_modules[] = {
{"ltn12", luaopen_lua_m_ltn12},
{"mime", luaopen_lua_m_mime},
{"socket.ftp", luaopen_lua_m_socket_ftp},
{"socket.headers", luaopen_lua_m_socket_headers},
{"socket.http", luaopen_lua_m_socket_http},
{"socket.mbox", luaopen_lua_m_socket_mbox},
{"socket.smtp", luaopen_lua_m_socket_smtp},
{"socket.tp", luaopen_lua_m_socket_tp},
{"socket.url", luaopen_lua_m_socket_url},
{"socket", luaopen_lua_m_socket},
{NULL, NULL}
};
// 注册socket.core的地方
static luaL_Reg luax_exts[] = {
{"socket.core", luaopen_socket_core},
{"mime.core", luaopen_mime_core},
LUASOCKET_API int luaopen_socket_core(lua_State *L) {
int i;
base_open(L);
// 在这个mod里面
for (i = 0; mod[i].name; i++) mod[i].func(L);
return 1;
}
static const luaL_Reg mod[] = {
{"auxiliar", auxiliar_open},
{"except", except_open},
{"timeout", timeout_open},
{"buffer", buffer_open},
{"inet", inet_open},
{"tcp", tcp_open}, // 就是这个tcp
{"udp", udp_open},
{"select", select_open},
{NULL, NULL}
};
int tcp_open(lua_State *L)
{
/* create classes */
auxiliar_newclass(L, "tcp{master}", tcp_methods); // 注册了很多函数
auxiliar_newclass(L, "tcp{client}", tcp_methods);
auxiliar_newclass(L, "tcp{server}", tcp_methods);
/* create class groups */
auxiliar_add2group(L, "tcp{master}", "tcp{any}");
auxiliar_add2group(L, "tcp{client}", "tcp{any}");
auxiliar_add2group(L, "tcp{server}", "tcp{any}");
/* define library functions */
#if LUA_VERSION_NUM > 501 && !defined(LUA_COMPAT_MODULE)
luaL_setfuncs(L, func, 0);
#else
luaL_openlib(L, NULL, func, 0);
#endif
return 0;
}
/* tcp object methods */
static luaL_Reg tcp_methods[] = {
{"__gc", meth_close},
{"__tostring", auxiliar_tostring},
{"accept", meth_accept},
{"bind", meth_bind},
{"close", meth_close},
{"connect", meth_connect}, // 我们要找的就是这个connect函数
{"dirty", meth_dirty},
{"getfamily", meth_getfamily},
{"getfd", meth_getfd},
{"getoption", meth_getoption},
{"getpeername", meth_getpeername},
{"getsockname", meth_getsockname},
{"getstats", meth_getstats},
{"setstats", meth_setstats},
{"listen", meth_listen},
{"receive", meth_receive},
{"send", meth_send},
{"setfd", meth_setfd},
{"setoption", meth_setoption},
{"setpeername", meth_connect},
{"setsockname", meth_bind},
{"settimeout", meth_settimeout},
{"shutdown", meth_shutdown},
{NULL, NULL}
};
static int meth_connect(lua_State *L)
{
p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1);
const char *address = luaL_checkstring(L, 2);
const char *port = luaL_checkstring(L, 3);
struct addrinfo connecthints;
const char *err;
memset(&connecthints, 0, sizeof(connecthints));
connecthints.ai_socktype = SOCK_STREAM;
/* make sure we try to connect only to the same family */
connecthints.ai_family = tcp->family;
timeout_markstart(&tcp->tm);
// 这里尝试着去连接
err = inet_tryconnect(&tcp->sock, &tcp->family, address, port,
&tcp->tm, &connecthints);
/* have to set the class even if it failed due to non-blocking connects */
auxiliar_setclass(L, "tcp{client}", 1);
if (err) {
lua_pushnil(L);
lua_pushstring(L, err);
return 2;
}
lua_pushnumber(L, 1);
return 1;
}
const char *inet_tryconnect(p_socket ps, int *family, const char *address,
const char *serv, p_timeout tm, struct addrinfo *connecthints)
{
struct addrinfo *iterator = NULL, *resolved = NULL;
const char *err = NULL;
/* try resolving */
err = socket_gaistrerror(getaddrinfo(address, serv,
connecthints, &resolved));
if (err != NULL) {
if (resolved) freeaddrinfo(resolved);
return err;
}
for (iterator = resolved; iterator; iterator = iterator->ai_next) {
timeout_markstart(tm);
/* create new socket if necessary. if there was no
* bind, we need to create one for every new family
* that shows up while iterating. if there was a
* bind, all families will be the same and we will
* not enter this branch. */
if (*family != iterator->ai_family) {
socket_destroy(ps);
err = socket_strerror(socket_create(ps, iterator->ai_family,
iterator->ai_socktype, iterator->ai_protocol));
if (err != NULL) {
freeaddrinfo(resolved);
return err;
}
*family = iterator->ai_family;
/* all sockets initially non-blocking */
socket_setnonblocking(ps);
}
/* try connecting to remote address */
// ↓真正的链接函数
err = socket_strerror(socket_connect(ps, (SA *) iterator->ai_addr,
(socklen_t) iterator->ai_addrlen, tm));
/* if success, break out of loop */
if (err == NULL) break;
}
freeaddrinfo(resolved);
/* here, if err is set, we failed */
return err;
}
// 注意:这个是window的实现,在wsocket.c
int socket_connect(p_socket ps, SA *addr, socklen_t len, p_timeout tm) {
int err;
/* don't call on closed socket */
if (*ps == SOCKET_INVALID) return IO_CLOSED;
/* ask system to connect */
if (connect(*ps, addr, len) == 0) return IO_DONE;
/* make sure the system is trying to connect */
err = WSAGetLastError();
if (err != WSAEWOULDBLOCK && err != WSAEINPROGRESS) return err;
/* zero timeout case optimization */
if (timeout_iszero(tm)) return IO_TIMEOUT;
/* we wait until something happens */
err = socket_waitfd(ps, WAITFD_C, tm);
if (err == IO_CLOSED) {
int len = sizeof(err);
/* give windows time to set the error (yes, disgusting) */
Sleep(10);
/* find out why we failed */
getsockopt(*ps, SOL_SOCKET, SO_ERROR, (char *)&err, &len);
/* we KNOW there was an error. if 'why' is 0, we will return
* "unknown error", but it's not really our fault */
return err > 0? err: IO_UNKNOWN;
} else return err;
}
// 这个才是ios的实现,在usocket.c
int socket_connect(p_socket ps, SA *addr, socklen_t len, p_timeout tm) {
int err;
/* avoid calling on closed sockets */
if (*ps == SOCKET_INVALID) return IO_CLOSED;
/* call connect until done or failed without being interrupted */
// 真正的实现用到了connect这个操作系统api
// int connect(int, const struct sockaddr *, socklen_t)
do if (connect(*ps, addr, len) == 0) return IO_DONE;
while ((err = errno) == EINTR);
/* if connection failed immediately, return error code */
// 如果链接失败,立刻返回错误码
if (err != EINPROGRESS && err != EAGAIN) return err;
/* zero timeout case optimization */
if (timeout_iszero(tm)) return IO_TIMEOUT;
/* wait until we have the result of the connection attempt or timeout */
err = socket_waitfd(ps, WAITFD_C, tm);
if (err == IO_CLOSED) {
if (recv(*ps, (char *) &err, 0, 0) == 0) return IO_DONE;
else return errno;
} else return err;
}
错误码:
#define EHOSTUNREACH 65 /* No route to host */
static const char *wstrerror(int err) {
switch (err) {
case WSAEINTR: return "Interrupted function call";
case WSAEACCES: return "Permission denied";
case WSAEFAULT: return "Bad address";
case WSAEINVAL: return "Invalid argument";
case WSAEMFILE: return "Too many open files";
case WSAEWOULDBLOCK: return "Resource temporarily unavailable";
case WSAEINPROGRESS: return "Operation now in progress";
case WSAEALREADY: return "Operation already in progress";
case WSAENOTSOCK: return "Socket operation on nonsocket";
case WSAEDESTADDRREQ: return "Destination address required";
case WSAEMSGSIZE: return "Message too long";
case WSAEPROTOTYPE: return "Protocol wrong type for socket";
case WSAENOPROTOOPT: return "Bad protocol option";
case WSAEPROTONOSUPPORT: return "Protocol not supported";
case WSAESOCKTNOSUPPORT: return "Socket type not supported";
case WSAEOPNOTSUPP: return "Operation not supported";
case WSAEPFNOSUPPORT: return "Protocol family not supported";
case WSAEAFNOSUPPORT:
return "Address family not supported by protocol family";
case WSAEADDRINUSE: return "Address already in use";
case WSAEADDRNOTAVAIL: return "Cannot assign requested address";
case WSAENETDOWN: return "Network is down";
case WSAENETUNREACH: return "Network is unreachable";
case WSAENETRESET: return "Network dropped connection on reset";
case WSAECONNABORTED: return "Software caused connection abort";
case WSAECONNRESET: return "Connection reset by peer";
case WSAENOBUFS: return "No buffer space available";
case WSAEISCONN: return "Socket is already connected";
case WSAENOTCONN: return "Socket is not connected";
case WSAESHUTDOWN: return "Cannot send after socket shutdown";
case WSAETIMEDOUT: return "Connection timed out";
case WSAECONNREFUSED: return "Connection refused";
case WSAEHOSTDOWN: return "Host is down";
case WSAEHOSTUNREACH: return "No route to host"; // 就是这个报错
case WSAEPROCLIM: return "Too many processes";
case WSASYSNOTREADY: return "Network subsystem is unavailable";
case WSAVERNOTSUPPORTED: return "Winsock.dll version out of range";
case WSANOTINITIALISED:
return "Successful WSAStartup not yet performed";
case WSAEDISCON: return "Graceful shutdown in progress";
case WSAHOST_NOT_FOUND: return "Host not found";
case WSATRY_AGAIN: return "Nonauthoritative host not found";
case WSANO_RECOVERY: return "Nonrecoverable name lookup error";
case WSANO_DATA: return "Valid name, no data record of requested type";
default: return "Unknown error";
}
}
测试代码
local panda=require("LuaPanda")
panda.setLogLevel(0)
local sock = require("socket.core").tcp();
require "config"
require "cocos.init"
local scene = cc.Scene:create()
local director = cc.Director:getInstance()
director:runWithScene(scene)
local cache = cc.TextureCache:getInstance()
local director = cc.Director:getInstance();
local size = director:getVisibleSize();
if true then
local text = ccui.Text:create("Test Panda", "", 60);
text:setTouchEnabled(true);
text:addClickEventListener(function(event)
text:setString("click to connect")
panda.start("192.168.1.134", 8818);
end);
text:setPosition(cc.p(size.width / 2, size.height / 2+100));
scene:addChild(text);
end
if true then
local text = ccui.Text:create("Test Socket", "", 60);
text:setTouchEnabled(true);
text:addClickEventListener(function(event)
-- 链接公网是没有任何问题的
local connectSuccess, status = sock:connect("82.157.123.54",9010)
-- 局域网不行,需要申请权限
-- local connectSuccess, status = sock:connect("192.168.1.134", 8818);
if connectSuccess then
log("connectSuccess")
text:setString("connectSuccess")
else
log("connectFailure")
text:setString("connectFailure")
if status then
log("status " .. status)
text:setString("status " .. status)
end
end
end)
text:setPosition(cc.p(size.width / 2, size.height / 2-100));
scene:addChild(text);
end
if true then return end
结论:
需要启用多播网络: www.jianshu.com/p/b137d36ec…
苹果官方教程: How to use multicast networking in your app
Apple开发者账号需要向官方申请多播网络的能力,通过后生成带有多播网络功能的证书,项目使用该证书即可。
其他解决办法
使用花生壳等内网映射工具,链接公网接口
luapanda的超时时间过短,导致第一次连接很容易失败,增加超时时间即可
扩展
为了保证所有人都可以在本机调试远程设备,可以考虑在app内增加一个设置界面,用来设置ip和端口,避免频繁的打包,同时又满足自定义链接远程服务器的需求。