GO語言的進階之路-網絡編程之socket
作者:尹正傑
版權聲明:原創作品,謝絕轉載!否則將追究法律責任。
一.什么是socket;
在說socket之前,我們要對兩個概念要有所了解,就是IP和端口。
1.什么是IP;
IP地址是我們進行TCP/IP通訊的基礎,每個鏈接到網絡的計算機都必須有一個IP地址。在這里我不打算給大家說IPV4和IPV6,也不打算說主機位和網絡位。
我們可以簡單的理解,在局域網中,IP就是用來標識主機的。(大家不要鑽牛角尖說NAT這種情況,我們在這里是忽略的。)
2.什么是端口;
如果說IP是用來標識主機的,那么端口就是用來標識這台主機的所有服務。
這樣我們就方便理解了,端口是用來標識服務的,只要主機的服務啟動,然后我們訪問主機的對應端口就能被提供服務。歡聚話說,局域網中如果別人知道你IP和端口,就能訪問到的你的服務了(前提下是別人的主機和你的主機是要互通的。)
3.什么是socket;
網絡上的兩個程序通過一個雙向的通信連接實現數據的交換,這個連接的一端稱為一個socket。
建立網絡通信連接至少要一對端口號(socket)。socket本質是編程接口(API),對TCP/IP的封裝,TCP/IP也要提供可供程序員做網絡開發所用的接口,這就是Socket編程接口;HTTP是轎車,提供了封裝或者顯示數據的具體形式;Socket是發動機,提供了網絡通信的能力。
Socket的英文原義是“孔”或“插座”。作為BSD UNIX的進程通信機制,取后一種意思。通常也稱作"套接字",用於描述IP地址和端口,是一個通信鏈的句柄,可以用來實現不同虛擬機或不同計算機之間的通信。每種服務都打開一個Socket,並綁定到一個端口上,不同的端口對應於不同的服務。Socket正如其英文原意那樣,像一個多孔插座。插座是用來給插頭提供一個接口讓其通電的,此時我們就可以將插座當做一個服務端,不同的插頭當做客戶端。
二.客戶端Socket;
1.串行指定讀取客戶端返回內容大小;(這種方法我不推薦使用!我推薦使用的是后三種方式讀取。請看下面的備注。)
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import ( 11 "net" 12 "log" 13 "fmt" 14 "reflect" 15 "io" 16 ) 17 18 func main() { 19 addr := "wwww.baidu.com:80" //定義主機名 20 conn,err := net.Dial("tcp",addr) //撥號操作,需要指定協議。 21 if err != nil { 22 log.Fatal(err) 23 } 24 fmt.Println("訪問公網IP地址是:",conn.RemoteAddr().String()) /*獲取“conn”中的公網地址。注意:最好是加上后面的String方法,因為他們的那些是不一樣的喲·當然你打印的時候 25 可以不加輸出結果是一樣的,但是你的內心是不一樣的喲!*/ 26 fmt.Printf("客戶端鏈接的地址及端口是:%v\n",conn.LocalAddr()) //獲取到本地的訪問地址和端口。 27 fmt.Println("“conn.LocalAddr()”所對應的數據類型是:",reflect.TypeOf(conn.LocalAddr())) 28 fmt.Println("“conn.RemoteAddr().String()”所對應的數據類型是:",reflect.TypeOf(conn.RemoteAddr().String())) 29 n,err := conn.Write([]byte("GET / HTTP/1.1\r\n\r\n")) //向服務端發送數據。用n接受返回的數據大小,用err接受錯誤信息。 30 if err != nil { 31 log.Fatal(err) 32 } 33 fmt.Println("向服務端發送的數據大小是:",n) 34 35 buf := make([]byte,1024) //定義一個切片的長度是1024。 36 37 n,err = conn.Read(buf) //接收到的內容大小。 38 39 if err != nil && err != io.EOF { //io.EOF在網絡編程中表示對端把鏈接關閉了。 40 log.Fatal(err) 41 } 42 fmt.Println(string(buf[:n])) //將接受的內容都讀取出來。 43 conn.Close() //斷開TCP鏈接。 44 } 45 46 47 48 #以上代碼輸出結果如下: 49 訪問公網IP地址是: 111.13.101.208:80 50 客戶端鏈接的地址及端口是:172.16.3.210:49568 51 “conn.LocalAddr()”所對應的數據類型是: *net.TCPAddr 52 “conn.RemoteAddr().String()”所對應的數據類型是: string 53 向服務端發送的數據大小是: 18 54 HTTP/1.1 400 Bad Request 55 Date: Mon, 31 Jul 2017 06:22:41 GMT 56 Server: Apache 57 Content-Length: 226 58 Connection: Keep-Alive 59 Content-Type: text/html; charset=iso-8859-1
備注:io.Copy不會主動調用close,io.Copy結束的條件是reader得到EOF。感興趣的可以看下源碼。

1 func copyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) { 2 // If the reader has a WriteTo method, use it to do the copy. 3 // Avoids an allocation and a copy. 4 if wt, ok := src.(WriterTo); ok { 5 return wt.WriteTo(dst) 6 } 7 // Similarly, if the writer has a ReadFrom method, use it to do the copy. 8 if rt, ok := dst.(ReaderFrom); ok { 9 return rt.ReadFrom(src) 10 } 11 if buf == nil { 12 buf = make([]byte, 32*1024) 13 } 14 for { 15 nr, er := src.Read(buf) 16 if nr > 0 { 17 nw, ew := dst.Write(buf[0:nr]) 18 if nw > 0 { 19 written += int64(nw) 20 } 21 if ew != nil { 22 err = ew 23 break 24 } 25 if nr != nw { 26 err = ErrShortWrite 27 break 28 } 29 } 30 if er != nil { 31 if er != EOF { 32 err = er 33 } 34 break 35 } 36 } 37 return written, err 38 }
2.按照指定大小循環讀取;
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import ( 11 "net" 12 "log" 13 "fmt" 14 "reflect" 15 "io" 16 ) 17 18 func main() { 19 addr := "wwww.baidu.com:80" //定義主機名 20 conn,err := net.Dial("tcp",addr) //撥號操作,需要指定協議。 21 if err != nil { 22 log.Fatal(err) 23 } 24 fmt.Println(conn.RemoteAddr().String()) //最好是加上后面的String方法,因為他們的那些是不一樣的喲·當然你打印的時候可以不加。 25 fmt.Println(conn.LocalAddr()) 26 fmt.Println(reflect.TypeOf(conn.LocalAddr())) 27 fmt.Println(reflect.TypeOf(conn.RemoteAddr().String())) 28 n,err := conn.Write([]byte("GET / HTTP/1.1\r\n\r\n")) //向服務端發送數據。用n接受返回的數據大小,用err接受錯誤信息。 29 if err != nil { 30 log.Fatal(err) 31 } 32 fmt.Println("寫入的大小是:",n) 33 34 buf := make([]byte,10) //定義一個切片的長度是1024。 35 36 for { 37 n,err = conn.Read(buf) //接收到的內容大小。 38 if err == io.EOF { 39 conn.Close() 40 } 41 fmt.Print(string(buf[:n])) 42 } 43 44 fmt.Println(string(buf[:n])) //將接受的內容都讀取出來。 45 46 } 47 48 49 50 51 #以上代碼輸出結果如下: 52 111.13.101.208:80 53 172.16.3.210:63838 54 *net.TCPAddr 55 string 56 寫入的大小是: 18 57 HTTP/1.1 400 Bad Request 58 Date: Mon, 31 Jul 2017 10:38:42 GMT 59 Server: Apache 60 Content-Length: 226 61 Connection: Keep-Alive 62 Content-Type: text/html; charset=iso-8859-1 63 64 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> 65 <html><head> 66 <title>400 Bad Request</title> 67 </head><body> 68 <h1>Bad Request</h1> 69 <p>Your browser sent a request that this server could not understand.<br /> 70 </p> 71 </body></html>
3.按行讀取;
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import ( 11 "net" 12 "log" 13 "fmt" 14 "reflect" 15 "io" 16 "bufio" 17 ) 18 19 func main() { 20 addr := "wwww.baidu.com:80" //定義主機名 21 conn,err := net.Dial("tcp",addr) //撥號操作,需要指定協議。 22 if err != nil { 23 log.Fatal(err) 24 } 25 fmt.Println(conn.RemoteAddr().String()) //最好是加上后面的String方法,因為他們的那些是不一樣的喲·當然你打印的時候可以不加。 26 fmt.Println(conn.LocalAddr()) 27 fmt.Println(reflect.TypeOf(conn.LocalAddr())) 28 fmt.Println(reflect.TypeOf(conn.RemoteAddr().String())) 29 n,err := conn.Write([]byte("GET / HTTP/1.1\r\n\r\n")) //向服務端發送數據。用n接受返回的數據大小,用err接受錯誤信息。 30 if err != nil { 31 log.Fatal(err) 32 } 33 fmt.Println("寫入的大小是:",n) 34 35 r := bufio.NewReader(conn) //將這個鏈接(connection)包裝以下。將conn的內容都放入r中,但是沒有進行讀取,讓步我們一會對其進行操作。 36 for { 37 line,err := r.ReadString('\n') //將r的內容也就是conn的數據按照換行符進行讀取。 38 if err == io.EOF { 39 conn.Close() 40 } 41 fmt.Print(line) 42 } 43 44 45 } 46 47 48 49 50 #以上代碼輸出結果如下: 51 111.13.101.208:80 52 172.16.3.210:64613 53 *net.TCPAddr 54 string 55 寫入的大小是: 18 56 HTTP/1.1 400 Bad Request 57 Date: Mon, 31 Jul 2017 10:41:48 GMT 58 Server: Apache 59 Content-Length: 226 60 Connection: Keep-Alive 61 Content-Type: text/html; charset=iso-8859-1 62 63 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> 64 <html><head> 65 <title>400 Bad Request</title> 66 </head><body> 67 <h1>Bad Request</h1> 68 <p>Your browser sent a request that this server could not understand.<br /> 69 </p> 70 </body></html>
4.io讀取http請求
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 9 package main 10 11 import ( 12 "net" 13 "log" 14 "fmt" 15 "reflect" 16 "io" 17 "os" 18 ) 19 20 func main() { 21 addr := "wwww.baidu.com:80" //定義主機名 22 conn,err := net.Dial("tcp",addr) //撥號操作,需要指定協議。 23 if err != nil { 24 log.Fatal(err) 25 } 26 fmt.Println("訪問公網IP地址以及端口是:",conn.RemoteAddr().String()) /*獲取“conn”中的公網地址。注意:最好是加上后面的String方法,因為他們的那些是不一樣的喲·當然你打印的時候 27 可以不加輸出結果是一樣的,但是你的內心是不一樣的喲!*/ 28 fmt.Printf("客戶端鏈接的地址及端口是:%v\n",conn.LocalAddr()) //獲取到本地的訪問地址和端口。 29 fmt.Println("“conn.LocalAddr()”所對應的數據類型是:",reflect.TypeOf(conn.LocalAddr())) 30 fmt.Println("“conn.RemoteAddr().String()”所對應的數據類型是:",reflect.TypeOf(conn.RemoteAddr().String())) 31 n,err := conn.Write([]byte("GET / HTTP/1.1\r\n\r\n")) //向服務端發送數據。用n接受返回的數據大小,用err接受錯誤信息。 32 if err != nil { 33 log.Fatal(err) 34 } 35 fmt.Println("寫入的大小是:",n) 36 io.Copy(os.Stdout,conn) 37 conn.Close() 38 } 39 40 41 42 43 #以上代碼輸出結果如下: 44 訪問公網IP地址以及端口是: 111.13.101.208:80 45 客戶端鏈接的地址及端口是:172.16.3.210:52409 46 “conn.LocalAddr()”所對應的數據類型是: *net.TCPAddr 47 “conn.RemoteAddr().String()”所對應的數據類型是: string 48 寫入的大小是: 18 49 HTTP/1.1 400 Bad Request 50 Date: Tue, 01 Aug 2017 02:35:11 GMT 51 Server: Apache 52 Content-Length: 226 53 Connection: Keep-Alive 54 Content-Type: text/html; charset=iso-8859-1 55 56 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> 57 <html><head> 58 <title>400 Bad Request</title> 59 </head><body> 60 <h1>Bad Request</h1> 61 <p>Your browser sent a request that this server could not understand.<br /> 62 </p> 63 </body></html>
三.服務端Socket;
1.串行服務端;
當客戶端鏈接過來的時候,我們服務端可以給客戶端回復特定的字符串等等。我們就以下面這段代碼為例子:
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import ( 11 "net" 12 "log" 13 "time" 14 ) 15 16 func main() { 17 addr := "0.0.0.0:8080" //表示監聽本地所有ip的8080端口,也可以這樣寫:addr := ":8080" 18 listener,err := net.Listen("tcp",addr) //使用協議是tcp,監聽的地址是addr 19 if err != nil { 20 log.Fatal(err) 21 } 22 defer listener.Close() //關閉監聽的端口
23 for { 24 conn,err := listener.Accept() //用conn接收鏈接 25 if err != nil { 26 log.Fatal(err) 27 } 28 conn.Write([]byte("Yinzhengjie\n")) //通過conn的wirte方法將這些數據返回給客戶端。 29 conn.Write([]byte("hello Golang\n")) 30 time.Sleep(time.Minute) //在結束這個鏈接之前需要睡一分鍾在結束當前循環。 31 conn.Close() //與客戶端斷開連接。 32 } 33 }
注意,我們需要在服務端運行代碼,然后在客戶端進行telnet連接,如果操作的呢?很簡單,我用了2台網絡設備做測試:
路由器連接:
防火牆連接:
我用兩個網絡設備在1分鍾內同時連接服務器,發現先連接的路由器收到了回復,然后就一直在sleep,沒有下文了,這個時候防火牆也在連接,但是一直是在連接狀態,也未出現斷開的情況,等過了一分鍾之后,分別出現了以下現象:
路由器連接:
防火牆連接:
小伙伴們或許發現了規律,當路由器斷開連接的時候,防火牆開始受到數據了,然后在過一分鍾后防火牆也斷開連接了。因為我的代碼中在執行寫的是睡眠一分鍾后就斷開連接。
一分鍾后防火牆連接:
2.並發服務端;
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import ( 11 "net" 12 "log" 13 "time" 14 ) 15 16 func Handle_conn(conn net.Conn) { //這個是在處理客戶端會阻塞的代碼。 17 conn.Write([]byte("Yinzhengjie\n")) //通過conn的wirte方法將這些數據返回給客戶端。 18 conn.Write([]byte("尹正傑是一個好男孩!\n")) 19 time.Sleep(time.Minute) 20 conn.Close() //與客戶端斷開連接。 21 } 22 23 func main() { 24 addr := "0.0.0.0:8080" //表示監聽本地所有ip的8080端口,也可以這樣寫:addr := ":8080" 25 listener,err := net.Listen("tcp",addr) 26 if err != nil { 27 log.Fatal(err) 28 } 29 defer listener.Close() 30 31 for { 32 conn,err := listener.Accept() //用conn接收鏈接 33 if err != nil { 34 log.Fatal(err) 35 } 36 go Handle_conn(conn) //開啟多個協程。 37 } 38 }
同意,我還是用路由器和防火牆做測試,發現這次效果迥然不同。
路由器連接效果:
防火牆連接效果:
我們很明顯發現,當兩個網絡設備同事去連接服務器的時候,此次的輸出結果是不一致的,2個客戶端同事獲得了回應,這就是服務器的並發效果,可以在同一時間處理多個鏈接。等待一分鍾后,兩個客戶端都會自動斷開了鏈接。
路由器一分鍾后狀態:
防火牆一分鍾后狀態:
3.web並發服務器;
其實我們寫的代碼也可以讓訪問對象是瀏覽器,這個時候我們返回其特定的html標簽標簽即可,代碼如下:
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import ( 11 "net" 12 "log" 13 ) 14 15 16 var content = `HTTP/1.1 200 OK 17 Date: Sat, 29 Jul 2017 06:18:23 GMT 18 Content-Type: text/html 19 Connection: Keep-Alive 20 Server: BWS/1.1 21 X-UA-Compatible: IE=Edge,chrome=1 22 BDPAGETYPE: 3 23 Set-Cookie: BDSVRTM=0; path=/ 24 25 <html> 26 <head name="尹正傑" age="25"> <!--標簽的開頭,其和面跟的內容(name="尹正傑")是標簽的屬性,其屬性可以定義多個。--> 27 <meta charset="UTF-8"/> <!--指定頁面編碼,--> 28 <meta http-equiv="refresh" content="30; Url=http://www.cnblogs.com/yinzhengjie/"> <!--這是做了一個界面的跳轉,表示30s不運行的話就跳轉到指定的URL--> 29 <title>尹正傑的個人主頁</title> <!--定義頭部的標題--> 30 </head> <!--標簽的結尾--> 31 32 <body> 33 <h1 style="color:red">尹正傑</h1> 34 <h1 style="color:green">hello golang</h1> 35 36 </body> 37 </html> 38 ` 39 40 func Handle_conn(conn net.Conn) { //這個是在處理客戶端會阻塞。 41 conn.Write([]byte(content)) //將html的代碼返回給客戶端,這樣客戶端在web上訪問就可以拿到指定字符。 42 conn.Close() 43 } 44 45 46 func main() { 47 addr := "0.0.0.0:8080" //表示監聽本地所有ip的8080端口,也可以這樣寫:addr := ":8080" 48 listener,err := net.Listen("tcp",addr) 49 if err != nil { 50 log.Fatal(err) 51 } 52 defer listener.Close() 53 54 for { 55 conn,err := listener.Accept() //用conn接收鏈接 56 if err != nil { 57 log.Fatal(err) 58 } 59 go Handle_conn(conn) //將接受來的鏈接交給該函數去處理。 60 } 61 }
客戶端訪問效果如下:
我們發現30s過后后頁面發生了變化如下:
通過這個案例,可能大家會想我能不能把文件的內容傳給客戶端呢?當然是可以的啦~寫法也很簡單,就直接把讀內容轉換成“[]byte”類型然后返回給客戶端即可,在這里我就不多廢話了。
四.socket鏈接的應用;
1.寫個FTP服務器初級版本;
服務端:
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import ( 11 "net" 12 "log" 13 "io" 14 "fmt" 15 "bufio" 16 "strings" 17 "os" 18 "path/filepath" 19 "io/ioutil" 20 ) 21 22 var ( 23 cmd string 24 file_name string 25 ) 26 27 func main() { 28 addr := "0.0.0.0:8080" //表示監聽本地所有ip的8080端口,也可以這樣寫:addr := ":8080" 29 listener,err := net.Listen("tcp",addr) 30 if err != nil { 31 log.Fatal(err) 32 } 33 defer listener.Close() 34 conn,err := listener.Accept() //用conn接收鏈接 35 if err != nil { 36 log.Fatal(err) 37 } 38 conn.Write([]byte("歡迎來到尹正傑迷你FTP服務器!")) 39 r := bufio.NewReader(conn) //將這個鏈接(connection)包裝以下。將conn的內容都放入r中,但是沒有進行讀取,讓步我們一會對其進行操作。 40 for { 41 line,err := r.ReadString('\n') //將r的內容也就是conn的數據按照換行符進行讀取。 42 if err == io.EOF { 43 conn.Close() 44 } 45 fmt.Print(line) 46 line = strings.TrimSpace(line) //去掉換行符。 47 fmt.Println(len(strings.Fields(line))) 48 if len(line) == 0 { //為了讓客戶端長時間和服務器通話。 49 continue 50 } 51 cmd = strings.Fields(line)[0] 52 if len(strings.Fields(line)) > 1 { 53 file_name = strings.Fields(line)[1] //需要獲取服務器的文件 54 } 55 pwd,err := os.Getwd() 56 if err != nil { 57 panic("獲取路徑出錯了!") 58 } 59 file_name = filepath.Join(pwd,file_name) 60 fmt.Println(file_name) 61 switch cmd{ 62 case "GET","get": 63 f,err := os.Open(file_name) //打開文件的內容。 64 if err != nil { 65 fmt.Println(err) 66 } 67 defer f.Close() 68 buf,err := ioutil.ReadAll(f) 69 if err != nil { 70 log.Print(err) 71 return 72 } 73 conn.Write(buf) 74 case "PUSH","push": 75 fmt.Println("上傳文件的語句") 76 conn.Write([]byte("上傳文件的命令\n")) 77 78 case "EXIT","exit": 79 //conn.Close() 80 return 81 default: 82 fmt.Println("您輸入的命令無效!") 83 conn.Write([]byte("您輸入的指令有問題!\n")) 84 } 85 } 86 }
客戶端:
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import ( 11 "net" 12 "log" 13 "fmt" 14 "bufio" 15 "os" 16 "io" 17 ) 18 19 var ( 20 cmd string 21 line string 22 ) 23 24 func main() { 25 addr := "172.16.3.210:8080" //定義主機名 26 conn,err := net.Dial("tcp",addr) //撥號操作,用於連接服務端,需要指定協議。 27 if err != nil { 28 log.Fatal(err) 29 } 30 31 buf := make([]byte,10240) //定義一個切片的長度是10240。 32 n,err := conn.Read(buf) //接收到的內容大小為我們提前定義好的大小。 33 if err != nil && err != io.EOF { //io.EOF在網絡編程中表示對端把鏈接關閉了。 34 log.Fatal(err) 35 } 36 fmt.Println(string(buf[:n])) //將接受的內容都讀取出來。 37 38 39 40 f := bufio.NewReader(os.Stdin) 41 for { 42 //fmt.Print("請輸入>>>:") 43 line,err = f.ReadString('\n') //定義一行的內容,結束標識符是換行符“\n” 44 fmt.Sscan(line,&cmd) 45 if len(line) == 1 { 46 continue 47 } 48 //fmt.Print(line) 49 go sender(conn,line) 50 } 51 conn.Close() //斷開TCP鏈接。 52 } 53 54 func sender(conn net.Conn ,line string) { 55 n,err := conn.Write([]byte(line)) //向服務端發送數據。用n接受返回的數據大小,用err接受錯誤信息。 56 if err != nil { 57 log.Fatal(err) 58 } 59 60 buf := make([]byte,10) //定義一個切片的長度是1024。 61 62 for { 63 n,err = conn.Read(buf) //接收到的內容大小。 64 if err == io.EOF { 65 conn.Close() 66 } 67 fmt.Print(string(buf[:n])) 68 } 69 return 70 }
測試結果:

1 歡迎來到尹正傑迷你FTP服務器! 2 get a.txt 3 11111 4 22222 5 33333 6 7 get \Day9\yinzhengjie.html 8 <!DOCTYPE html> <!--Doctype告訴瀏覽器使用什么樣的html或xhtml規范來解析html文檔。html這種模式兼容瀏覽器是最好的--> 9 <html lang="en"> 10 <head name="尹正傑" age="25"> <!--標簽的開頭,其和面跟的內容(name="尹正傑")是標簽的屬性,其屬性可以定義多個。--> 11 <meta charset="UTF-8"/> <!--指定頁面編碼,--> 12 <meta http-equiv="refresh" content="30; Url=http://www.cnblogs.com/yinzhengjie/"> <!--這是做了一個界面的跳轉,表示3s不運行的話就跳轉到指定的URL--> 13 <title>尹正傑的個人主頁</title> <!--定義頭部的標題--> 14 <meta name="keywords" content="開發者,博客園,開發者,程序猿,程序媛,極客,編程,代碼,開源,IT網站,Developer,Programmer,Coder,Geek,技術社區" /> <!--“content”定義關鍵字,其作用就是讓瀏覽器通過搜索關鍵字時,會匹配該網站,這就是說如果你沒有單獨給百度錢的話,這些關鍵字就尤為重要啦!--> 15 <meta name="description" content="博客園是一個面向開發者的知識分享社區。自創建以來,博客園一直致力並專注於為開發者打造一個純凈的技術交流社區,推動並幫助開發者通過互聯網分享知識,從而讓更多開發者從中受益。博客園的使命是幫助開發者用代碼改變世界。" /> <!--定義描述字符,其作用就告訴客戶你的這個網站是干嘛使用的。--> 16 <link rel="shortcut icon" href="https://baike.baidu.com/pic/%E9%82%93%E7%B4%AB%E6%A3%8B/6798196/0/d1a20cf431adcbef011db9bba6af2edda3cc9f66?fr=lemma&ct=single#aid=0&pic=d1a20cf431adcbef011db9bba6af2edda3cc9f66" type="image/x-icon" /> <!--定義頭部圖標--> 17 <meta http-equiv="x-ua-compatible" content="IE=Edge"> <!--這個是IE的瀏覽器生效的規則,如果你用的是谷歌,360等瀏覽器的話,這行規則不生效,如果你用的是IE瀏覽器的話,表示用IE最新的引擎去渲染HTML--> 18 </head> <!--標簽的結尾--> 19 <body> 20 <h1>尹正傑</h1><!--定義文件的內容,其中“h1”標簽中--> 21 <h2>尹正傑</h2> 22 <h3>尹正傑</h3> 23 <h4>尹正傑</h4> 24 <h5>尹正傑</h5> 25 <h6>尹正傑</h6> 26 <h1>You are a good boy!</h1> 27 <div style="width: 4000px"> <!--是其縮進代碼的腹肌標簽,給其定義寬度屬性是200像素大小--> 28 <h1>尹正傑</h1><!--塊級標簽:也叫父�即自己單獨占了一行空�標簽,��說是占它父級標簽的100%。作�間,或蔨:定義文件的內容--> 29 <h1>You are a good boy!</h1> 30 </div> <!--div的標簽的結尾--> 31 <p>素胚勾勒出青花筆鋒濃轉淡<br/>瓶身描繪的牡丹一如你初妝<br/>冉冉檀香��事我�透過窗宣紙上贐然<br/>�筆至此擱一半<渲染仕�br/>釉色�被私藏<br/>而僉�圖韻呠嫣然的一笑如含苞待放</p> <!--其中<br/>表示換行符的意思,<p></p>表示一個段落的意思。--> 32 <a>yinzhengjie</a> <!--內聯標簽,以a開頭的標簽都是內聯標簽,�的內容�些標簽��連接在一起的a>2017</a>。:--> 33 <"http://ww 34 <a href=w.cnblogs.com/yinzhengjie/" target="_blank">尹正傑博客</a> <!--a標簽特有的性能,重定向,通過href屬性定義需要跳轉的網站,通過target="_blank"表示新打開一個標簽頁並打開新的URL地址--> 35 36 <a href="#Y1">Golang第一!--a標簽特有的�章</a> <�錨,找��能,�ID為"Y1"的標簽--="#Y2">Gol> 37 <a href��</a> <!ang第二�"Y2"的標--找ID為簽--> 38 <a href="#Y3�三章</a">Golang�> <!--找ID為"Y3"的標簽--> 39 40 41 42 <div id="Y1" style="height:700px;background-color:antiquewhite"> <!--用id來定義標簽為"Y1",用style來定義高度為700像素,顏色用background-color來定義--> 43 Golang進階之路Day1<br/> 44 Go語言官方自稱,之所以開發Go 語言,是因為“近10年來開發程序之難讓我們有點沮喪”。 這一定位暗示了Go語言希望取代C和Java的地位,成為最流行的通用開發語言。博客地址:http://www.cnblogs.com/yinzhengjie/p/6482675.html 45 </div> 46 47 <div id="Y2" style="height:700px;background-color:rebeccapurple;"> 48 <br/>Golang進階之路Day2<br/> 49 前者大家應該都很熟悉,因為我在上一篇(http://www.cnblogs.com/yinzhengjie/p/6482675.html)關於GO的博客中用"go build"命令編譯不同的版本,但是在這里我們還是要演示一下go build的花式用法。博客地址:http://www.cnblogs.com/yinzhengjie/p/7000272.html 50 </div> 51 52 <div id="Y3" style="height:700px;background-color:brown;"> 53 Golang進階之路Day3<br/> 54 當然我這里只是介紹��冰山�了Golang�角,對Golang感興趣的小伙伴,可以看一下Golang官網的文檔說明。畢竟官方才是最權�出國內地址:https://golang.org/pkg/!博客威的,�tp://www.c��址:htnblogs.com/yinzhengjie/p/7043430.html 55 </div> 56 57 <!--功能最少的標簽,最純潔的易於加工的標簽--> 58 <div>我是塊標簽</div> 59 <span>我是內聯標簽</span> 60 61 <!--列表--> 62 <ul> <!--打印字符穿前面帶個小黑點--> 63 一</li> 64 <li>菜單二</li> 65 <li>菜單三</li> 66 </ul> 67 68 <ol> <!--打印字符串前面有數字標識--> 69 <li>第�<li>菜單 70 <li>第二章</li> 71 <li>第三��章</li>章</li> 72 ol> 73 <</ol> 74 75 <dd>北京<自帶縮進,可以用於寫新聞的�/dd> <!--��題--> 76 <dt>朝陽區</dt>亦庄�t> 77 <d區</dt> 78 �濟開發 <dt>�t> 79 <dt>海淀區</dt> 80 <dd>河�台區</d��</dd> 81 家庄</dt> 82 <dt>保定</d <dt>石d>陝西</dd> 83 <dt>西安</dt> 84 <dt>安康t> 85 <dl> 86 87 88 <!--表格--> 89 <table border="1"> <!--�</dt> 90 </o表格,�義一個�border="1��屬性�",表示加邊框�--> 91 <thead> <!--定義表頭信息--> 92 <tr> <��意思�!--'tr'表示定義一行的�面的內容由子栰據,里�簽<th></th>實現- <th>姓名</th> <!--'th'�-> 93 ��義同一行的內是說只�容,也就��帶有這個標簽的且在其父標簽"tr"標簽中就是寫的同�。--> 94 ��行內�>年齡</t <th <td>性�h> 95 ��</td> 96 </tr> 97 </thead> 98 <tbody> <!--��的內容--> 99 <tr> <!--'tr'表示定義表暄數據�每一行�其定義的是行的操作。--> 100 <td>尹正傑</tthead中的'th'用�d> <!--與�相同。只不過'th’有�!--> 101 <td>�粗效果 <td25</td> 102 103 <!--'tr'表示每� 104 <tr>��行的數據--> 105 <td>尹正傑</td> <!--‘<td></t�定義的d>’標�是列的操作--> 106 <td colspan="2">26</td> <!--表示這䭾占兩券�'td'標�的空間--> 107 </tr> 108 'tr'表示<tr> <!--每一行的數據--> 109 <td>yinzhengjie</td> <!--‘<td></td>’標簽定��的操�義的是�--> 110 d'標簽� 111 </tr> 112 <tr> <!--'tr'表示每�據--> 113 <td��行的�>yinzhengjie</td> <!--‘<td><簽定義的是列�/td>’標�操作--> 114 <td >26</t-表示這個'td'標簽占兩列的空�d> <!-�--> 115 </tr> 116 </tbody> 117 </table> 118 </body> 119 </html>
該腳本並沒有完成完整的FTP,只是寫了一個引子,放在這里,如果以后我有興趣了,可能會把它優化一下,我把這段代碼貼在這里就是為了提供一個網絡socket的傳輸案例,方便我以后查看,哈哈~
2.后台聊天程序;
該程序主要實現了,不同用戶登錄服務器可以輸入相同的密碼,然后大家可以互相通信的小程序。具體代碼和注釋如下:
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import ( 11 "bufio" 12 "fmt" 13 "log" 14 "net" 15 "strings" 16 "time" 17 ) 18 19 var globalRoom *Room = NewRoom() 20 21 type Room struct { 22 users map[string]net.Conn 23 } 24 25 func NewRoom() *Room { 26 return &Room{ 27 users: make(map[string]net.Conn), 28 } 29 } 30 31 func (r *Room) Join(user string, conn net.Conn) { 32 _, ok := r.users[user] 33 if ok { 34 r.Leave(user) //如果存在用戶user就提出之前的鏈接,調用Leave方法實現。 35 } 36 r.users[user] = conn 37 fmt.Printf("%s 登錄成功。\n", user) 38 conn.Write([]byte(user + ":加入聊天室!\n")) 39 } 40 41 func (r *Room) Leave(user string) { 42 conn, ok := r.users[user] 43 if !ok { 44 fmt.Printf("%v用戶不存在!", user) 45 } 46 conn.Close() //如果存在用戶就斷開鏈接。 47 delete(r.users, user) //將用戶從字典中刪除。 48 fmt.Printf("%s 離開", user) 49 } 50 51 func (r *Room) Broadcast(who string, msg string) { 52 time_info := time.Now().Format("2006年01月02日 15:04:05") //這個是對日期定義一個格式化輸出。告訴你一個記住它的方法:2006-01-02 15:04:05對應着2006 1(01) 2(02) 3(15) 4(04) 5(05) 哈哈 53 tosend := fmt.Sprintf("%v %s:%s\n", time_info,who, msg) 54 for user, conn := range r.users { //遍歷所有用戶, 55 if user == who { 56 continue //當發現用戶是自己就不發送數據。即跳過循環。 57 } 58 conn.Write([]byte(tosend)) //將數據發送給登陸的用戶。 59 } 60 } 61 62 func Handle_Conn(conn net.Conn) { 63 defer conn.Close() 64 r := bufio.NewReader(conn) //將用戶的輸入存入“r”中,方便一會我們按塊讀取。 65 line, err := r.ReadString('\n') 66 if err != nil { 67 fmt.Println(err) 68 return 69 } 70 line = strings.TrimSpace(line) 71 fields := strings.Fields(line) 72 if len(fields) != 2 { 73 conn.Write([]byte("您輸入的字符串用戶名活密碼無效,程序強制退出!\n")) 74 return 75 } 76 user := fields[0] 77 password := fields[1] 78 if password != "123" { 79 return 80 } 81 globalRoom.Join(user, conn) 82 globalRoom.Broadcast("System", fmt.Sprintf("%s join room", user)) 83 for { //獲取用戶的輸入。 84 conn.Write([]byte("按回車鍵發送消息:>>>"))//這里是給客戶端增加一個提示符 85 line, err := r.ReadString('\n') //循環讀取用戶輸入的內容。換行符為“\n” 86 if err != nil { //當用戶主動關閉連接是,會出現報錯就直接直接終止循環。 87 break 88 } 89 line = strings.TrimSpace(line) //去掉換行符 90 fmt.Println(user,line) 91 globalRoom.Broadcast(user, line) // 將用戶輸入的消息進行廣播。 92 } 93 globalRoom.Broadcast("System", fmt.Sprintf("%s Leave room", user)) 94 globalRoom.Leave(user) //踢掉用戶。 95 } 96 97 func main() { 98 addr := "0.0.0.0:8888" 99 listener, err := net.Listen("tcp", addr) 100 if err != nil { 101 log.Fatal(err) 102 } 103 defer listener.Close() 104 for { 105 conn, err := listener.Accept() 106 if err != nil { 107 log.Fatal(err) 108 } 109 go Handle_Conn(conn) 110 } 111 }