1、思路
因为如果使用传统的阻塞式输入(如fmt.Scanln),程序会一直等待用户输入,无法同时处理其他任务。因此,应该使用并发机制,让输入监听和处理SIP消息可以同时进行。
在Go中,可以使用bufio.NewReader来读取标准输入,但通常这是阻塞的。为了非阻塞地读取输入,可能需要使用goroutine来单独处理输入,这样主线程可以继续执行网络请求。
当用户输入“r”时,调用RequestRegister函数;输入“rm”时,调用取消注册的函数;当用户输入“q”,则退出循环。需要设计一个循环,不断监听输入,并根据输入内容触发不同的操作。
在main函数中需要持续处理其他任务,比如接收SIP消息。这意味着主函数不能因为等待输入而阻塞。因此,使用goroutine来并发执行输入监听和其他任务是一个合理的方案。
2、实现功能的步骤
启动一个goroutine来监听键盘输入:这个goroutine会不断读取用户的输入,并根据输入内容触发相应的函数调用。
在主goroutine中处理其他任务:比如持续接收SIP消息,处理网络请求等。
处理并发访问共享资源的问题:如果RequestRegister或其他函数涉及到共享资源(比如全局的UDP连接),需要使用互斥锁(sync.Mutex)来保证线程安全。
实现取消注册函数:用户提到“rm”触发取消注册。需要设计一个函数,比如RequestUnregister,用于发送取消注册的SIP请求,类似于注册过程但使用不同的方法。
3、关键代码
// 启动输入监听
go handleUserInput()
// 主循环处理网络消息
for {
buffer := make([]byte, 2048)
n, remoteAddr, err := conn.ReadFromUDP(buffer)
if err != nil {
log.Printf("读取错误: %v", err)
continue
}
msg := string(buffer[:n])
switch {
case strings.HasPrefix(msg, "SIP/2.0 401"):
handle401Response(msg)
case strings.HasPrefix(msg, "NOTIFY"):
go handleNotify(msg, remoteAddr)
case strings.HasPrefix(msg, "SIP/2.0 200"):
log.Println("操作成功")
default:
log.Printf("收到未知消息: %.200s...", msg)
}
}
// 处理用户输入
func handleUserInput() {
scanner := bufio.NewScanner(os.Stdin)
fmt.Println("命令提示:")
fmt.Println("r - 注册")
fmt.Println("rm - 取消注册")
fmt.Println("q - 退出程序")
for scanner.Scan() {
input := strings.TrimSpace(scanner.Text())
switch input {
case "r":
go func() {
connLock.Lock()
defer connLock.Unlock()
if !isRegistered {
RequestRegister()
isRegistered = true
}
}()
case "rm":
go func() {
connLock.Lock()
defer connLock.Unlock()
if isRegistered {
RequestUnregister()
isRegistered = false
}
}()
case "q":
os.Exit(0)
default:
fmt.Println("未知命令,可用命令:r/rm/q")
}
}
}
4、实现效果
-
命令行交互与网络处理的完美并行
-
线程安全的共享资源访问
-
符合SIP标准的注册/注销流程
-
清晰的状态管理机制