I am trying to build a very simple tcp server/client. And I want the program could close the connection when it is interrupted by ctrl-c.
If I only send message or only receive message in the main thread, everything works just fine.
Here is the code for the client side.
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"net"
"bufio"
"io"
"time"
)
const (
TIMEOUT = 10
)
func main() {
if len(os.Args) < 2 {
fmt.Println(usage(os.Args[0]))
return
}
var timeout time.Duration
if len(os.Args) > 2 {
timeout, _ = time.ParseDuration(os.Args[2])
}
if timeout == 0 {
timeout = time.Duration(TIMEOUT * time.Second)
}
conn, err := net.DialTimeout("tcp", os.Args[1], timeout)
if err != nil {
fmt.Println("Error connecting: ", err.Error())
return
}
defer conn.Close()
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
go func() {
fmt.Println("wait ctrl-c")
for _ = range c {
fmt.Println("close on ctrl-c")
conn.Close()
}
}()
for {
message, err := bufio.NewReader(os.Stdin).ReadString('
')
if err == io.EOF {
fmt.Fprint(conn, message)
break
} else if err != nil {
fmt.Println("Error reading: ", err.Error())
break
} else {
fmt.Fprintf(conn, message)
}
}
}
func usage(filename string) string {
return fmt.Sprintf("Usage: %s <address (ex. localhost:8016, google.com:http, [2001:db8::1]:http, etc.)> [timeout (ex. 10s, 300ms, etc.)]", filename)
}
But after I added the code that just print what it received and run it in another thread, the ctrl-c handler does not work.
This is the code:
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"net"
"bufio"
"io"
"time"
)
const (
TIMEOUT = 10
)
func main() {
if len(os.Args) < 2 {
fmt.Println(usage(os.Args[0]))
return
}
var timeout time.Duration
if len(os.Args) > 2 {
timeout, _ = time.ParseDuration(os.Args[2])
}
if timeout == 0 {
timeout = time.Duration(TIMEOUT * time.Second)
}
conn, err := net.DialTimeout("tcp", os.Args[1], timeout)
if err != nil {
fmt.Println("Error connecting: ", err.Error())
return
}
defer conn.Close()
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
go func() {
fmt.Println("wait ctrl-c")
for _ = range c {
fmt.Println("close on ctrl-c")
conn.Close()
}
}()
// code added.
go func() {
for {
message, err := bufio.NewReader(conn).ReadString('
')
if err == io.EOF {
fmt.Print(message)
break
} else if err != nil {
fmt.Println("Error remote reading: ", err.Error())
break
} else {
fmt.Print(message)
}
}
conn.Close()
os.Exit(0)
}()
for {
message, err := bufio.NewReader(os.Stdin).ReadString('
')
if err == io.EOF {
fmt.Fprint(conn, message)
break
} else if err != nil {
fmt.Println("Error reading: ", err.Error())
break
} else {
fmt.Fprintf(conn, message)
}
}
}
func usage(filename string) string {
return fmt.Sprintf("Usage: %s <address (ex. localhost:8016, google.com:http, [2001:db8::1]:http, etc.)> [timeout (ex. 10s, 300ms, etc.)]", filename)
}
I am working on Windows 7 now. What is the problem and how can I solve it?
You can find both server side and client side code here: https://gist.github.com/programus/52591a97def30df9dc81
I did some more investigation and I found that the key point of the problem is not the threads but whether there is enough time for signal handler running.
I made a simple program to re-produce the problem under Windows 7 as well as the solution.
package main
import (
"fmt"
"io"
"os"
"os/signal"
)
func main() {
c := make(chan os.Signal, 1)
//q := make(chan bool, 1)
signal.Notify(c, os.Interrupt, os.Kill)
go func() {
sig := <- c
fmt.Println(sig)
//close(q)
}()
count, err := io.Copy(os.Stdout, os.Stdin)
fmt.Println(count, err)
//select {
//case <- q:
//}
}
You will find the signal might not be printed out sometime. But after I uncommented the q
channel related code. It always works.
So I think it would because the program quit too quickly to let the signal handler run.
Hope this Q&A could help others who has the same problem.
Also, I worked out a solution for the simple tcp server/client and updated the gist.