zoukankan      html  css  js  c++  java
  • 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
    ",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
    
    ")) //向服务端发送数据。用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
    
    ")) //向服务端发送数据。用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
    
    ")) //向服务端发送数据。用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('
    ') //将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
    ",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
    
    ")) //向服务端发送数据。用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 ")) //通过conn的wirte方法将这些数据返回给客户端。 29 conn.Write([]byte("hello Golang ")) 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
    "))  //通过conn的wirte方法将这些数据返回给客户端。
    18     conn.Write([]byte("尹正杰是一个好男孩!
    "))
    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('
    ') //将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("上传文件的命令
    "))
    77 
    78         case "EXIT","exit":
    79             //conn.Close()
    80             return
    81         default:
    82             fmt.Println("您输入的命令无效!")
    83             conn.Write([]byte("您输入的指令有问题!
    "))
    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('
    ') //定义一行的内容,结束标识符是换行符“
    ”
    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 Day9yinzhengjie.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=" 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 登录成功。
    ", user)
     38     conn.Write([]byte(user + ":加入聊天室!
    "))
     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
    ", 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('
    ')
     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("您输入的字符串用户名活密码无效,程序强制退出!
    "))
     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('
    ') //循环读取用户输入的内容。换行符为“
    ”
     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 }
  • 相关阅读:
    高级架构进阶之HashMap源码就该这么学
    MySQL底层索引剖析
    一篇文章把本该属于你的源码天赋还给你
    不懂RPC实现原理怎能实现架构梦
    观《亿级流量网站架构核心技术》一书有感
    高效程序员如何优雅落地需求
    职场软技能:开启程序员的“破冰之旅”
    获取ScrollView的onScrollListener
    Android自定义控件之圆形进度条ImageView
    Android之內置、外置SDCard
  • 原文地址:https://www.cnblogs.com/yinzhengjie/p/7261584.html
Copyright © 2011-2022 走看看