Simple Go TCP server and client
Your reverse, and swapCase functions are good. The native use of runes is great, and the functions are clear, etc. If I was to really nit-pick, I would suggest a real function (instead of an anonymous) for the switch statement.

func swapRuneCase(r rune) rune {
    switch {
    case unicode.IsLower(r):
        return unicode.ToUpper(r)
    case unicode.IsUpper(r):
        return unicode.ToLower(r)
    }
    return r
}


That makes it clearer, and potentially, later, reusable.

It also makes the swapCase function look like:

func swapCase(s string) string {
    return strings.Map(swapRuneCase, s)
}


Interestingly, it also leads to a more rune-based server handler function...

Here's your function:


func handleServerConnection(c net.Conn, i int) {
    for {
        // scan message
        scanner := bufio.NewScanner(c)
        for scanner.Scan() {
            msg := scanner.Text()
            log.Printf("Client %v sends: %v", i, msg)
            msgNew := swapCase(reverse((msg)))
            c.Write([]byte(msgNew + "\n"))
            log.Printf("Client %v receives: %v", i, msgNew)
            fmt.Println("---")
        }
        if errRead := scanner.Err(); errRead != nil {
            log.Printf("Client %v disconnected...", i)
            fmt.Println("---")
            return
        }
    }
}



That function is doing too many things .... hmmm actually it's just got a useless outer loop. The "scan" loop is all you need. The scanner.Scan() will only terminate the loop when the connection is closed (the c Reader returns an error - presumably EOF, but any other error has the same consequence... c is not readable). So, remove the outer loop, and, we know that the scanner will return an error too, so the condition at the end is not useful. This leaves:

func handleServerConnection(c net.Conn, i int) {
    // scan message
    scanner := bufio.NewScanner(c)
    for scanner.Scan() {
        msg := scanner.Text()
        log.Printf("Client %v sends: %v", i, msg)
        msgNew := swapCase(reverse((msg)))
        c.Write([]byte(msgNew + "\n"))
        log.Printf("Client %v receives: %v", i, msgNew)
        fmt.Println("---")
    }
    log.Printf("Client %v disconnected...", i)
    fmt.Println("---")
}


Note that the fiddling you do with the newline is necessary because you can't "reverse" it too.

Your mixing of both log and fmt in things like log.Printf and fmt.Println is confusing. Use one of them, not both. log is probably the better option. Also, you should be adding \n to the fmt.Printf format strings (you don't need to do that for log.Printf - but you can if you want). There's too much logging! You're not inspecting the error value for Write(...), and you're not closing the Conn which will lead to "CLOSE_WAIT" sockets in the OS network stack. In general, though. I would rationalize it all down to:

func handleServerConnection(c net.Conn, i int) {
    defer func() {
        if err := c.Close(); err != nil {
            log.Printf("Closing client %v connection returned error: %v\n", i, err)
        }
    }()

    // scan message
    scanner := bufio.NewScanner(c)
    for scanner.Scan() {
        msg := scanner.Text()
        msgNew := swapCase(reverse((msg)))
        if _, err := c.Write([]byte(msgNew + "\n")); err != nil {
            log.Printf("Client %v has closed writer: %v\n", i, err)
            break
        }
        log.Printf("Client %v\n received: %v\n returned: %v\n", i, msg, msgNew)
    }
    log.Printf("Client %v disconnected...\n", i)
}


Right, that's it for the handler function.

Your main function is structurally OK, but you MUST check the error state from the Listen call:


ln, _ := net.Listen("tcp", ":9999")



By ignoring the error, if you start your program twice, the second program will panic with a nil pointer reference on the ln.Accept() call (since ln will be nil)

That Accept() call should also have it's error checked too!

Again, using both log and fmt is confusing.

In general, you should specialize your network services/processes when you can. You use net.Listen and ln.Accept when you should probably use ListenTCP and AcceptTCP (you get a TCPListener instead of a Listener) - and your connection is a TCPConn and not a Conn (you should change the parameter type on the handler function too).

Your client side has similar issues, but I think you can figure them out as you go based on the above.