douken1726 2015-09-09 07:53
浏览 78
已采纳

在go中添加另一个阻塞线程时,为什么信号处理程序不起作用?

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

  • 写回答

1条回答 默认 最新

  • dougang1965 2015-09-10 05:13
    关注

    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.

    https://gist.github.com/programus/52591a97def30df9dc81

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 有了解d3和topogram.js库的吗?有偿请教
  • ¥100 任意维数的K均值聚类
  • ¥15 stamps做sbas-insar,时序沉降图怎么画
  • ¥15 unity第一人称射击小游戏,有demo,在原脚本的基础上进行修改以达到要求
  • ¥15 买了个传感器,根据商家发的代码和步骤使用但是代码报错了不会改,有没有人可以看看
  • ¥15 关于#Java#的问题,如何解决?
  • ¥15 加热介质是液体,换热器壳侧导热系数和总的导热系数怎么算
  • ¥100 嵌入式系统基于PIC16F882和热敏电阻的数字温度计
  • ¥15 cmd cl 0x000007b
  • ¥20 BAPI_PR_CHANGE how to add account assignment information for service line