1、思路
-
如何在Go中實現(xiàn)命令行輸入的非阻塞讀取
因為如果使用傳統(tǒng)的阻塞式輸入(如fmt.Scanln),程序會一直等待用戶輸入,無法同時處理其他任務。因此,應該使用并發(fā)機制,讓輸入監(jiān)聽和處理SIP消息可以同時進行。
在Go中,可以使用bufio.NewReader來讀取標準輸入,但通常這是阻塞的。為了非阻塞地讀取輸入,可能需要使用goroutine來單獨處理輸入,這樣主線程可以繼續(xù)執(zhí)行網(wǎng)絡請求。
當用戶輸入“r”時,調用RequestRegister函數(shù);輸入“rm”時,調用取消注冊的函數(shù);當用戶輸入“q”,則退出循環(huán)。需要設計一個循環(huán),不斷監(jiān)聽輸入,并根據(jù)輸入內(nèi)容觸發(fā)不同的操作。
在main函數(shù)中需要持續(xù)處理其他任務,比如接收SIP消息。這意味著主函數(shù)不能因為等待輸入而阻塞。因此,使用goroutine來并發(fā)執(zhí)行輸入監(jiān)聽和其他任務是一個合理的方案。
2、實現(xiàn)功能的步驟
啟動一個goroutine來監(jiān)聽鍵盤輸入:這個goroutine會不斷讀取用戶的輸入,并根據(jù)輸入內(nèi)容觸發(fā)相應的函數(shù)調用。
在主goroutine中處理其他任務:比如持續(xù)接收SIP消息,處理網(wǎng)絡請求等。
處理并發(fā)訪問共享資源的問題:如果RequestRegister或其他函數(shù)涉及到共享資源(比如全局的UDP連接),需要使用互斥鎖(sync.Mutex)來保證線程安全。
實現(xiàn)取消注冊函數(shù):用戶提到“rm”觸發(fā)取消注冊。需要設計一個函數(shù),比如RequestUnregister,用于發(fā)送取消注冊的SIP請求,類似于注冊過程但使用不同的方法。
3、關鍵代碼
// 啟動輸入監(jiān)聽
go handleUserInput()
// 主循環(huán)處理網(wǎng)絡消息
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、實現(xiàn)效果
-
命令行交互與網(wǎng)絡處理的完美并行
-
線程安全的共享資源訪問
-
符合SIP標準的注冊/注銷流程
-
清晰的狀態(tài)管理機制