网站怎么通过流量赚钱,建设医院的网站,小装修网站开发费用,驻马店做网站多少钱对半关闭状态进行了解决的有JAVA的netty、skynet开源框架。大多数网络连接程序在read0时即调用close()关闭TCP连接#xff1b;但是#xff0c;在read0到调用close()之间#xff0c;可能还有很多数据需要发送#xff08;send#xff09;#xff0c;如果read0时即调用close…对半关闭状态进行了解决的有JAVA的netty、skynet开源框架。大多数网络连接程序在read0时即调用close()关闭TCP连接但是在read0到调用close()之间可能还有很多数据需要发送send如果read0时即调用close()那么对端就收不到这些数据了。对TCP三次握手和四次挥手不了解的可以查阅前面的文章。二、TCP四次挥手流程需要四次挥手的原因希望将FIN包控制权交给应用程序去处理以便处理断开连接的善后工作即对端可能还有数据要发送。四次挥手流程服务器收到FIN包自动回复ACK包进入CLOSE_WAIT状态。此时TCP协议栈会为FIN包插入一个文件描述符EOF到内核态网络读缓冲区。应用程序通过read0来感知这个FIN包。如果此时应用程序有数据要发送则发送数据后调用关闭连接操作发送FIN包到对端。双端关闭都需要一个FIN和一个ACK流程。三、发送FIN包的场景关闭读端调用shutdown(fd,SHUT_RD)。关闭写端调用shutdown(fd,SHUT_WR)半关闭连接仍旧接收数据继而等待对方关闭。关闭双端调用close(fd) 或者shoutdown(fd,SHUT_RDWR)。关闭进程内核协议栈会自动发送FIN包。如果客户端想进入半关闭状态需要关闭写端服务端会把自己的读端关闭从而进入半关闭状态。如果客户端是关闭读端或者把读写端都关闭了调用close(fd) 或者shoutdown(fd,SHUT_RDWR)或者是关闭了进程那么会进入快速关闭流程即接受到对端回复的FIN包之后回复RST包直接进入CLOSE状态而没有TIME_WAIT状态这是解决有大量TIME_WAIT现象的常用方法。思考四次挥手中为什么存在TIME_WAIT状态因为ACK不会重传防止没有LAST_ACK或LAST_ACK丢失对端没有收到LAST_ACK而一直重发FIN包。一直重发已经不存在的socket可能会对新建立的连接造成干扰。四、skynet 网络封装支持半关闭状态skynet 网络层支持半关闭状态。skynet 采用 reactor 网络编程模型reactor 网络模型是一种异步事件模型。通过epoll_ctl设置struct epoll_event中data.ptr (struct socket *)ud;来完成 fd 与 actor绑定。skynet 通过socket.start(fd, func)来完成 actor 与 fd 的绑定。4.1、连接的建立客户端与服务端建立连接处理建立成功的标识是 listenfd有读事件触发。服务端与第三方服务建立连接建立成功的标识是 connect 返回的fd有可写事件触发。4.2、连接断开skynet 网络层支持半关闭状态。1读写端都关闭epoll的事件EPOLLHUP 读写段都关闭(socket_epoll.h)展开代码语言C自动换行AI代码解释static int sp_wait(int efd, struct event *e, int max) { struct epoll_event ev[max]; int n epoll_wait(efd , ev, max, -1); int i; for (i0;in;i) { e[i].s ev[i].data.ptr; unsigned flag ev[i].events; e[i].write (flag EPOLLOUT) ! 0; e[i].read (flag EPOLLIN) ! 0; e[i].error (flag EPOLLERR) ! 0; e[i].eof (flag EPOLLHUP) ! 0; } return n; }(socket_server.c)展开代码语言C自动换行AI代码解释// 处理直接关闭并向 actor 返回事件 SOCKET_CLOSE if (e-eof) { // For epoll (at least), FIN packets are exchanged both ways. // See: https://stackoverflow.com/questions/52976152/tcp-when-is-epollhup-generated int halfclose halfclose_read(s); force_close(ss, s, l, result); // 如果前面因为关闭读端已经发送SOCKET_CLOSE在这里避免重复 SOCKET_CLOSE if (!halfclose) { return SOCKET_CLOSE; } }2检测读端关闭(socket_server.c)展开代码语言C自动换行AI代码解释// return -1 (ignore) when error static int forward_message_tcp(struct socket_server *ss, struct socket *s, struct socket_lock *l, struct socket_message * result) { int sz s-p.size; char * buffer MALLOC(sz); int n (int)read(s-fd, buffer, sz); if (n0) { FREE(buffer); switch(errno) { case EINTR: case AGAIN_WOULDBLOCK: break; default: return report_error(s, result, strerror(errno)); } return -1; } if (n0) { FREE(buffer); if (s-closing) { // 如果该连接的 socket 已经关闭 // Rare case : if s-closing is true, reading event is disable, and SOCKET_CLOSE is raised. if (nomore_sending_data(s)) { force_close(ss,s,l,result); } return -1; } int t ATOM_LOAD(s-type); if (t SOCKET_TYPE_HALFCLOSE_READ) { // 如果已经处理过读端关闭 // Rare case : Already shutdown read. return -1; } if (t SOCKET_TYPE_HALFCLOSE_WRITE) { // 如果之前已经处理过写端关闭则直接 close // Remote shutdown read (write error) before. force_close(ss,s,l,result); } else { close_read(ss, s, result); } return SOCKET_CLOSE; } if (halfclose_read(s)) { // discard recv data (Rare case : if socket is HALFCLOSE_READ, reading event is disable.) FREE(buffer); return -1; } stat_read(ss,s,n); result-opaque s-opaque; result-id s-id; result-ud n; result-data buffer; if (n sz) { s-p.size * 2; return SOCKET_MORE; } else if (sz MIN_READ_BUFFER n*2 sz) { s-p.size / 2; } return SOCKET_DATA; }3检测写端关闭write 0 errno EPIPE能检测对端读端关闭。(socket_server.c)展开代码语言C自动换行AI代码解释static int send_list_tcp(struct socket_server *ss, struct socket *s, struct wb_list *list, struct socket_lock *l, struct socket_message *result) { while (list-head) { struct write_buffer * tmp list-head; for (;;) { ssize_t sz write(s-fd, tmp-ptr, tmp-sz); if (sz 0) { switch(errno) { case EINTR: continue; case AGAIN_WOULDBLOCK: return -1; } return close_write(ss, s, l, result); } stat_write(ss,s,(int)sz); s-wb_size - sz; if (sz ! tmp-sz) { tmp-ptr sz; tmp-sz - sz; return -1; } break; } list-head tmp-next; write_buffer_free(ss,tmp); } list-tail NULL; return -1; }4.3、消息到达单线程读。读策略代码语言C自动换行AI代码解释int n read(fd, buf, sz);sz 初始值为 64根据从网络接收数据情况动态调整 sz 的大小。(socket_server.c)展开代码语言C自动换行AI代码解释// return -1 (ignore) when error static int forward_message_tcp(struct socket_server *ss, struct socket *s, struct socket_lock *l, struct socket_message * result) { int sz s-p.size; char * buffer MALLOC(sz); int n (int)read(s-fd, buffer, sz); if (n0) { FREE(buffer); switch(errno) { case EINTR: case AGAIN_WOULDBLOCK: break; default: return report_error(s, result, strerror(errno)); } return -1; } if (n0) { FREE(buffer); if (s-closing) { // Rare case : if s-closing is true, reading event is disable, and SOCKET_CLOSE is raised. if (nomore_sending_data(s)) { force_close(ss,s,l,result); } return -1; } int t ATOM_LOAD(s-type); if (t SOCKET_TYPE_HALFCLOSE_READ) { // Rare case : Already shutdown read. return -1; } if (t SOCKET_TYPE_HALFCLOSE_WRITE) { // Remote shutdown read (write error) before. force_close(ss,s,l,result); } else { close_read(ss, s, result); } return SOCKET_CLOSE; } if (halfclose_read(s)) { // discard recv data (Rare case : if socket is HALFCLOSE_READ, reading event is disable.) FREE(buffer); return -1; } stat_read(ss,s,n); result-opaque s-opaque; result-id s-id; result-ud n; result-data buffer; if (n sz) { s-p.size * 2; return SOCKET_MORE; } else if (sz MIN_READ_BUFFER n*2 sz) { s-p.size / 2; } return SOCKET_DATA; }4.4、消息发送完毕多线程写。同一个 fd 可以在不同的 actor 中发送数据。skynet底层通过加锁确保数据正确发送到 socket 的写缓冲区。展开代码语言C自动换行AI代码解释int socket_server_send(struct socket_server *ss, struct socket_sendbuffer *buf) { int id buf-id; struct socket * s ss-slot[HASH_ID(id)]; if (socket_invalid(s, id) || s-closing) { free_buffer(ss, buf); return -1; } struct socket_lock l; socket_lock_init(s, l); // 确保能在fd上写数据连接可用状态检测 socket 线程是否在对该fd操作 if (can_direct_write(s,id) socket_trylock(l)) { // may be we can send directly, double check if (can_direct_write(s,id)) { // 双检测 // send directly struct send_object so; send_object_init_from_sendbuffer(ss, so, buf); ssize_t n; if (s-protocol PROTOCOL_TCP) { // 尝试在 work 线程直接写如果n 0则写成功 n write(s-fd, so.buffer, so.sz); } else { union sockaddr_all sa; socklen_t sasz udp_socket_address(s, s-p.udp_address, sa); if (sasz 0) { skynet_error(NULL, socket-server : set udp (%d) address first., id); socket_unlock(l); so.free_func((void *)buf-buffer); return -1; } n sendto(s-fd, so.buffer, so.sz, 0, sa.s, sasz); } if (n0) { // ignore error, let socket thread try again // 如果失败不要在 work 线程中处理异常所有网络异常在 socket 线程中处理 n 0; } stat_write(ss,s,n); if (n so.sz) { // write done socket_unlock(l); so.free_func((void *)buf-buffer); return 0; } // write failed, put buffer into s-dw_* , and let socket thread send it. see send_buffer() s-dw_buffer clone_buffer(buf, s-dw_size); s-dw_offset n; socket_unlock(l); struct request_package request; request.u.send.id id; request.u.send.sz 0; request.u.send.buffer NULL; // 如果写失败可能写缓冲区满或被中断打断直接交由 socket 线程去重试。 // 这里通过 pipe 来与 socket 线程通信。 // let socket thread enable write event send_request(ss, request, W, sizeof(request.u.send)); return 0; } socket_unlock(l); } inc_sending_ref(s, id); struct request_package request; request.u.send.id id; request.u.send.buffer clone_buffer(buf, request.u.send.sz); send_request(ss, request, D, sizeof(request.u.send)); return 0; }注意在 work 线程中调用的。如果work线程写失败可能写缓冲区满或被中断打断直接交由 socket 线程去重试。这里通过 pipe 来与 socket 线程通信。五、测试skynet对半关闭的支持main.lua的代码实现展开代码语言Lua自动换行AI代码解释local skynet require skynet local socket require skynet.socket skynet.start(function () local listenfd socket.listen(0.0.0.0, 8888) socket.start(listenfd, function (clientfd, addr) print(receive a client:, clientfd, addr) socket.start(clientfd) while true do local data socket.readline(clientfd, \n) if not data then -- read 0 -- for i1,10 do socket.write(clientfd, FINAL\n) -- end socket.close(clientfd) print(closed, clientfd) return end if data quit then print(recv quit, clientfd) socket.close(clientfd) return else print(S recv:, data) socket.write(clientfd, ..data..\n) end end end) end)client.lua的代码实现展开代码语言Lua自动换行AI代码解释package.cpath ./skynet/luaclib/?.so package.path ./skynet/lualib/?.lua;./?.lua if _VERSION ~ Lua 5.4 then error Use lua 5.4 end local socket require client.socket local fd assert(socket.connect(127.0.0.1, 8888)) local function send_package(pack) socket.send(fd, pack .. \n) end local function unpack_package(text) local size #text if size 1 then return nil, text end local pos text:find(\n, 1, true) if not pos then return nil, text end return text:sub(1,pos-1), text:sub(pos1) end local function recv_package(last) local result result, last unpack_package(last) if result then return result, last end local r socket.recv(fd) if not r then return nil, last end if r then error Server closed end return unpack_package(last .. r) end local last local function dispatch_package() while true do local v v, last recv_package(last) if not v then break end print(v) end end while true do dispatch_package() local cmd socket.readstdin() if cmd then if cmd quit then send_package(quit) elseif cmd shut_r then -- send_package(shut_r) socket.shutdown(fd, r) -- send_package(shut_r) elseif cmd shut_w then -- send_package(shut_w) send_package(shut_w1) send_package(shut_w2) send_package(shut_w3) send_package(shut_w4) socket.shutdown(fd, w) else send_package(cmd) end else socket.usleep(100) end endconfig设置展开代码语言Lua自动换行AI代码解释thread4 loggernil harbor0 startmain -- 启动第一个服务 lua_path./skynet/lualib/?.lua;.../skynet/lualib/?/init.lua;.../lualib/?.lua; luaservice./skynet/service/?.lua;./app/?.lua lualoader./skynet/lualib/loader.lua cpath./skynet/cservice/?.so lua_cpath./skynet/luaclib/?.soMakefile展开代码语言Bash自动换行AI代码解释SKYNET_PATH?./skynet all: cd $(SKYNET_PATH) $(MAKE) PLATlinux clean: cd $(SKYNET_PATH) $(MAKE) clean项目结构展开代码语言Bash自动换行AI代码解释-- mytest ---- skynet ----| app -------- main.lua -------- client.lua ---- config ---- Makefile编译和运行代码语言Bash自动换行AI代码解释make ./skynet/skynet config启动client.lua的方式代码语言Bash自动换行AI代码解释./skynet/3rd/lua/lua ./app/client.lua5.1、测试直接关闭进程启动client.lua然后CTLC直接关闭进程。代码语言Bash自动换行AI代码解释./skynet/3rd/lua/lua ./app/client.lua此时内核协议栈会收到一个FIN包内核协议栈的读缓冲区插入EOF并触发读事件。skynet开始读数据读到EOF即read()0开始关闭读事件和读端。然后进入main.lua的while循环尝试发送消息但是消息是无法发送出去了因为客户端进程已经关闭资源已经被释放协议栈会把其丢弃。注意读数据、写数据、业务逻辑都在线程池执行socket网络线程只负责监听触发事件socket网络线程和线程池通过pipeline消息交互epoll可以监听这个消息然后注册读、写事件。5.2、测试关闭读端启动client.lua输入shut_r命令。代码语言Bash自动换行AI代码解释$ ./skynet/3rd/lua/lua ./app/client.lua shut_r关闭读端会走RST流程立即进入CLOSE状态服务端会报出“Connection reset by peer”错误。服务端关闭写端但仍然能接受到客户端发送的数据。接受完数据后才调用关闭读端结束连接。代码语言Bash自动换行AI代码解释receive a client: 4 127.0.0.1:35384 S recv: shut_r S recv: shut_r2 closed 4 [:00000008] socket: error on 4 Connection reset by peer5.2、测试关闭写端启动client.lua输入shut_w命令。代码语言Bash自动换行AI代码解释$ ./skynet/3rd/lua/lua ./app/client.lua shut_w关闭写端服务器会关闭读端。在完全关闭连接之前服务器还可以发送数据到客户端。server端代码语言Bash自动换行AI代码解释receive a client: 5 127.0.0.1:35486 S recv: shut_w1 S recv: shut_w2 S recv: shut_w3 S recv: shut_w4 closed 5client端展开代码语言Bash自动换行AI代码解释shut_w SHUTDOWN 3 1 shut_w1 shut_w2 shut_w3 shut_w4 FINAL六、总结ACK包是不会重发的如果ACK包丢失了那么发送端在一定时间内接受不到数据就会重发FIN包一般三次FIN包都没有收到ACK就会直接进入close。