Skynet服务器框架(六) Socket服务源码剖析和应用

阅读:1163 2019-03-20 16:45:53 来源:新网

引言:如何在skynet框架中使用socket+protobuf。上篇skynet服务器框架(五)使用pbc(protobuf)我们已经大致了解了如何在skynet中通过pcb来使用protobuf,接下来我们开始了解skynet中有关socket的部分。socket_serverc源码解析:较早版本的skynet并没有提供对于网络层的定制,而是可以由开发者自行定义,不过最新版本的skynet已经加入了网络层的支持,也有独立的项目例子socket-server是纯c语言的实现。核心源码包括:socket_epoll.h、socket_kqueue.h、socket_poll.h、socket_server.c和socket_server.h。1.异步io此网络库已经封装了socket的epoll和kququ两种底层接口,其功能就是:处理阻塞/非阻塞socket中read/write的问题,它们的区别在于适用于不同操作系统的通信接口的封装:epoll(linux2.6下性能最好的多路i/o就绪通知方法)->linux系统(详见:【linux学习】epoll详解)kqueue->其他unix的变种系统(例如:freebsd)假如是在windows下,可以使用iocp模型来实现类似的功能。这一点可以从socket_poll.h的源码的宏定义中看出来://平台判断#ifdef__linux__#include"socket_epoll.h"#endif#ifdefined(__apple__)||defined(__freebsd__)||defined(__openbsd__)||defined(__netbsd__)#include"socket_kqueue.h"#endif在socket_epoll.c和socket_kqueue.c中都实现了socket_poll.h中定义的方法,通过这个宏判断,假如当前运行于linux系统下,就调用epoll的实现,而在freebsd系统中则使用kqueue的实现。定义部分://统一使用的句柄类型typedefintpoll_fd;//转存的内核通知的结构体structevent{void*s;//通知的句柄boolread;//是否可读boolwrite;//是否可写};核心接口:这是poll用来管理socket事件或者消息的核心调用接口:/***定义了外部使用的接口,具体实现在socket_epoll.h和socket_kqueue.h中定义*///错误检测接口(fd:检测的文件描述符(句柄),返回true表示有错误)staticboolsp_invalid(poll_fdfd);//创建句柄(可通过sp_invalid检测是否创建失败,poll_fd是创建好的句柄)staticpoll_fdsp_create();//释放句柄staticvoidsp_release(poll_fdfd);/**在轮序句柄fd中添加一个指定sock文件描述符,用来检测该socket*fd:sp_create()返回的句柄*sock:待处理的文件描述符,一般为socket()返回结果*ud:自己使用的指针地址特殊处理*:返回0表示添加成功,-1表示失败*/staticintsp_add(poll_fdfd,intsock,void*ud);/**在轮询句柄fd中删除注册过的sock描述符*fd:sp_create()创建的句柄*sock:socket()创建的句柄*/staticvoidsp_del(poll_fdfd,intsock);/**在轮序句柄fd中修改sock注册类型*fd:轮询句柄*sock:待处理的句柄*ud:用户自定义数据地址*enable:true表示开启写,false表示还是监听读*/staticvoidsp_write(poll_fd,intsock,void*ud,boolenable);/**轮询句柄,等待有结果的时候构造当前用户层结构structevent结构描述中*fd:sp_create()创建的句柄*e:一段structevent内存的首地址*max:e内存能够使用的最大值*:返回等待到的变动数,相对于e*/staticintsp_wait(poll_fd,structevent*e,intmax);/**为套接字描述符设置为非阻塞的*sock:文件描述符*/staticvoidsp_nonblocking(intsock);接口具体实现在socket_epoll.h和socket_kqueue.h中定义更详细的实现区别可以参考:《socket编程之select、poll、kqueue、epoll》2.socket-server测试实例:下载纯c实现的socket-server源码后,先打开源码中给出的test.c,并找到main入口:intmain(){//忽略对于sigpipe信号的默认处理structsigactionsa;sa.sa_handler=sig_ign;sigaction(sigpipe,&sa,0);//创建一个socket服务实例structsocket_server*ss=socket_server_create();//测试方法test(ss);//释放socket实例相关的资源socket_server_release(ss);return0;}上面操作就是使用socket_server最简单的例子,通过socket_server来控制和管理真正的socket网络通信过程,实现步骤大致如下:创建一个socket_server实例;通过socket_server管理socket通信;释放socket_server实例结束程序。3.源码剖析:在程序入门位置设置忽略sigpipe信号:structsigactionsa;sa.sa_handler=sig_ign;sigaction(sigpipe,&sa,0);在网上检索资料《monitorserver代码阅读笔记一:使用sigactionsigpipe,&sa,0避免写一个已关闭的socket导致进程退出》了解到原因大致如下:在linux下写socket的程序的时候,如果尝试send到一个disconnectedsocket会让底层抛出一个sigpipe信号;对一个对端已经关闭的socket调用两次write,第二次将会生成sigpipe信号。对于这个信号的默认处理方法是退出进程,但是通常我们不希望按照这样来处理,所以通过上述代码屏蔽默认初始方法。创建socket服务实例:structsocket_server*ss=socket_server_create();这里通过调用socket_server_create方法来创建一个socket_server对象,这个对象有一个比较重要的成员是epoll类型的对象,它负责管理自身所有的socket连接和数据读写操作。test函数:test方法中做的事情主要是:创建和启动一个线程用于轮询socket消息:pthread_tpid;//创建一个新的线程用于轮询poll消息pthread_create(&pid,null,_poll,ss);//启动轮询线程pthread_join(pid,null);尝试作为一个客户端去连接一个端口地址:intc=socket_server_connect(ss,100,"127.0.0.1",80);作为一个服务器新建一个监听(监听指定端口地址),并启动监听://创建监听,返回socket句柄用于操作此监听intl=socket_server_listen(ss,200,"127.0.0.1",8888,32);printf("listening%dn",l);//通过操作句柄启动此socketsocket_server_start(ss,201,l);创建多个连接请求,等待5秒后退出socket:inti;for(i=0;i<100;i++){socket_server_connect(ss,400+i,"127.0.0.1",8888);}//休眠5秒sleep(5);//退出socketsocket_server_exit(ss);循环查询socket消息:staticvoid*_poll(void*ud){structsocket_server*ss=ud;//返回消息的内容structsocket_messageresult;//执行一个死循环for(;;){//一直查询socket消息类型typeinttype=socket_server_poll(ss,&result,null);//最好不要在这个线程中执行对于socket的操作指令(例如:socket_server_close等)switch(type){casesocket_exit://退出死循环returnnull;casesocket_data://此时result.ud表示的是result.data的大小printf("message(%lu)[id=%d]size=%dn",result.opaque,result.id,result.ud);//释放数据缓存free(result.data);break;casesocket_close:printf("close(%lu)[id=%d]n",result.opaque,result.id);break;casesocket_open:printf("open(%lu)[id=%d]%sn",result.opaque,result.id,result.data);break;casesocket_error:printf("error(%lu)[id=%d]n",result.opaque,result.id);break;casesocket_accept://被动连接,此时还不能发送消息给连接上来的客户端,因为socket还没加入到poll中进行管理,需要先调用socket_server_start才能发送数据printf("accept(%lu)[id=%d%s]from[%d]n",result.opaque,result.ud,result.data,result.id);break;}}}4.socket_server.h源码剖析:一般阅读源码,肯定是先从.h头文件读起,因为这里定义了此类对外提供的功能接口,所以在解读socket_server.c前我们先来看看它的头文件socket_server.h:首先,宏定义中定义了socket通信的所有消息类型://宏定义了socket_server_poll()返回的socket消息类型#definesocket_data0//数据data到来消息#definesocket_close1//关闭连接消息#definesocket_open2//连接成功消息#definesocket_accept3//被动连接建立消息(accept返回了连接的fd句柄,但此连接还未被假如epoll中管理)#definesocket_error4//错误消息#definesocket_exit5//退出socket消息#definesocket_udp6//udp通信消息消息数据结构:在skynet的socket通信的c源码中定义了几个消息结构(socket_message、skynet_socket_message和skynet_message),他们分别对应于不同的服务:socket_message对应于socket_server服务中的消息传输类型:structsocket_message{intid;//应用层的socketfd句柄uintptr_topaque;//在skynet中对应一个ator实体的handle句柄intud;//对于accept连接来说,ud是新连接的fd;对于数据(data)来说,ud是数据的大小char*data;//数据指针};在将socket_server引入到skynet框架中时,还进行了第二次的封装,在skynet中调用socket服务也是调用封装后的skynet_socket.h中的接口,skynet_socket_message对应skynet_socket_server://skynet_socket服务间传递消息结构structskynet_socket_message{inttype;intid;//intud;char*buffer;//消息携带数据};当然在skynet不同服务(actor)间进行通信的时候还使用了另一种消息结构:skynet_message对应actor之间,定义在skynet_mq.h中:structskynet_message{uint32_tsource;intsession;void*data;size_tsz;};核心api接口:假如要在c语言中直接使用socket_server,基本上是用这些封装好的接口基本上也就足够了://创建一个socket_serverstructsocket_server*socket_server_create();//释放一个socket_server的资源占用voidsocket_server_release(structsocket_server*);/**封装了的epoll或kqueue,用来获取socket的网络事件或消息*(通常放在循环体中持续监听网络消息)*socket_server:socket_server_create()返回的socket_server实例*result:结果数据存放的地址指针*:返回消息类型,对应于宏定义中的socket_data的类型*/intsocket_server_poll(structsocket_server*,structsocket_message*result,int*more);//退出socket_servervoidsocket_server_exit(structsocket_server*);/**关闭socket_server*socket_server:socket_server_create()返回的socket_server实例*opaque:skynet中服务handle的句柄*id:socket_server_listen()返回的id*/voidsocket_server_close(structsocket_server*,uintptr_topaque,intid);/**停止socket*socket_server:socket_server_create()返回的socket_server实例*opaque:skynet中服务handle的句柄*id:socket句柄*/voidsocket_server_shutdown(structsocket_server*,uintptr_topaque,intid);/**启动socket监听(启动之前要先通过socket_server_listen()绑定端口)*socket_server:socket_server_create()返回的socket_server实例*opaque:skynet中服务handle的句柄*id:socket_server_listen()返回的id*/voidsocket_server_start(structsocket_server*,uintptr_topaque,intid);/**发送数据*socket_server:socket_server_create()返回的socket_server实例*buffer:要发送的数据*sz:数据的大小*id:socket_server_listen()返回的id*:假如返回-1表示error*/int64_tsocket_server_send(structsocket_server*,intid,constvoid*buffer,intsz);voidsocket_server_send_lowpriority(structsocket_server*,intid,constvoid*buffer,intsz);/**绑定监听ip端口*socket_server:socket_server_create()返回的socket_server实例*opaque:skynet中服务handle的句柄*addr:ip地址*port:端口号*:返回一个id作为操作此端口监听的句柄*/intsocket_server_listen(structsocket_server*,uintptr_topaque,constchar*addr,intport,intbacklog);/**以非阻塞的方式连接服务器*socket_server:socket_server_create()返回的socket_server实例*opaque:skynet中服务handle的句柄*addr:ip地址*port:端口号*:返回一个id作为操作此端口监听的句柄*/intsocket_server_connect(structsocket_server*,uintptr_topaque,constchar*addr,intport);/**并不对应bind函数,而是将stdin、stout这类io加入到epoll中管理*socket_server:socket_server_create()返回的socket_server实例*opaque:skynet中服务handle的句柄*fd:socket的文本描述*/intsocket_server_bind(structsocket_server*,uintptr_topaque,intfd);//fortcpvoidsocket_server_nodelay(structsocket_server*,intid);/**创建一个udpsocket监听,并绑定skynet服务的handle,udp不需要像tcp那样要调用socket_server_start后才能接收消息*如果port!=0,绑定socket,如果addr==null,绑定ipv40.0.0.0。如果想要使用ipv6,地址使用“::”,端口中port设为0*/intsocket_server_udp(structsocket_server*,uintptr_topaque,constchar*addr,intport);//设置默认的端口地址,返回0表示成功intsocket_server_udp_connect(structsocket_server*,intid,constchar*addr,intport);/**假如socket_udp_address是空的,使用最后最后调用socket_server_udp_connect时传入的address代替*也可以使用socket_server_send来发送udp数据*/int64_tsocket_server_udp_send(structsocket_server*,intid,conststructsocket_udp_address*,constvoid*buffer,intsz);//获取传入消息的ip地址address,传入的socket_message*必须是socket_udp类型conststructsocket_udp_address*socket_server_udp_address(structsocket_server*,structsocket_message*,int*addrsz);//ifyousendpackagesz==-1,usesoi.voidsocket_server_userobject(structsocket_server*,structsocket_object_interface*soi);5.socket_server.c源码解析:首先,我们先看一下声明的几个结构体://写缓冲队列structwb_list{structwrite_buffer*head;//写缓冲区的头指针structwrite_buffer*tail;//写缓冲区的尾指针};structsocket{uintptr_topaque;//所属服务在skynet中对应的handlestructwb_listhigh;//高优先级写队列structwb_listlow;//低优先级写队列int64_twb_size;//写缓存大小intfd;//对应内存分配的fd(文件描述)intid;//应用层维护一个与fd对应的id句柄uint16_tprotocol;//使用的协议类型(tcp/udp)uint16_ttype;//scoket的类型或状态(读、写、监听等)union{intsize;//读缓存预估需要的大小uint8_tudp_address[udp_address_size];}p;};structsocket_server{intrecvctrl_fd;//接收管道的句柄intsendctrl_fd;//发送管道的句柄intcheckctrl;//释放检测命令poll_fdevent_fd;//epoll或kevent的句柄intalloc_id;//应用层分配id用的intevent_n;//epoll_wait返回的事件数intevent_index;//当前处理的事件序号structsocket_object_interfacesoi;structeventev[max_event];//epoll_wait返回的事件集合structsocketslot[max_socket];//每个socketserver可以包含多个socket,这是存储这些socket的数组(应用层预先分配的)charbuffer[max_info];//临时数据的保存,比如保存对方的地址信息等uint8_tudpbuffer[max_udp_package];fd_setrfds;//用于select的fd集};6.socket_server接口实现分析:调用过程:一个socket_server中可以有多个socket,工作实现过程如下:skynet的某个服务通过socket_server发送命令来操作底层的socket。原理解析:服务调用socket_server的任何接口,实质上都是通过向socket_server管道的写端发送一个request_package的命令包,例如:intsocket_server_connect(structsocket_server*ss,uintptr_topaque,constchar*addr,intport){//创建一个命令结构体structrequest_packagerequest;//计算包体的大小intlen=open_request(ss,&request,opaque,addr,port);//包体小于0则不执行此操作if(len<0)return-1;//向写管道发送一个'0'指令,发起一个tcp连接请求send_request(ss,&request,'o',sizeof(request.u.open)+len);//返回一个用于操作此socket连接的句柄returnrequest.u.open.id;}常用操作指令:上面发起连接时发送了一个‘o’(字母)指令来实现socket连接操作请求,其实socket_server其他操作的实现方式也与此类似,只是使用的指令不同,常用的指令有:sstartsocket启动一个socketbbindsocket绑定一个socketllistensocket监听一个socketkclosesocket关闭一个socketoconnectto(open)连接一个socketxexit退出一个socketdsendpackage(high)发送数据psendpackage(low)(不常用,也用于发送数据)asendudppackagetsetoptucreateudpsocketcsetudpaddress以上就是socket_server的源码核心部分,这部分集成到skynet的capi中后,采用异步读写,直接通过c语言调用的话,监听一个端口或者发起一个tcp连接,操作的结果要等待skynet的事件回调,skynet会将结果以ptype_socket类型的消息发送给发起请求的服务。skynet中socket服务的使用:接下来,我们直接使用skynet框架中自带的socket服务,为了进一步适用于skynet框架,又进行一步对socket_server进行了封装,所有常用的接口都封装在skynet_socket.h和skynet_socket.c中,因为框架业务层逻辑都使用lua来编写,所以下面我们尝试在lua中启动一个socket服务。1.注意点:由于异步非阻塞的capi在skynet中使用起来并不方便,结合lua的语言特性中的coroutine机制(携程),skynet中采用阻塞模式封装了一组luaapi用于tcpsocket的读写操作。当我们在skynet的某个服务的lua中调用了socketapi时,服务有可能被挂起(时间片被让给其他业务处理),待结果通过socket消息返回,coroutine将延续执行。2.api几个常用的skynet中的socket接口:*新建一个tcp连接:socket.open(address,port)*启动socket监听:socket.start(id)*读取socket接收数据:socket.read(id)*向socket中写数据:socket.write(id,str)*监听一个端口:socket.listen(id,port)*服务开始方式:socket.abandon(id)*关闭socket:socket.close(id)查询接口可以在lualib/socket.lua中查找,更详细的api解析参考:skynet官方文档socket.open(address,port)建立一个tcp连接。返回一个数字id。socket.close(id)关闭一个连接,这个api有可能阻塞住执行流。因为如果有其它coroutine正在阻塞读这个id对应的连接,会先驱使读操作结束,close操作才返回。socket.close_fd(id)在极其罕见的情况下,需要粗暴的直接关闭某个连接,而避免socket.close的阻塞等待流程,可以使用它。socket.shutdown(id)强行关闭一个连接。和close不同的是,它不会等待可能存在的其它coroutine的读操作。一般不建议使用这个api,但如果你需要在__gc元方法中关闭连接的话,shutdown是一个比close更好的选择(因为在gc过程中无法切换coroutine)。socket.read(id,sz)从一个socket上读sz指定的字节数。如果读到了指定长度的字符串,它把这个字符串返回。如果连接断开导致字节数不够,将返回一个false加上读到的字符串。如果sz为nil,则返回尽可能多的字节数,但至少读一个字节(若无新数据,会阻塞)。socket.readall(id)从一个socket上读所有的数据,直到socket主动断开,或在其它coroutine用socket.close关闭它。socket.readline(id,sep)从一个socket上读一行数据。sep指行分割符。默认的sep为“n”。读到的字符串是不包含这个分割符的。socket.block(id)等待一个socket可读。socketapi中有两个不同的写操作。对应skynet为每个socket设定的两个写队列。通常我们只需要用:socket.write(id,str)把一个字符串置入正常的写队列,skynet框架会在socket可写时发送它。但同时skynet还提供一个低优先级的写操作(如果你不需要这个设计,可以不使用它):socket.lwrite(id,str)把字符串写入低优先级队列。如果正常的写队列还有写操作未完成时,低优先级队列上的数据永远不会被发出。只有在正常写队列为空时,才会处理低优先级队列。但是,每次写的字符串都可以看成原子操作。不会只发送一半,然后转去发送正常写队列的数据。对于服务器,通常我们需要监听一个端口,并转发某个接入连接的处理权。那么可以用如下api:socket.listen(address,port)监听一个端口,返回一个id,供start使用。socket.start(id,accept)accept是一个函数。每当一个监听的id对应的socket上有连接接入的时候,都会调用accept函数。这个函数会得到接入连接的id以及ip地址。你可以做后续操作。每当accept函数获得一个新的socketid后,并不会立即收到这个socket上的数据。这是因为,我们有时会希望把这个socket的操作权转让给别的服务去处理。socket的id对于整个skynet节点都是公开的。也就是说,你可以把id这个数字通过消息发送给其它服务,其他服务也可以去操作它。任何一个服务只有在调用socket.start(id)之后,才可以收到这个socket上的数据。skynet框架是根据调用start这个api的位置来决定把对应socket上的数据转发到哪里去的。向一个socketid写数据也需要先调用start,但写数据不限制在调用start的同一个服务中。也就是说,你可以在一个服务中调用start,然后在另一个服务中向其写入数据。skynet可以保证一次write调用的原子性。即,如果你有多个服务同时向一个socketid写数据,每个写操作的串不会被分割开。socket.abandon(id)清除socketid在本服务内的数据结构,但并不关闭这个socket。这可以用于你把id发送给其它服务,以转交socket的控制权。socket.warning(id,callback)当id对应的socket上待发的数据超过1m字节后,系统将回调callback以示警告。functioncallback(id,size)回调函数接收两个参数id和size,size的单位是k。如果你不设回调,那么将每增加64k利用skynet.error写一行错误信息。3.服务端实现:参考skynet提供的testsocket.lua的实现案例,创建一个socket服务端,接收客户端的连接,输出连接客户端的ip地址,并发送一段字符串”hello,”,步骤如下:将ip和端口号等信息添加到config配置文件中:server_ip="0.0.0.0:10080"新建一个服务器脚本socketserver.lua放在test或examples目录下;引入模块并创建skynet服务实例和socket实例:localskynet=require"skynet"localsocket=require"socket"调用skynet.start接口,并设置监听指定ip地址:官方的案例提供了两种方式用于socket处理,普通模式和agent模式,他们的实现方式分别如下:普通模式:localfunctionaccept(id)socket.start(id)--向socket中写数据socket.write(id,"helloskynetn")--创建一个agent服务skynet.newservice(service_name,"agent",id)--notice:somedataonthisconnection(id)maylostbeforenewservicestart.--so,becarefulwhenyouwanttousestart/abandon/start.socket.abandon(id)endskynet.start(function()--监听一个端口,返回的id可作为此socket的句柄,用来操作此socketlocalid=assert(socket.listen(skynet.getenv"server_ip"))print("listensocket:"..skynet.getenv"server_ip")--启动socket监听socket.start(id,function(id,addr)print("connectfrom"..addr..""..id)--youhavechoices:--1.skynet.newservice("testsocket","agent",id)--2.skynet.fork(echo,id)--3.accept(id)--处理接收的数据的方法accept(id)end)end)agent模式:localfunctionecho(id)socket.start(id)whiletruedolocalstr=socket.read(id)ifstrthensocket.write(id,str)elsesocket.close(id)returnendendendid=tonumber(id)skynet.start(function()--创建一个额外线程用于监听返回结果skynet.fork(function()echo(id)skynet.exit()end)end)参考这个例子,我们编写我们的服务端脚本代码如下:localskynet=require"skynet"localsocket=require"socket"--简单echo服务functionecho(id,addr)socket.start(id)whiletruedolocalstr=socket.read(id)ifstrthenskynet.error("客户端"..id,"发送内容:",str)socket.write(id,str)elsesocket.close(id)skynet.error("客户端"..id,"["..addr.."]","断开连接")returnendendend--服务入口skynet.start(function()localid=assert(socket.listen(skynet.getenv"app_server"))socket.start(id,function(id,addr)skynet.error("客户端"..id,"["..addr.."]","已连接")skynet.fork(echo,id,addr)end)end)4.客户端实现:客户端的功能是向服务器监听的端口发送字符串数据,然后打印输出服务器返回的数据。新建一个socket客户端测试脚本socketclient.lua放在test或examples目录下,内容如下:localskynet=require"skynet"localsocket=require"socket"localname=...or""function_read(id)whiletruedolocalstr=socket.read(id)ifstrthenskynet.error(id,"收到服务器数据:",str)socket.close(id)skynet.exit()elsesocket.close(id)skynet.error("断开链接")skynet.exit()endendendskynet.start(function()--连接到服务器localaddr=skynet.getenv"app_server"localid=socket.open(addr)ifnotidthenskynet.error("无法连接"..addr)skynet.exit()endskynet.error("已连接")--启动读协程skynet.fork(_read,id)socket.write(id,"hello,"..name)end)运行程序:便捷启动skynet的lua服务:这里使用了另外一种启动snlua服务的方式:linsh@ubuntu:/application/skynet$./skynetconfig[:01000001]launchlogger[:01000002]launchsnluabootstrap[:01000003]launchsnlualauncher[:01000004]launchsnluacmaster[:01000004]masterlistensocket0.0.0.0:2017[:01000005]launchsnluacslave[:01000005]slaveconnecttomaster127.0.0.1:2017[:01000006]launchharbor116777221[:01000004]connectfrom127.0.0.1:591564[:01000004]harbor1(fd=4)report127.0.0.1:2526[:01000005]waitingfor0harbors[:01000005]shakehandready[:01000007]launchsnluadatacenterd[:01000008]launchsnluaservice_mgr[:01000009]launchsnluamain[:01000009]serverstart[:0100000a]launchsnluaprotoloader[:0100000b]launchsnluaconsole[:0100000c]launchsnluadebug_console8000[:0100000c]startdebugconsoleat127.0.0.1:8000[:0100000d]launchsnluasimpledb[:0100000e]launchsnluawatchdog[:0100000f]launchsnluagate[:0100000f]listenon0.0.0.0:8888[:01000009]watchdoglistenon8888[:01000009]killself[:01000002]killself也就是先使用.skynet(config文件目录)来启动skynet服务,然后输入要启动的lua服务名称,例如这里我们创建的firsttest.lua:firsttest[:01000010]launchsnluafirsttest如此便可以实现启动指定的服务,无需通过修改main.lua来完成lua服务的启动。启动服务器:在终端输入:socketserver启动客户端:在终端输入:socketclient输出结果:linsh@ubuntu:/application/skynet$./skynetconfig[:01000001]launchlogger[:01000002]launchsnluabootstrap[:01000003]launchsnlualauncher[:01000004]launchsnluacmaster[:01000004]masterlistensocket0.0.0.0:2017[:01000005]launchsnluacslave[:01000005]slaveconnecttomaster127.0.0.1:2017[:01000004]connectfrom127.0.0.1:592024[:01000006]launchharbor116777221[:01000004]harbor1(fd=4)report127.0.0.1:2526[:01000005]waitingfor0harbors[:01000005]shakehandready[:01000007]launchsnluadatacenterd[:01000008]launchsnluaservice_mgr[:01000009]launchsnluamain[:01000009]serverstart[:0100000a]launchsnluaprotoloader[:0100000b]launchsnluaconsole[:0100000c]launchsnluadebug_console8000[:0100000c]startdebugconsoleat127.0.0.1:8000[:0100000d]launchsnluasimpledb[:0100000e]launchsnluawatchdog[:0100000f]launchsnluagate[:0100000f]listenon0.0.0.0:8888[:01000009]watchdoglistenon8888[:01000009]killself[:01000002]killselfsocketserver[:01000010]launchsnluasocketserversocketclient[:01000012]launchsnluasocketclient[:01000010]客户端10[127.0.0.1:54504]已连接[:01000012]已连接[:01000010]客户端10发送内容:hello,[:01000012]9收到服务器数据:hello,[:01000010]客户端10[127.0.0.1:54504]断开连接[:01000012]killself我们看[]符号中间的id,那个就是skynet用来管理服务所分配的id标识,不难发现socketserver服务的id是10,而socketclient服务的id是12,客户端启动成功后连接服务器,服务器接收到连接打印当前请求连接的客户端ip,同时返回一个字符串“hello,”然后客户端收到连接成功的结果,打印出“已连接”和服务器发送的数据,然后服务器就断开了与客户端的连接。小结:本篇主要是了解socket_server这个socke的capi的实现过程还有引入到skynet框架后的一些调整,最后其实重要的是了解socket_server的luaapi的使用以及lua的coroutine机制。附件:关于socket_server源码解析,还可以参考一下视频:skynet源码分析-01skynet源码分析(一)epoll及事件循环工作流程:skynet源码分析-02skynet源码分析(二)skynet源码分析-03skynet源码分析(三)

相关文章
{{ v.title }}
{{ v.description||(cleanHtml(v.content)).substr(0,100)+'···' }}
你可能感兴趣
推荐阅读 更多>
推荐商标

{{ v.name }}

{{ v.cls }}类

立即购买 联系客服