int main (int argc, char ** argv) { prev = getthetime(); (void) signal(SIGINT,leave); if (enet_initialize () != 0) { fprintf (stderr, "An error occurred while initializing ENet.\n"); return EXIT_FAILURE; } atexit (enet_deinitialize); address.host = ENET_HOST_ANY; address.port = 5001; enetserver = enet_host_create (& address /* the address to bind the server host to */, 32 /* allow up to 32 clients and/or outgoing connections */, 0 /* allow up to 2 channels to be used, 0 and 1 */, 0 /* assume any amount of incoming bandwidth */, 0 /* assume any amount of outgoing bandwidth */); if (enetserver== NULL) { fprintf (stderr, "An error occurred while trying to create an ENet server host.\n"); exit (EXIT_FAILURE); } tronserver = server_init(enetserver); atexit(clean_up); printf("This is the TronClone Server.\nPush ^c to kill the server.\n"); /* Main game loop */ while(1) { int i, index, temp; char buf[80]; if (enet_host_service (enetserver, & event, 10) > 0) { switch (event.type) { case ENET_EVENT_TYPE_CONNECT: enet_address_get_host_ip(&event.peer->address, buf, sizeof buf); printf ("A new client connected from %s\n", buf); server_add_user(tronserver, event.peer); break; case ENET_EVENT_TYPE_RECEIVE: server_process_packet(tronserver, event); enet_packet_destroy (event.packet); break; case ENET_EVENT_TYPE_DISCONNECT: enet_address_get_host_ip(&event.peer->address, buf, sizeof buf); printf ("A client disconnected from %s: %u.\n", buf, event.peer -> address.port); server_remove_user(tronserver, event.peer); event.peer -> data = NULL; break; } } now = getthetime(); double h = (now-prev)/1000000.0f; server_update(tronserver, h); prev = now; } clean_up(); }
/* \brief update enet state internally */ static int _nethckEnetUpdate(void) { __NETHCKclient client; ENetEvent event; unsigned int packets = 0; TRACE(2); /* wait up to 1000 milliseconds for an event */ while (enet_host_service(_NETHCKserver.enet, &event, 1000) > 0) { switch (event.type) { case ENET_EVENT_TYPE_CONNECT: printf("A new client connected from %x:%u.\n", event.peer->address.host, event.peer->address.port); /* fill new client struct */ memset(&client, 0, sizeof(__NETHCKclient)); enet_address_get_host_ip(&event.peer->address, client.host, sizeof(client.host)); event.peer->data = _nethckServerNewClient(&client); break; case ENET_EVENT_TYPE_RECEIVE: /* manage packet by kind */ printf("A packet of length %zu was received on channel %u.\n", event.packet->dataLength, event.channelID); printf("ID: %d\n", ((nethckPacket*)event.packet->data)->type); switch (((nethckPacket*)event.packet->data)->type) { case NETHCK_PACKET_OBJECT: _nethckServerManagePacketObject(event.packet->data); break; case NETHCK_PACKET_OBJECT_TRANSLATION: case NETHCK_PACKET_OBJECT_MATERIAL: case NETHCK_PACKET_OBJECT_TEXTURE: default: break; } /* echo the packet to clients */ enet_host_broadcast(_NETHCKserver.enet, 0, event.packet); /* clean up the packet now that we're done using it. */ //enet_packet_destroy(event.packet); break; case ENET_EVENT_TYPE_DISCONNECT: printf("%s disconected.\n", ((__NETHCKclient*)event.peer->data)->host); /* free the client */ _nethckServerFreeClient(event.peer->data); event.peer->data = NULL; break; default: break; } ++packets; } RET(2, "%u", packets); return packets; }
bool CNetServerWorker::RunStep() { // Check for messages from the game thread. // (Do as little work as possible while the mutex is held open, // to avoid performance problems and deadlocks.) m_ScriptInterface->GetRuntime()->MaybeIncrementalGC(0.5f); JSContext* cx = m_ScriptInterface->GetContext(); JSAutoRequest rq(cx); std::vector<std::pair<int, CStr> > newAssignPlayer; std::vector<bool> newStartGame; std::vector<std::pair<CStr, int> > newPlayerReady; std::vector<bool> newPlayerResetReady; std::vector<std::string> newGameAttributes; std::vector<u32> newTurnLength; { CScopeLock lock(m_WorkerMutex); if (m_Shutdown) return false; newStartGame.swap(m_StartGameQueue); newPlayerReady.swap(m_PlayerReadyQueue); newPlayerResetReady.swap(m_PlayerResetReadyQueue); newAssignPlayer.swap(m_AssignPlayerQueue); newGameAttributes.swap(m_GameAttributesQueue); newTurnLength.swap(m_TurnLengthQueue); } for (size_t i = 0; i < newAssignPlayer.size(); ++i) AssignPlayer(newAssignPlayer[i].first, newAssignPlayer[i].second); for (size_t i = 0; i < newPlayerReady.size(); ++i) SetPlayerReady(newPlayerReady[i].first, newPlayerReady[i].second); if (!newPlayerResetReady.empty()) ClearAllPlayerReady(); if (!newGameAttributes.empty()) { JS::RootedValue gameAttributesVal(cx); GetScriptInterface().ParseJSON(newGameAttributes.back(), &gameAttributesVal); UpdateGameAttributes(&gameAttributesVal); } if (!newTurnLength.empty()) SetTurnLength(newTurnLength.back()); // Do StartGame last, so we have the most up-to-date game attributes when we start if (!newStartGame.empty()) StartGame(); // Perform file transfers for (size_t i = 0; i < m_Sessions.size(); ++i) m_Sessions[i]->GetFileTransferer().Poll(); // Process network events: ENetEvent event; int status = enet_host_service(m_Host, &event, HOST_SERVICE_TIMEOUT); if (status < 0) { LOGERROR("CNetServerWorker: enet_host_service failed (%d)", status); // TODO: notify game that the server has shut down return false; } if (status == 0) { // Reached timeout with no events - try again return true; } // Process the event: switch (event.type) { case ENET_EVENT_TYPE_CONNECT: { // Report the client address char hostname[256] = "(error)"; enet_address_get_host_ip(&event.peer->address, hostname, ARRAY_SIZE(hostname)); LOGMESSAGE("Net server: Received connection from %s:%u", hostname, (unsigned int)event.peer->address.port); // Set up a session object for this peer CNetServerSession* session = new CNetServerSession(*this, event.peer); m_Sessions.push_back(session); SetupSession(session); ENSURE(event.peer->data == NULL); event.peer->data = session; HandleConnect(session); break; } case ENET_EVENT_TYPE_DISCONNECT: { // If there is an active session with this peer, then reset and delete it CNetServerSession* session = static_cast<CNetServerSession*>(event.peer->data); if (session) { LOGMESSAGE("Net server: Disconnected %s", DebugName(session).c_str()); // Remove the session first, so we won't send player-update messages to it // when updating the FSM m_Sessions.erase(remove(m_Sessions.begin(), m_Sessions.end(), session), m_Sessions.end()); session->Update((uint)NMT_CONNECTION_LOST, NULL); delete session; event.peer->data = NULL; } break; } case ENET_EVENT_TYPE_RECEIVE: { // If there is an active session with this peer, then process the message CNetServerSession* session = static_cast<CNetServerSession*>(event.peer->data); if (session) { // Create message from raw data CNetMessage* msg = CNetMessageFactory::CreateMessage(event.packet->data, event.packet->dataLength, GetScriptInterface()); if (msg) { LOGMESSAGE("Net server: Received message %s of size %lu from %s", msg->ToString().c_str(), (unsigned long)msg->GetSerializedLength(), DebugName(session).c_str()); HandleMessageReceive(msg, session); delete msg; } } // Done using the packet enet_packet_destroy(event.packet); break; } case ENET_EVENT_TYPE_NONE: break; } return true; }
{ return curpeer || (attempt && connpeer); } ICOMMAND(isconnected, "i", (int *attempt), intret(isconnected(*attempt > 0) ? 1 : 0)); const ENetAddress *connectedpeer() { return curpeer ? &curpeer->address : NULL; } ICOMMAND(connectedip, "", (), { const ENetAddress *address = connectedpeer(); string hostname; result(address && enet_address_get_host_ip(address, hostname, sizeof(hostname)) >= 0 ? hostname : ""); }); ICOMMAND(connectedport, "", (), { const ENetAddress *address = connectedpeer(); intret(address ? address->port : -1); }); void abortconnect() { if(!connpeer) return; game::connectfail(); if(connpeer->state!=ENET_PEER_STATE_DISCONNECTED) enet_peer_reset(connpeer); connpeer = NULL; if(curpeer) return;
VALUE renet_server_update(VALUE self, VALUE timeout) { Server* server; Data_Get_Struct(self, Server, server); VALUE lock = rb_iv_get(self, "@lock"); rb_mutex_lock(lock); int peer_id; /* wait up to timeout milliseconds for a packet */ if (service(self, server, NUM2UINT(timeout)) > 0) { do { switch (server->event->type) { case ENET_EVENT_TYPE_NONE: break; case ENET_EVENT_TYPE_CONNECT: server->n_clients += 1; enet_address_get_host_ip(&(server->event->peer->address), server->conn_ip, 20); peer_id = (int)(server->event->peer - server->host->peers); renet_server_execute_on_connection(self, INT2NUM(peer_id), rb_str_new2(server->conn_ip)); break; case ENET_EVENT_TYPE_RECEIVE: peer_id = (int)(server->event->peer - server->host->peers); renet_server_execute_on_packet_receive(self, INT2NUM(peer_id), server->event->packet, server->event->channelID); break; case ENET_EVENT_TYPE_DISCONNECT: server->n_clients -= 1; peer_id = (int)(server->event->peer - server->host->peers); server->event->peer->data = NULL; renet_server_execute_on_disconnection(self, INT2NUM(peer_id)); break; } } while (service(self, server, 0) > 0); } /* we are unlocking now because it's important to unlock before going back into ruby land (which rb_funcall will do). If we don't then an exception can leave the locks in an inconsistent state */ rb_mutex_unlock(lock); { VALUE total = rb_iv_get(self, "@total_sent_data"); VALUE result = rb_funcall( total , rb_intern("+") , 1 , UINT2NUM(server->host->totalSentData)); rb_iv_set(self, "@total_sent_data", result); server->host->totalSentData = 0; } { VALUE total = rb_iv_get(self, "@total_received_data"); VALUE result = rb_funcall( total , rb_intern("+") , 1 , UINT2NUM(server->host->totalReceivedData)); rb_iv_set(self, "@total_received_data", result); server->host->totalReceivedData = 0; } { VALUE total = rb_iv_get(self, "@total_sent_packets"); VALUE result = rb_funcall( total , rb_intern("+") , 1 , UINT2NUM(server->host->totalSentPackets)); rb_iv_set(self, "@total_sent_packets", result); server->host->totalSentPackets = 0; } { VALUE total = rb_iv_get(self, "@total_received_packets"); VALUE result = rb_funcall( total , rb_intern("+") , 1 , UINT2NUM(server->host->totalReceivedPackets)); rb_iv_set(self, "@total_received_packets", result); server->host->totalReceivedPackets = 0; } return Qtrue; }
void Room::RoomImpl::HandleModBanPacket(const ENetEvent* event) { if (!HasModPermission(event->peer)) { SendModPermissionDenied(event->peer); return; } Packet packet; packet.Append(event->packet->data, event->packet->dataLength); packet.IgnoreBytes(sizeof(u8)); // Ignore the message type std::string nickname; packet >> nickname; std::string username; std::string ip; { std::lock_guard lock(member_mutex); const auto target_member = std::find_if(members.begin(), members.end(), [&nickname](const auto& member) { return member.nickname == nickname; }); if (target_member == members.end()) { SendModNoSuchUser(event->peer); return; } // Notify the banned member SendUserBanned(target_member->peer); nickname = target_member->nickname; username = target_member->user_data.username; char ip_raw[256]; enet_address_get_host_ip(&target_member->peer->address, ip_raw, sizeof(ip_raw) - 1); ip = ip_raw; enet_peer_disconnect(target_member->peer, 0); members.erase(target_member); } { std::lock_guard lock(ban_list_mutex); if (!username.empty()) { // Ban the forum username if (std::find(username_ban_list.begin(), username_ban_list.end(), username) == username_ban_list.end()) { username_ban_list.emplace_back(username); } } // Ban the member's IP as well if (std::find(ip_ban_list.begin(), ip_ban_list.end(), ip) == ip_ban_list.end()) { ip_ban_list.emplace_back(ip); } } // Announce the change to all clients. SendStatusMessage(IdMemberBanned, nickname, username); BroadcastRoomInformation(); }
void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) { { std::lock_guard lock(member_mutex); if (members.size() >= room_information.member_slots) { SendRoomIsFull(event->peer); return; } } Packet packet; packet.Append(event->packet->data, event->packet->dataLength); packet.IgnoreBytes(sizeof(u8)); // Ignore the message type std::string nickname; packet >> nickname; std::string console_id_hash; packet >> console_id_hash; MacAddress preferred_mac; packet >> preferred_mac; u32 client_version; packet >> client_version; std::string pass; packet >> pass; std::string token; packet >> token; if (pass != password) { SendWrongPassword(event->peer); return; } if (!IsValidNickname(nickname)) { SendNameCollision(event->peer); return; } if (preferred_mac != NoPreferredMac) { // Verify if the preferred mac is available if (!IsValidMacAddress(preferred_mac)) { SendMacCollision(event->peer); return; } } else { // Assign a MAC address of this client automatically preferred_mac = GenerateMacAddress(); } if (!IsValidConsoleId(console_id_hash)) { SendConsoleIdCollision(event->peer); return; } if (client_version != network_version) { SendVersionMismatch(event->peer); return; } // At this point the client is ready to be added to the room. Member member{}; member.mac_address = preferred_mac; member.console_id_hash = console_id_hash; member.nickname = nickname; member.peer = event->peer; std::string uid; { std::lock_guard lock(verify_UID_mutex); uid = verify_UID; } member.user_data = verify_backend->LoadUserData(uid, token); { std::lock_guard lock(ban_list_mutex); // Check username ban if (!member.user_data.username.empty() && std::find(username_ban_list.begin(), username_ban_list.end(), member.user_data.username) != username_ban_list.end()) { SendUserBanned(event->peer); return; } // Check IP ban char ip_raw[256]; enet_address_get_host_ip(&event->peer->address, ip_raw, sizeof(ip_raw) - 1); std::string ip = ip_raw; if (std::find(ip_ban_list.begin(), ip_ban_list.end(), ip) != ip_ban_list.end()) { SendUserBanned(event->peer); return; } } // Notify everyone that the user has joined. SendStatusMessage(IdMemberJoin, member.nickname, member.user_data.username); { std::lock_guard lock(member_mutex); members.push_back(std::move(member)); } // Notify everyone that the room information has changed. BroadcastRoomInformation(); if (HasModPermission(event->peer)) { SendJoinSuccessAsMod(event->peer, preferred_mac); } else { SendJoinSuccess(event->peer, preferred_mac); } }
void NetServer::ReadDriverInfoPacket(ENetPacket *pPacket, ENetPeer * pPeer) { NetDriver driver; char hostName[256]; enet_address_get_host_ip (&driver.address,hostName,256); GfLogTrace ("Client Player Info connected from %s\n",hostName); PackedBuffer msg(pPacket->data, pPacket->dataLength); GfLogTrace("ReadDriverInfoPacket: packed data length=%d\n", msg.length()); try { msg.unpack_ubyte(); driver.idx = msg.unpack_int(); msg.unpack_string(driver.name, sizeof driver.name); msg.unpack_string(driver.car, sizeof driver.car); msg.unpack_string(driver.team, sizeof driver.team); msg.unpack_string(driver.author, sizeof driver.author); driver.racenumber = msg.unpack_int(); msg.unpack_string(driver.skilllevel, sizeof driver.skilllevel); driver.red = msg.unpack_float(); driver.green = msg.unpack_float(); driver.blue = msg.unpack_float(); msg.unpack_string(driver.module, sizeof driver.module); msg.unpack_string(driver.type, sizeof driver.type); driver.client = msg.unpack_int(); } catch (PackedBufferException &e) { GfLogFatal("ReadDriverInfoPacket: packed buffer error\n"); } GfLogTrace("ReadDriverInfoPacket: driver\n"); GfLogTrace(".host=%d\n", driver.address.host); GfLogTrace(".port=%d\n", driver.address.port); GfLogTrace(".idx=%d\n", driver.idx); GfLogTrace(".name=%s\n", driver.name); GfLogTrace(".car=%s\n", driver.car); GfLogTrace(".team=%s\n", driver.team); GfLogTrace(".author=%s\n", driver.author); GfLogTrace(".racenumber=%d\n", driver.racenumber); GfLogTrace(".skilllevel=%s\n", driver.skilllevel); GfLogTrace(".red=%.1f\n", driver.red); GfLogTrace(".green=%.1f\n", driver.green); GfLogTrace(".blue=%.1f\n", driver.blue); GfLogTrace(".module=%s\n", driver.module); GfLogTrace(".type=%s\n", driver.type); GfLogTrace(".client=%d\n", driver.client); //Make sure player name is unique otherwise disconnect player NetServerMutexData *pSData = LockServerData(); for(unsigned int i=0;i<pSData->m_vecNetworkPlayers.size();i++) { if (strcmp(driver.name,pSData->m_vecNetworkPlayers[i].name)==0) { SendPlayerRejectedPacket(pPeer,"Player name already used. Please choose a different name."); UnlockServerData(); return; } } UnlockServerData(); driver.address.host = pPeer->address.host; driver.hostPort = pPeer->address.port; SendPlayerAcceptedPacket(pPeer); UpdateDriver(driver); GfLogTrace("Reading Driver Info Packet: Driver: %s,Car: %s\n",driver.name,driver.car); }
void NetServer::RemoveDriver(ENetEvent event) { int playerStartIndex; ENetAddress address = event.peer->address; char hostName[256]; enet_address_get_host_ip (&address,hostName,256); GfLogTrace ("Client Player Info disconnect from %s\n",hostName); std::vector<NetDriver>::iterator p; if (m_vecWaitForPlayers.size()>0) { p = m_vecWaitForPlayers.begin(); while(p!=m_vecWaitForPlayers.end()) { if ((p->address.host == address.host)&&(p->hostPort == address.port)) { m_vecWaitForPlayers.erase(p); break; } p++; } if (m_vecWaitForPlayers.size()==0) m_bBeginRace = true; } //look for driver id NetServerMutexData *pSData = LockServerData(); for (p = pSData->m_vecNetworkPlayers.begin();p!=pSData->m_vecNetworkPlayers.end();p++) { if (p->client) { if ((p->address.host == address.host)&&(p->hostPort == address.port)) { if(m_bRaceActive) { playerStartIndex = p->idx-1; pSData->m_vecNetworkPlayers.erase(p); RemovePlayerFromRace(playerStartIndex); GenerateDriversForXML(); RobotXml rXml; rXml.CreateRobotFile("networkhuman",pSData->m_vecNetworkPlayers); SetRaceInfoChanged(true); } else { pSData->m_vecNetworkPlayers.erase(p); GenerateDriversForXML(); RobotXml rXml; rXml.CreateRobotFile("networkhuman",pSData->m_vecNetworkPlayers); SetRaceInfoChanged(true); } UnlockServerData(); return; } } } UnlockServerData(); }
int main (int argc, char *argv[]) { ENetAddress address; ENetEvent event; ChatContext cc, *pcc = &cc; char host_ip[32] = {0}; char peer_info[32] = {0}; int lastEvent = -1; int enableDebug = 0; int trycount = 0; int ret; memset(pcc, 0, sizeof(*pcc)); /* Initialize the ENet */ if (enet_initialize() != 0) { fprintf(stderr, "An error (%s) occured while initializing ENet.\n", strerror(errno)); return EXIT_FAILURE; } atexit(enet_deinitialize); /* Create the client host */ pcc->client = enet_host_create(NULL, SHUTTLE_MAX_CLIENT_NUM, SHUTTLE_MAX_CHANNEL_NUM, SHUTTLE_MAX_INCOMING_BANDWIDTH, SHUTTLE_MAX_OUTGOING_BANDWIDTH); if (pcc->client == NULL) { fprintf(stderr, "An error (%s) occured while trying to create an ENet client host.\n", strerror(errno)); exit(EXIT_FAILURE); } /* Connect to the server */ enet_address_set_host(&address, SHUTTLE_SERVER_HOST); address.port = SHUTTLE_SERVER_PORT; pcc->peer = enet_host_connect(pcc->client, &address, SHUTTLE_MAX_CHANNEL_NUM, 0); if (pcc->peer == NULL) { fprintf(stderr, "No available peers for initializing an ENet connection.\n"); exit(EXIT_FAILURE); } do { trycount++; printf("(Peer) Try to connect to server: the %dth tryouts.\n", trycount); if (enet_host_service(pcc->client, &event, 1000) > 0 && event.type == ENET_EVENT_TYPE_CONNECT) { /* We can send packet to server only after we have received ENET_EVENT_TYPE_CONNECT */ pcc->connected = 1; enet_address_get_host_ip(&event.peer->address, host_ip, sizeof(host_ip) - 1); snprintf(peer_info, sizeof(peer_info), "[%s:%d]", host_ip, event.peer->address.port); if (event.peer->data) free(event.peer->data); event.peer->data = malloc(strlen(peer_info) + 1); if (event.peer->data) strcpy(event.peer->data, peer_info); printf("(Peer) Connected to server (%s:%d).\n", host_ip, event.peer->address.port); } } while (trycount < 4 && !pcc->connected); if (!pcc->connected) { fprintf(stderr, "Fail to connect to server.\n"); enet_peer_reset(pcc->peer); enet_host_destroy(pcc->client); exit(EXIT_FAILURE); } /* We do not block the main event dispatcher */ ret = pthread_create(&pcc->thread, NULL, peer_chater, pcc); if (ret) { pcc->thread = 0; fprintf(stderr, "Fail to create thread.\n"); goto cleanup_pos; } while (1) { /* Event dispatcher: MUST not be hanged up */ int eventStatus = enet_host_service(pcc->client, &event, 10); if (eventStatus >= 0) { switch (event.type) { case ENET_EVENT_TYPE_NONE: /* Silence huge repeated NONE events */ if (lastEvent != ENET_EVENT_TYPE_NONE) { if (enableDebug) printf("(Peer) No event.\n"); } break; case ENET_EVENT_TYPE_CONNECT: /* Store any relevant client information here. */ pcc->connected = 1; enet_address_get_host_ip(&event.peer->address, host_ip, sizeof(host_ip) - 1); snprintf(peer_info, sizeof(peer_info), "[%s:%d]", host_ip, event.peer->address.port); if (event.peer->data) free(event.peer->data); event.peer->data = malloc(strlen(peer_info)); if (event.peer->data) strcpy(event.peer->data, peer_info); printf("(Peer) Connected to server (%s:%d).\n", host_ip, event.peer->address.port); break; case ENET_EVENT_TYPE_RECEIVE: if (event.channelID == SHUTTLE_CHANNEL_NOTIFY) printf("(Peer) Got a notification message : %s.\n", (char*)event.packet->data); else printf("(Peer) Got a chat message: %s.\n", (char*)event.packet->data); /* Clean up the packet now that we're done using it. */ enet_packet_destroy(event.packet); break; case ENET_EVENT_TYPE_DISCONNECT: /* A connected peer has either explicitly disconnected or timed out. */ printf("(Peer) Connection status: %d.\n", pcc->connected); if (event.peer->data) { printf("(Peer) %s is disconnected.\n", (char*)event.peer->data); free(event.peer->data); } else { /* We fail to receive CONNECT event becasue the server is down. */ enet_address_get_host_ip(&event.peer->address, host_ip, sizeof(host_ip) - 1); snprintf(peer_info, sizeof(peer_info), "[%s:%d]", host_ip, event.peer->address.port); printf("(Peer) Unknown (%s) connection is disconnected.\n", peer_info); } /* Reset the peer's information. */ event.peer->data = NULL; pcc->connected = 0; lastEvent = -1; enet_peer_reset(event.peer); /* Reconnect the server */ pcc->peer = enet_host_connect(pcc->client, &address, SHUTTLE_MAX_CHANNEL_NUM, 0); if (pcc->peer == NULL) { fprintf(stderr, "No available peers for initializing an ENet connection.\n"); enet_host_destroy(pcc->client); ret = EXIT_FAILURE; goto cleanup_pos; } break; default: assert(0); break; } lastEvent = event.type; } else { fprintf(stderr, "(Peer) Something went wrong: %d.\n", eventStatus); lastEvent = -1; pcc->connected = 0; enet_peer_reset(pcc->peer); ret = eventStatus; goto cleanup_pos; } } ret = 0; cleanup_pos: /* Terminate the chater thread. */ pcc->terminated = 1; if (pcc->thread) { pthread_join(pcc->thread, NULL); pcc->thread = 0; } enet_host_destroy(pcc->client); printf("Client is terminated.\n"); return ret; }