網絡上的兩個程序經過一個雙向的通訊鏈接實現數據的交換,這個鏈接的一端稱爲一個socket
。
創建網絡通訊鏈接至少要一對端口號(socket
)。socket本質是編程接口(API
),對TCP/IP
的封裝,TCP/IP
也要提供可供程序員作網絡開發所用的接口,這就是Socket編程接口
;HTTP是轎車
,提供了封裝或者顯示數據的具體形式;Socket是發動機
,提供了網絡通訊的能力。
Socket的英文原義是「孔」或「插座」。做爲BSD UNIX的進程通訊機制,取後一種意思。一般也稱做"套接字",用於描述IP地址和端口,是一個通訊鏈的句柄,能夠用來實現不一樣虛擬機或不一樣計算機之間的通訊。每種服務都打開一個Socket,並綁定到一個端口上,不一樣的端口對應於不一樣的服務。Socket正如其英文原意那樣,像一個多孔插座。插座是用來給插頭提供一個接口讓其通電的,此時咱們就能夠將插座當作一個服務端,不一樣的插頭當作客戶端。程序員
net.Dial("tcp",addr)
func main() { addr := "wwww.baidu.com:80" //定義主機名 conn,err := net.Dial("tcp",addr) //撥號操做,須要指定協議。 if err != nil { log.Fatal(err) } fmt.Println(conn.RemoteAddr().String()) //220.181.57.217:80 fmt.Println(conn.LocalAddr()) //192.168.0.120:62662 fmt.Println(reflect.TypeOf(conn.LocalAddr())) //*net.TCPAddr fmt.Println(reflect.TypeOf(conn.RemoteAddr())) n,err := conn.Write([]byte("GET / HTTP/1.1\r\n\r\n")) //向服務端發送數據。用n接受返回的數據大小。 if err != nil { log.Fatal(err) } fmt.Println("寫入的大小是:",n)//18 buf := make([]byte,10) //定義一個切片的長度是1024。 for { n,err = conn.Read(buf) //接收到的內容大小。 if err == io.EOF { conn.Close() } fmt.Print(string(buf[:n])) } fmt.Println(string(buf[:n])) //將接受的內容都讀取出來。 }
r := bufio.NewReader(conn) //將這個連接(connection)包裝如下。將conn的內容都放入r中,可是沒有進行讀取 for { line,err := r.ReadString('\n') //將r的內容也就是conn的數據按照換行符進行讀取。 if err == io.EOF { conn.Close() } fmt.Print(line) }
io.Copy(os.Stdout,conn)
package main import ( "net" "log" "time" ) func Handle_conn(conn net.Conn) { //這個是在處理客戶端會阻塞的代碼。 //var buf = make([]byte, 10) //讀取信息 //n, err := c.Read(buf) conn.Write([]byte("xxxxx\n")) //經過conn的wirte方法將這些數據返回給客戶端。 conn.Write([]byte("hello world!\n")) time.Sleep(time.Minute) conn.Close() //與客戶端斷開鏈接。 } func main() { addr := "0.0.0.0:8080" //表示監聽本地全部ip的8080端口,也能夠這樣寫:addr := ":8080" listener,err := net.Listen("tcp",addr) if err != nil { log.Fatal(err) } defer listener.Close() for { conn,err := listener.Accept() //用conn接收連接 if err != nil { log.Fatal(err) } go Handle_conn(conn) //開啓多個協程。 } }
對方服務器很忙,瞬間有大量client端鏈接嘗試向server創建,server端的listen backlog隊列滿,server accept不及時((即使不accept,那麼在backlog數量範疇裏面,connect都會是成功的,由於new conn已經加入到server side的listen queue中了,accept只是從queue中取出一個conn而已),這將致使client端Dial阻塞。咱們仍是經過例子感覺Dial的行爲特色:編程
鏈接創建後,若是對方未發送數據到socket,接收方(Server)會阻塞在Read操做上,這和前面提到的「模型」原理是一致的。執行該Read操做的goroutine也會被掛起。runtime會監視該socket,直到其有數據纔會從新
調度該socket對應的Goroutine完成read。緩存
若是socket中有部分數據,且長度小於一次Read操做所指望讀出的數據長度,那麼Read將會成功讀出這部分數據並返回,而不是等待全部指望數據所有讀取後再返回。服務器
TCP鏈接通訊兩端的OS都會爲該鏈接保留數據緩衝,一端調用Write後,實際上數據是寫入到OS的協議棧的數據緩衝的。TCP是全雙工通訊,所以每一個方向都有獨立的數據緩衝。當發送方將對方的接收緩衝區以及自身的發送緩衝區寫滿後,Write就會阻塞。網絡
若是非要給Write增長一個期限,那咱們能夠調用SetWriteDeadline方法。咱們copy一份client5.go,造成client6.go,在client6.go的Write以前增長一行timeout設置代碼:socket
conn.SetWriteDeadline(time.Now().Add(time.Microsecond * 10))
ICMP是用來對網絡情況進行反饋的協議,能夠用來偵測網絡狀態或檢測網路錯誤。
參考文章tcp
func main() { //構建服務器鏈接 conn,_:=net.ResolveTCPAddr("tcp","127.0.0.1:1234") //鏈接撥號 n,_:=net.DialTCP("tcp",nil,conn) //發送數據 n.Write([]byte("hello kongyixueyuan")) fmt.Println("發送結束") }
func main() { //監聽電腦中的端口,1024-65535 netListen,_:=net.Listen("tcp","127.0.0.1:1234") //延時關閉 defer netListen.Close() //經過循環等待客戶端的鏈接 for { //只有客戶端鏈接成功纔會向下執行 conn,_:=netListen.Accept() //建立緩存,存放客戶端發送的數據 data:=make([]byte,1024) for { //接收客戶端發送的數據,n數據的大小 n,_:=conn.Read(data) //data[:n]就是接受到的密文 fmt.Println("密文爲:",data[:n]) break } } }