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和端口,避免频繁的打包,同时又满足自定义链接远程服务器的需求。