void net_init(void) { WSADATA wsaData; if(0!=WSAStartup(MAKEWORD(2,2), &wsaData)) abErrorFatal("couldn't startup winsock: %s\n",last_error_str()); g_netctxt.vers = NET_VERSION; }
NetServer* netserver_create(NetCtxt *ctxt, U16 port, LinkConnFp conn_callback, LinkDiscoFp disco_callback, MessageFp msg_callback) { struct sockaddr_in service = {0}; // bind NetServer *s = NetServer_Create(ctxt); s->port = port; s->listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); s->conn_callback = conn_callback; s->disco_callback = disco_callback; s->msg_callback = msg_callback; if(s->listen_sock == INVALID_SOCKET) { printf("error creating socket %s\n", last_error_str()); goto error; } if(!sock_set_noblocking(s->listen_sock)) { printf("error setting listen socket to async: %s\n", last_error_str()); goto error; } service.sin_family = AF_INET; service.sin_addr.s_addr = inet_addr("127.0.0.1"); service.sin_port = htons(port); if(bind( s->listen_sock, (SOCKADDR*)&service,sizeof(service)) == SOCKET_ERROR) { printf("bind failed %s\n",last_error_str()); goto error; } SOCK_CLEAR_DELAY(s->listen_sock); if(listen(s->listen_sock,1) == SOCKET_ERROR) { printf("listen failed %s\n",last_error_str()); goto error; } return s; error: NetServer_Destroy(&s); return NULL; }
inline void validate_io(BOOL expression, const char* failMessage) { if(!expression) { DWORD lastError = ::GetLastError(); if(ERROR_IO_PENDING != lastError) { std::string errMsg(failMessage); errMsg.append(" "); errMsg.append(last_error_str(lastError)); boost::throw_exception(io_exception(errMsg)); } } }
//---------------------------------------- // helper for raw sockets that are // nonblocking, closes the passed socket // on error // TODO: optimize for win32 specific, e.g. async accept //---------------------------------------- SOCKET socket_accept(SOCKET *hlisten_sock, struct sockaddr *client_address) { SOCKET sock; int addr_len = sizeof(*client_address); if(!hlisten_sock || *hlisten_sock == INVALID_SOCKET) return INVALID_SOCKET; sock = accept(*hlisten_sock, client_address, client_address?&addr_len:NULL); if(sock < 0) { if(last_error() != EWOULDBLOCK) { LOG("accept failed on socket %i: %s\n",*hlisten_sock,last_error_str()); closesocket(*hlisten_sock); } return *hlisten_sock = INVALID_SOCKET; } SOCK_CLEAR_DELAY(sock); return sock; }
//---------------------------------------- // connect : TCP/UDP layer // handshake : // - once connected, send handshake // - wait for handshake response // - process (fail: close) //---------------------------------------- BOOL netlink_tick(NetLink *c) { MessageFp msg_callback; int i; char *hdr; int n; if(!c || c->sock == INVALID_SOCKET) return FALSE; if(!c->connected) { if(c->s) // server side sends handshake { LinkHandshake hs = {0}; // remember to add sends below assert_infunc_static(sizeof(hs) == 8); hs.vers = htonl(c->ctxt->vers); hs.flags = htonl(0); // todo n = 0; if(n>=0) n = send(c->sock, (char*)&hs.vers, sizeof(hs.vers), 0); if(n>=0) n = send(c->sock, (char*)&hs.flags, sizeof(hs.flags), 0); if(n < 0) { if(last_error() == ENOTCONN) // okay, still connecting return TRUE; else { LOG("error handshaking client %s",last_error_str()); goto netlinktick_error; } } c->connected = TRUE; } else { if(sock_canrecv(c->sock,0)) c->connected = TRUE; } } if(!c->handshaken) { if(!sock_recv(c->sock, &c->recv_frame, &c->closed)) { ERR("error in link %s:%p recv during handshake %s", linkip(c),c,last_error_str()); goto netlinktick_error; } if(c->s) // server-side handling of handshake response { U32 vers_res; if(achr_size(&c->recv_frame) < sizeof(vers_res)) return TRUE; achr_mv(&vers_res,&c->recv_frame,sizeof(vers_res)); vers_res = ntohl(vers_res); if(vers_res != c->ctxt->vers) { ERR("%s:%p link handshake responded with wrong version %u should be %u",linkip(c),c,vers_res,c->ctxt->vers); goto netlinktick_error; } } else { U32 tmp; LinkHandshake lh = {0}; if(achr_size(&c->recv_frame) < sizeof(lh)) return TRUE; assert_infunc_static(sizeof(lh) == 8); achr_mv(&lh.vers,&c->recv_frame,sizeof(lh.vers)); lh.vers = ntohl(lh.vers); achr_mv(&lh.flags,&c->recv_frame,sizeof(lh.flags)); lh.flags = ntohl(lh.flags); if(lh.vers != c->ctxt->vers) { ERR("%s:%p version mismatch between client(%c) and server(%c)",linkip(c),c,c->ctxt->vers,lh.vers); goto netlinktick_error; } tmp = htonl(c->ctxt->vers); n = send(c->sock,(char*)&tmp,sizeof(c->ctxt->vers),0); if(n < 0) { ERR("%s:%p couldn't send version response",linkip(c),c); goto netlinktick_error; } } c->handshaken = TRUE; if(c->conn_callback) c->conn_callback(c); else if(SAFE_MEMBER(c->s,conn_callback)) c->s->conn_callback(c); } // ---------------------------------------- // recv and turn into packets if(!sock_recv(c->sock, &c->recv_frame, &c->closed)) { ERR("%s:%p recv failed %s",linkip(c),c,last_error_str()); goto netlinktick_error; } net_process_frame(c); // ---------------------------------------- // dispatch n = apak_size(&c->recvs); if(n) { msg_callback = c->msg_callback; if(!msg_callback && c->s) msg_callback = c->s->msg_callback; if(!msg_callback) { ERR("%s:%p no msg handler for link",linkip(c),c); goto netlinktick_error; } for(i = 0;i < n; ++i) { Pak *m = c->recvs[i]; if(!msg_callback(c,m,m->hdr.msgid)) continue; Pak_Destroy(&m); apak_rm(&c->recvs,i,1); } } n = apak_size(&c->sends); if(n) { hdr = achr_create(sizeof(PakHdr)); for( i = 0; i < n; ++i ) { U32 tmp; Pak *p = c->sends[i]; // @todo -AB: encrypted, etc. :09/26/08 achr_setsize(&hdr,0); tmp = htonl(p->hdr.msgid); achr_append(&hdr,(char*)&tmp,sizeof(tmp)); tmp = htonl(p->hdr.id); achr_append(&hdr,(char*)&tmp,sizeof(tmp)); tmp = htonl(achr_size(&p->body)); achr_append(&hdr,(char*)&tmp,sizeof(tmp)); if(!sock_send(c->sock,&hdr,&p->body)) { if(last_error() == EMSGSIZE) // out of room to send. try again later { ERR("%s:%p send buffer full for socket: %s. waiting one tick",linkip(c),c,last_error_str()); break; } ERR("%s:%p failed to send on socket: %s",linkip(c),c,last_error_str()); net_shutdownlink(c); Pak_Destroy(&p); break; } Pak_Destroy(&p); } achr_destroy(&hdr); apak_rm(&c->sends,0,i); // cleanup happens above } // ---------- // cleanup // what are we doing here? The point is to allow a graceful // cleanup. At some point one side or the other is supposed // to initiate termination by calling 'shutdown'. there are // several ways this happens: // 1) link recvs 0 bytes : closed. all recvd packets are processed. sends are allowed. when all recv'd paks handled, shutdown is called // 2) link calls shutdown: no sends allowed, server should respond with FIN // e.g. // A: shutdown(send). sends FIN to B // B: recv returns 0. sets 'closed' flag. if no msgs to send // will call shutdown itself and close the socket. // A: recv returns 0. closesocket if(apak_size(&c->sends) == 0 && c->shutdown_req && !c->shutdown) { shutdown(c->sock,SD_SEND); c->shutdown = TRUE; } if(!c->closed) // @todo -AB: timeout code :09/25/08 return TRUE; else if(apak_size(&c->recvs)) return TRUE; if(!c->shutdown) { shutdown(c->sock,SD_SEND); c->shutdown = TRUE; } // if this link was made visibile above this layer if(c->handshaken) { if(c->disco_callback) c->disco_callback(c); else if(SAFE_MEMBER(c->s,disco_callback)) c->s->disco_callback(c); } return TRUE; netlinktick_error: closesocket(c->sock); c->sock = INVALID_SOCKET; return FALSE; }