GO語言的進階之路-網絡編程之socket


                          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   }
猛戳我可以看io.Copy的實現方式。

 

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 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM