Example #1
0
// TemporaryNetworkError returns a RetryFunc that retries up to a maximum of n
// times, only if the error returned by the RoundTrip function is a temporary
// network error (for example: a connect timeout).
func TemporaryNetworkError(n int) RetryFunc {
	return func(err error) (retry bool, delay time.Duration) {
		var nerr net.Error
		var ok bool

		// Never retry if this is not a network error.
		switch rerr := err.(type) {
		case *url.Error:
			if nerr, ok = rerr.Err.(net.Error); !ok {
				return false, 0
			}
		case net.Error:
			nerr = rerr
		default:
			return false, 0
		}

		if !nerr.Temporary() {
			return false, 0
		}

		// Don't retry if we're out of tries.
		if n--; n <= 0 {
			return false, 0
		}

		return true, 0
	}
}
Example #2
0
// This is executed as one process for each client. All messages sent because of this process must use blocking send.
// The reason is that non-blocking send will be sent in a channel to this process, and we must not send messages
// to our own channel.
func ManageOneClient2_WLuWLqWLmBlWLcWLw(conn net.Conn, i int) {
	buff := make([]byte, 50) // Command buffer, also used for blocking messages.
	up := allPlayers[i]
	up.Name = dummyLoginName // To have something to print
	previous := time.Now()
	longPrevious := previous
	previousAttack := previous
	// Loop until player disconnects
	for {
		// Measure how much time has passed since last iteration
		now := time.Now()
		delta := now.Sub(previous)
		if up.connState == PlayerConnStateIn {
			// Ignore this unless the player is logged in.
			if delta > ObjectsUpdatePeriod {
				// These functions cost a lot, don't run them unless enough time has passed.
				previous = now
				// Check if near objects have moved and send messages to the client
				clientTellMovedObjects_Bl(up)
				// Update new position for player
				up.cmdUpdatePosition_WLuWLqWLmWLwWLc()
				fullReport := false
				if now.Sub(longPrevious) > 2*time.Second {
					fullReport = true
					longPrevious = now
				}
				// Tell everyone near if the player moved
				up.checkOnePlayerPosChanged_RLuWLqBl(fullReport)
			}
			delta = now.Sub(previousAttack)
			if delta > CnfgAttackPeriod {
				previousAttack = now
				up.ManageAttackPeriod_WLuBl(delta) // Manage general attacking tasks
			}
		}
		if up.updatedStats {
			// As the player isn't locked, the stats may be updated while this is done. Clear the flag
			// first, to ensure other updates are not lost. The transient flags are cleared later, and so it may happen
			// that a flag will be lost. But this is not vital information, so a loss can be accepted if it is unlikely.
			// log.Printf("State %v, flags 0x%x, hp %v, exp %v, level %v, mana %v\n", up.connState, up.flags, up.HitPoints, up.Exp, up.Level, up.Mana)
			up.updatedStats = false
			up.SendMsgUpdatedStats_Bl(buff)
			up.flags &= ^UserFlagTransientMask // Clear all transient flags, now that the client has been informed.
		}
		if up.forceSave {
			up.forceSave = false
			CmdSavePlayerNow_RluBl(i)
		}
		// Read out all waiting data, if any, from the incoming channels
		for moreData := true; moreData; {
			select {
			case clientMessage := <-up.channel:
				up.writeBlocking_Bl(clientMessage)
			case clientCommand := <-up.commandChannel:
				clientCommand(up)
			default:
				moreData = false
			}
			if up.connState == PlayerConnStateDisc {
				return
			}
		}
		// Set a new deadline.
		conn.SetReadDeadline(time.Now().Add(ObjectsUpdatePeriod))
		n, err := conn.Read(buff[0:2]) // Read the length information. This will block for ObjectsUpdatePeriod ns
		if err != nil {
			if e2, ok := err.(*net.OpError); ok && (e2.Timeout() || e2.Temporary()) {
				// log.Printf("Read timeout %v", e2) // This will happen frequently
				continue
			}
			if *verboseFlag > 1 {
				// This is a normal case
				log.Printf("Disconnect %v because of '%v'\n", up.Name, err)
			}
			return
		}
		if n == 1 {
			if *verboseFlag > 1 {
				log.Printf("Got %d bytes reading from socket\n", n)
			}
			for n == 1 {
				n2, err := conn.Read(buff[1:2]) // Second byte of the length
				if err != nil || n2 != 1 {
					if e2, ok := err.(*net.OpError); ok && (e2.Timeout() || e2.Temporary()) {
						log.Printf("Read timeout %v", e2)
						continue
					}
					log.Printf("Failed again to read: %v\n", err)
					return
				}
				n = 2
			}
		}
		if n != 2 {
			log.Printf("Got %d bytes readin from socket\n", n)
			continue
		}
		length := int(uint(buff[1])<<8 + uint(buff[0]))
		if length > cap(buff) {
			buff2 := make([]byte, length)
			copy(buff2, buff[0:2])
			buff = buff2
			if *verboseFlag > 2 {
				log.Printf("Expecting %d bytes, which is too much for buffer. BUffer was extended\n", length)
			}
		}
		// Read the rest of the bytes
		for n2 := n; n2 < length; {
			// If unlucky, we only get one byte at a time
			var e2 net.Error
			for {
				// Loop here until we get what we want.
				n, err = conn.Read(buff[n2:length])
				if err == nil {
					break // No error
				}
				e2, ok := err.(net.Error)
				if ok && !e2.Temporary() && !e2.Timeout() {
					break // Bad error, can't handle it.
				}
				if *verboseFlag > 1 {
					log.Println("Timeout, sleep wait")
				}
				time.Sleep(1e7)
			}
			if err != nil {
				if *verboseFlag > 0 {
					log.Printf("Disconnect %v because of '%v'.\n", up.Name, err)
					if e2 != nil {
						log.Printf("Temporary: %v, Timeout: %v\n", e2.Temporary(), e2.Timeout())
					}
				}
				return
			}
			n2 += n
		}
		trafficStatistics.AddReceived(length)
		// fmt.Printf("ManageOneClient: command (%d bytes) %v\n", n, buff[:length])
		switch buff[2] {
		case CMD_PING:
			if buff[3] == 0 {
				// Request message
				buff[3] = 1                               // Change it to response type
				allPlayers[i].writeBlocking_Bl(buff[0:4]) // And send it back.
			}
		case CMD_SAVE:
			CmdSavePlayerNow_RluBl(i)
		case CMD_LOGIN:
			if length <= 3 {
				// TODO. Too short command, no login name
				log.Print("LOGIN no name\n")
				return
			}
			if *verboseFlag > 2 {
				log.Printf("Logincmd %v\n", buff[3:length])
			}
			up.CmdLogin_WLwWLuWLqBlWLc(string(buff[3:length]))
		case CMD_RESP_PASSWORD:
			// log.Printf("Logincmd %v\n", buff[3 : length])
			if !up.CmdPassword_WLwWLuWLqBlWLc(buff[3:length]) {
				buff[0] = 3
				buff[1] = 0
				buff[2] = CMD_LOGINFAILED
				allPlayers[i].writeBlocking_Bl(buff[0:3]) // Tell client login failed.
				if *verboseFlag > 0 {
					log.Printf("Disconnect %v\n", up.Name)
				}
				return
			}
			up.FileMessage(*welcomeMsgFile)
			if len(allPlayerIdMap) > 1 {
				up.Printf_Bl("Current players:")
				up.ReportPlayers()
			} else if lastUser != "" {
				up.Printf_Bl("Last logout: %s in %s", lastUser, time.Now().Sub(timeOfLogout))
			}
			if *verboseFlag > 0 {
				log.Println("Successful LOGIN for", up.Email, up.Name)
			}
		case CMD_QUIT:
			if *verboseFlag > 1 {
				log.Printf("Quit: %v\n", up.Name)
			}
			return
		case CMD_GET_COORDINATE:
			up.CmdReportCoordinate_RLuBl(false)
		case CMD_ATTACK_MONSTER:
			if length != 7 {
				log.Printf("CMD_ATTACK_MONSTER illegal length: %v\n", buff[0:length])
				return
			}
			up.CmdAttackMonster_WLuRLm(buff[3:7])
		case CMD_PLAYER_ACTION:
			if length != 4 {
				log.Printf("CMD_PLAYER_ACTION illegal length: %v\n", buff[0:length])
				return
			}
			up.CmdPlayerAction_WLuBl(buff[3])
		case CMD_READ_CHUNK:
			if length != 15 {
				log.Printf("CommandReadChunk illegal length: %v\n", buff[0:length])
				return
			} else {
				MakeCommandReadChunk_WLwWLcBl(i, buff[3:length])
			}
		case CMD_VRFY_CHUNCK_CS:
			if length < 10 || ((length-3)%7 != 0) {
				// fmt.Printf("Verify Checksum length error!")
				log.Printf("CMD_VRFY_CHUNCK_CS illegal length: %v\n", buff[0:length])
			} else {
				// A list of chunk checksums can be recieved, these should be verified and if the
				// checksum is not correct, the updated block should be sent
				// fmt.Printf("CMD_VRFY_CHUNCK_CS - %v %v %v %v\n", buff[6], buff[7], buff[8], buff[9] )
				CommandVerifyChunkCS_WLwWLcBl(i, buff[3:length])
			}
		case CMD_HIT_BLOCK:
			if length != 18 {
				log.Printf("HitBlockCommand illegal length %d: %v\n", length, buff[0:length])
				return
			} else {
				var b []byte
				var cc chunkdb.CC
				cc.X, b, _ = ParseInt32(buff[3:length])
				cc.Y, b, _ = ParseInt32(b)
				cc.Z, b, _ = ParseInt32(b)
				up.HitBlock_WLwWLcRLq(cc, b[0], b[1], b[2])
				// fmt.Printf("ManageOneClient %v\n", hbc)
				// fmt.Println("Buffer ", buff[0:18])
			}
		case CMD_BLOCK_UPDATE:
			if length != 19 {
				// The block update command allows for many blocks to be updated at the same time, but
				// that is for the server->client, not for the client->server.
				log.Printf("AttachBlockCommand illegal length %d: %v\n", length, buff[0:length])
				return
			} else {
				var b []byte
				var cc chunkdb.CC
				cc.X, b, _ = ParseInt32(buff[3:length])
				cc.Y, b, _ = ParseInt32(b)
				cc.Z, b, _ = ParseInt32(b)
				bl := block(b[3])
				if bl == BT_Teleport {
					cp := ChunkFind_WLwWLc(cc)
					cp.SetTeleport(cc, up, b[0], b[1], b[2])
				} else {
					// log.Printf("Attach block %v at chunk %v\n", bl, cc)
					CmdAttachBlock_WLwWLcRLq(cc, b[0], b[1], b[2], bl, i)
				}
			}
		case CMD_JUMP:
			up.CmdPlayerMove_WLuWLqWLmWLwWLc(CMD_JUMP)
		case CMD_START_FWD:
			up.CmdPlayerMove_WLuWLqWLmWLwWLc(CMD_START_FWD)
		case CMD_STOP_FWD:
			up.CmdPlayerMove_WLuWLqWLmWLwWLc(CMD_STOP_FWD)
		case CMD_START_BWD:
			up.CmdPlayerMove_WLuWLqWLmWLwWLc(CMD_START_BWD)
		case CMD_STOP_BWD:
			up.CmdPlayerMove_WLuWLqWLmWLwWLc(CMD_STOP_BWD)
		case CMD_START_LFT:
			up.CmdPlayerMove_WLuWLqWLmWLwWLc(CMD_START_LFT)
		case CMD_STOP_LFT:
			up.CmdPlayerMove_WLuWLqWLmWLwWLc(CMD_STOP_LFT)
		case CMD_START_RGT:
			up.CmdPlayerMove_WLuWLqWLmWLwWLc(CMD_START_RGT)
		case CMD_STOP_RGT:
			up.CmdPlayerMove_WLuWLqWLmWLwWLc(CMD_STOP_RGT)
		case CMD_SET_DIR:
			if length != 7 {
				log.Printf("Illegal SET_DIR client command: %v\n", buff[0:length])
				return
			} else {
				MakeDirectionsCommand(i, buff[3:length])
			}
		case CMD_DEBUG:
			up.playerStringMessage_RLuWLwRLqBlWLaWLc(buff[3:length])
		case CMD_USE_ITEM:
			code := ObjectCode(buff[3:7])
			lvl := uint32(0)
			if length == 11 {
				// This is the new proper way, where a level of the item is also provided,
				// but some clients remains with the old format. TODO: Clean up.
				lvl, _, _ = ParseUint32(buff[7:11])
			}
			up.Inventory.Use_WluBl(up, code, lvl)
		case CMD_DROP_ITEM:
			code := ObjectCode(buff[3:7])
			lvl, _, _ := ParseUint32(buff[7:11])
			up.Lock()
			// The Use function will not really do anything, only return a function. That way, only a read lock is needed.
			// The reason for this is that the Use function will do callbacks that will, in turn, lock what is needed. As this is
			// not known now, except that we know the user has to be read locked.
			val := ItemValueAsDrop(up.Level, lvl, code) * CnfgItemRewardNormalizer
			if val >= 0 {
				up.Inventory.Remove(code, lvl)
				up.AddExperience(val)
			}
			up.Unlock()
			// log.Println("CMD_DROP_ITEM", code, lvl, val)
			ReportOneInventoryItem_WluBl(up, code, lvl)
		case CMD_REQ_PLAYER_INFO:
			uid, _, _ := ParseUint32(buff[3:7])
			allPlayersSem.RLock()
			up, ok := allPlayerIdMap[uid]
			allPlayersSem.RUnlock()
			if ok {
				l := len(up.Name)
				buff[0] = byte(8 + l)
				buff[1] = 0
				buff[2] = CMD_RESP_PLAYER_NAME
				EncodeUint32(uid, buff[3:7])
				buff[7] = up.AdminLevel
				copy(buff[8:], up.Name)
				allPlayers[i].writeBlocking_Bl(buff[0 : 8+l])
				allPlayers[i].ReportEquipment_Bl(up)
			}
		case CMD_VRFY_SUPERCHUNCK_CS:
			if length < 10 || ((length-3)%7 != 0) {
				// fmt.Printf("Verify Checksum length error!")
				log.Printf("CMD_VRFY_CHUNK_CS illegal length: %v\n", buff[0:length])
			} else {
				// A list of chunk checksums can be recieved, these should be verified and if the
				// checksum is not correct, the updated block should be sent
				// fmt.Printf("CMD_VRFY_CHUNK_CS - %v %v %v %v\n", buff[6], buff[7], buff[8], buff[9] )
				up.CommandVerifySuperchunkCS_Bl(buff[3:length])
			}
		case CMD_TELEPORT:
			up.Teleport(buff[3:length])
		default:
			log.Printf("Unknown command '%v'.\n", buff[0:length])
			return
		}
		if length < n {
			// TODO: There are more commands still in the data, go to next
			panic("Too much")
		}
	}
}