• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

GO语言的进阶之路-网络编程之socket

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

                          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:[email protected]
 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:[email protected]
 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:[email protected]
 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:[email protected]
 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:[email protected]
 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:[email protected]
 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:[email protected]
 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 
 
                       
                    
                    

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
体验go语言的风骚式编程发布时间:2022-07-10
下一篇:
windows下《Go Web编程》之Go开发工具 - 落叶虽美只活一世Live2D发布时间:2022-07-10
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap