php入門到就業線上直播課:進入學習
Apipost = Postman + Swagger + Mock + Jmeter 超好用的API偵錯工具:
在日常的一些開發場景中,我們需要去和遠端伺服器進行一些通訊,執行一些相關命令操作,這個時候我們就可以使用SSH協定實現目標。SSH協定是建立在應用層上的安全協定,全稱為Secure Shell,採用的是面向連線的TCP協定進行傳輸,也就意味著它是安全可靠的。需要注意的是檔案傳輸並不能在SSH協定上完成,需要在下面提到的SFTP協定完成。
Go官方為我們提供了用於實現SSH連線的package,位於golang.org/x/crypto下,通過在程式中呼叫包中提供的相關方法,便可以實現與其他機器進行通訊。使用前我們需要使用go get匯入相關的依賴包。
go get golang.org/x/crypto/ssh
登入後複製
在進行通訊之前,我們還需要設定一些用於設定一些用於建立連線的相關引數。ssh包下的ClientConfig結構體中,定義了建立SSH連線需要用到的一些設定項,部分項提供了預設引數,我們使用時可以不進行宣告。
下面的程式碼段中,我們首先是宣告了使用者名稱和密碼,連線超時時間設定為10秒鐘,addr變數定義了目標機器的IP地址以及埠。
HostKeyCallback項,我們設定了忽略,這是因為SSH協定為使用者端提供了兩種安全驗證方式,一種是基於口令的安全驗證,也就是我們常常使用的賬號密碼形式,另外一種則是基於金鑰的安全驗證,相較於第一種,這種形式的校驗方法極大的提升了安全等級,缺點則是時間損耗相對較長。
如果需要使用這種方式進行校驗,首先我們需要在伺服器上為自己建立一對金鑰,作為使用者端進行存取時,首先會向伺服器端傳送安全驗證請求,伺服器端收到請求後,首先會將機器上儲存的公鑰與使用者端傳送的公鑰進行比較,如果一致,伺服器端則會向用戶端響應加密質詢,使用者端接受到質詢之後,使用私鑰進行解密,然後再將解密結果傳送給伺服器端,伺服器端進行校驗後再返回響應結果,到這裡就算是完成了一段金鑰校驗。
//新增設定
config := &ssh.ClientConfig{
User: "root",
Auth: []ssh.AuthMethod{ssh.Password("Password")},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Timeout: 10 * time.Second,
}
}
addr := fmt.Sprintf("%v:%v", IP, Port)
登入後複製
在完成了所有的引數初始化之後,我們便可以呼叫Dial方法建立SSH連線。Dial方法一共有三個引數和兩個返回值,第一個引數network為網路型別,這裡我們使用面向連線的TCP協定,第二個引數addr則為目標機器的IP地址和埠號,第三個引數config則為前面我們生命的設定項。Dial會返回一個SSH連線和錯誤型別。
func Dial(network, addr string, config *ClientConfig) (*Client, error)
//建立SSH連線
sshClient, err := ssh.Dial("tcp", addr, config)
if err != nil {
log.Fatal("unable to create ssh conn")
}
登入後複製
在建立了與目標機器的SSH連線之後,我們就可以通過建立SSH對談來與目標機器進行通訊。通過NewSession()方法便可以實現這一操作。
//建立SSH對談
sshSession, err := sshClient.NewSession() if err != nil {
log.Fatal("unable to create ssh session")
}
登入後複製
與目標機器建立對談後,我們就可以通過執行命令等來操作遠端伺服器。Go目前為我們提供了五個用於操作遠端機器的方法,分別是Run(), Start(), Output(), CombineOutpt(), Shell()。
?其中 Output(), **CombineOutpt()**這兩個方法是對Run()方法進行不同程度上的封裝,校驗了輸出流,錯誤流等相關內容。
// Output runs cmd on the remote host and returns its standard output.
func (s *Session) Output(cmd string) ([]byte, error) { if s.Stdout != nil { return nil, errors.New("ssh: Stdout already set")
} var b bytes.Buffer
s.Stdout = &b
err := s.Run(cmd) return b.Bytes(), err
}
// CombinedOutput runs cmd on the remote host and returns its combined
// standard output and standard error.
func (s *Session) CombinedOutput(cmd string) ([]byte, error) { if s.Stdout != nil { return nil, errors.New("ssh: Stdout already set")
} if s.Stderr != nil { return nil, errors.New("ssh: Stderr already set")
} var b singleWriter
s.Stdout = &b
s.Stderr = &b
err := s.Run(cmd) return b.b.Bytes(), err
}
登入後複製
Run()方法則是對Start()方法進行了封裝,新增了Wait方法,用於校驗遠端伺服器的退出指令。Wait()方法中有一個管道型別的變數exitStatus,它是用來儲存每次執行命令後,機器返回的退出狀態的。有興趣的朋友可以去看看這塊的程式碼,這裡就不貼程式碼了。
這裡面有一個坑,如果我們在遠端機器上去執行一個永遠不會停止的程式,這個時候我們的程式一直等待不到遠端機器傳送的退出指令,就會造成程式一直阻塞而無法正常返回。解決的辦法是用一個協程去單獨執行這一塊的任務,或者是使用定時器來定時結束session對談,來正常返回。
Start()方法與Shell方法一致,都是返回一個error型別,在底層都是呼叫了start()方法和SendRequest方法,關於這兩個方法的內容這裡就不做詳細介紹了,有興趣的朋友可以自行去閱讀。唯一的區別是Start()方法有一個string型別的引數,用於接收使用者輸入的引數,而Shell()方法是無引數的。
使用Shell()方法配合RequestPty()等方法可以在本地建立一個偽終端,可以直接通過輸入命令的形式操作目標機器。下面都會做一個範例。
//Run
func (s *Session) Run(cmd string) error {
err := s.Start(cmd) if err != nil {
fmt.Println(err) return err
} return s.Wait()
}
// Start runs cmd on the remote host. Typically, the remote
// server passes cmd to the shell for interpretation.
// A Session only accepts one call to Run, Start or Shell.
func (s *Session) Start(cmd string) error { if s.started { return errors.New("ssh: session already started")
}
req := execMsg{
Command: cmd,
}
ok, err := s.ch.SendRequest("exec", true, Marshal(&req)) if err == nil && !ok {
err = fmt.Errorf("ssh: command %v failed", cmd)
} if err != nil { return err
} return s.start()
}
登入後複製
這裡我們使用Run()方法來演示一下如果去執行命令,其他方法型別就不做演示了。這裡我們使用一個標準輸出流、錯誤流來儲存執行結果。
這裡演示了一個簡單的執行過程,使用了cd命令到/home/min目錄下,在給helloworld程式新增可執行許可權,最後執行程式。
var stdoutBuf, stderrBuf bytes.Buffer
session.Stdout = &stdoutBuf
session.Stderr = &stderrBuf
// cd /home/min
// chmod +x helloworld
// ./helloworld
cmd := fmt.Sprintf("cd %v ; chmod +x %v ; %v &", "/home/min", "helloworld", ./helloworld)
err := session.Run(cmd) if err != nil {
log.Fatal("[ERROR]: ", session.Stderr, err)
}
登入後複製
// 設定Terminal Mode
modes := ssh.TerminalModes{
ssh.ECHO: 0, // 關閉回顯
ssh.TTY_OP_ISPEED: 14400, // 設定傳輸速率
ssh.TTY_OP_OSPEED: 14400,
}
// 請求偽終端
err = session.RequestPty("linux", 32, 160, modes) if err != nil {
log.Println(err) return
}
// 設定輸入輸出
session.Stdout = os.Stdout
session.Stdin = os.Stdin
session.Stderr = os.Stderr
session.Shell() // 啟動shell
session.Wait() // 等待退出
登入後複製
//機器平臺資訊type Machine struct {
IP string
Port string
Username string
Password string}//建立SSH連線func CreateSSHConn(m *model.Machine) error { //初始化連線資訊
config := &ssh.ClientConfig{
User: m.Username,
Auth: []ssh.AuthMethod{ssh.Password(m.Password)},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Timeout: 10 * time.Second,
}
addr := fmt.Sprintf("%v:%v", m.IP, m.Port) //建立ssh連線
sshClient, err := ssh.Dial("tcp", addr, config) if err != nil {
fmt.Println("unable create ssh conn", err) return err
} defer sshClient.Close()
//建立ssh對談
session, err := sshClient.NewSession() if err != nil {
fmt.Println("unable create ssh conn", err) return err
} defer session.Close()
//執行命令
var stdoutBuf, stderrBuf bytes.Buffer
session.Stdout = &stdoutBuf
session.Stderr = &stderrBuf
cmd := fmt.Sprintf("cd %v ; chmod +x %v ; %v &", "/home/min", "helloworld", ./helloworld) if err := session.Run(cmd); err != nil {
log.Fatal("[ERROR]: ", session.Stderr, err)
}
//建立偽終端
// 設定Terminal Mode
modes := ssh.TerminalModes{
ssh.ECHO: 0, // 關閉回顯
ssh.TTY_OP_ISPEED: 14400, // 設定傳輸速率
ssh.TTY_OP_OSPEED: 14400,
}
// 請求偽終端
err = session.RequestPty("linux", 32, 160, modes) if err != nil {
log.Fatal(err)
}
// 設定輸入輸出
session.Stdout = os.Stdout
session.Stdin = os.Stdin
session.Stderr = os.Stderr
session.Shell() // 啟動shell
session.Wait() // 等待退出
return err
}
登入後複製
以上就是一文詳解golang如何實現ssh相關操作的詳細內容,更多請關注TW511.COM其它相關文章!