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 :yinzhengjie4 Blog:/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@6 */
7
8 package main9
10 import(11 "net"
12 "log"
13 "fmt"
14 "reflect"
15 "io"
16 )17
18 func main() {19 addr := ":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.TCPAddr52 “conn.RemoteAddr().String()”所对应的数据类型是: string53 向服务端发送的数据大小是: 18
54 HTTP/1.1 400Bad Request55 Date: Mon, 31 Jul 06:22:41GMT56 Server: Apache57 Content-Length: 226
58 Connection: Keep-Alive59 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 anda copy.4 if wt, ok :=src.(WriterTo); ok {5 returnwt.WriteTo(dst)6 }7 // Similarly, ifthe writer has a ReadFrom method, use it to do the copy.8 if rt, ok :=dst.(ReaderFrom); ok {9 returnrt.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 =ew23 break
24 }25 if nr !=nw {26 err =ErrShortWrite27 break
28 }29 }30 if er !=nil {31 if er !=EOF {32 err =er33 }34 break
35 }36 }37 returnwritten, err38 }
猛戳我可以看io.Copy的实现方式。
2.按照指定大小循环读取;
1 /*
2 #!/usr/bin/env gorun
3 @author :yinzhengjie4 Blog:/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@6 */
7
8 package main9
10 import(11 "net"
12 "log"
13 "fmt"
14 "reflect"
15 "io"
16 )17
18 func main() {19 addr := ":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.TCPAddr55 string56 写入的大小是: 18
57 HTTP/1.1 400Bad Request58 Date: Mon, 31 Jul 10:38:42GMT59 Server: Apache60 Content-Length: 226
61 Connection: Keep-Alive62 Content-Type: text/html; charset=iso-8859-1
63
64
65
66
400 Bad Request
67
68
Bad Request
69
Your browser sent a request that this server could not understand.
70
71
3.按行读取;
1 /*
2 #!/usr/bin/env gorun
3 @author :yinzhengjie4 Blog:/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@6 */
7
8 package main9
10 import(11 "net"
12 "log"
13 "fmt"
14 "reflect"
15 "io"
16 "bufio"
17 )18
19 func main() {20 addr := ":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.TCPAddr54 string55 写入的大小是: 18
56 HTTP/1.1 400Bad Request57 Date: Mon, 31 Jul 10:41:48GMT58 Server: Apache59 Content-Length: 226
60 Connection: Keep-Alive61 Content-Type: text/html; charset=iso-8859-1
62
63
64
65
400 Bad Request
66
67
Bad Request
68
Your browser sent a request that this server could not understand.
69
70
4.io读取http请求
1 /*
2 #!/usr/bin/env gorun
3 @author :yinzhengjie4 Blog:/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@6 */
7
8
9 package main10
11 import(12 "net"
13 "log"
14 "fmt"
15 "reflect"
16 "io"
17 "os"
18 )19
20 func main() {21 addr := ":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.TCPAddr47 “conn.RemoteAddr().String()”所对应的数据类型是: string48 写入的大小是: 18
49 HTTP/1.1 400Bad Request50 Date: Tue, 01 Aug 02:35:11GMT51 Server: Apache52 Content-Length: 226
53 Connection: Keep-Alive54 Content-Type: text/html; charset=iso-8859-1
55
56
57
58
400 Bad Request
59
60
Bad Request
61
Your browser sent a request that this server could not understand.
62
63
三.服务端Socket;
1.串行服务端;
当客户端链接过来的时候,我们服务端可以给客户端回复特定的字符串等等。我们就以下面这段代码为例子:
1 /*
2 #!/usr/bin/env gorun
3 @author :yinzhengjie4 Blog:/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@6 */
7
8 package main9
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,监听的地址是addr19 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 :yinzhengjie4 Blog:/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@6 */
7
8 package main9
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 :yinzhengjie4 Blog:/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@6 */
7
8 package main9
10 import(11 "net"
12 "log"
13 )14
15
16 var content = `HTTP/1.1 200OK17 Date: Sat, 29 Jul 06:18:23GMT18 Content-Type: text/html19 Connection: Keep-Alive20 Server: BWS/1.1
21 X-UA-Compatible: IE=Edge,chrome=1
22 BDPAGETYPE: 3
23 Set-Cookie: BDSVRTM=0; path=/
24
25
26
27
28
29
尹正杰的个人主页
30
31
32
33
尹正杰
34
hello golang
35
36
37
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 :yinzhengjie4 Blog:/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@6 */
7
8 package main9
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 string24 file_name string25 )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 :yinzhengjie4 Blog:/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@6 */
7
8 package main9
10 import(11 "net"
12 "log"
13 "fmt"
14 "bufio"
15 "os"
16 "io"
17 )18
19 var (20 cmd string21 line string22 )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.txt3 11111
4 22222
5 33333
6
7 get \Day9\yinzhengjie.html8
9
10
11
12
13
尹正杰的个人主页
14
15
16
17
18
19
20
尹正杰
21
尹正杰
22
尹正杰
23
尹正杰
24
尹正杰
25
尹正杰
26
You are a good boy!
27
28
尹正杰
29
You are a good boy!
30
31
素胚勾勒出青花笔锋浓转淡
瓶身描绘的牡丹一如你初妆
冉冉檀香��事我�透过窗宣纸上赆然
�笔至此搁一半釉色�被私藏
而佥�图韵呠嫣然的一笑如含苞待放
32 yinzhengjie
33
34 尹正杰博客
35
36 Golang第一!--a标签特有的�章 Gol>
37
38 Golang�>
39
40
41
42
43 Golang进阶之路Day1
44 Go语言官方自称,之所以开发Go 语言,是因为“近来开发程序之难让我们有点沮丧”。 这一定位暗示了Go语言希望取代C和Java的地位,成为最流行的通用开发语言。博客地址:/yinzhengjie/p/6482675.html45
46
47
48
Golang进阶之路Day2
49 前者大家应该都很熟悉,因为我在上一篇(/yinzhengjie/p/6482675.html)关于GO的博客中用"go build"命令编译不同的版本,但是在这里我们还是要演示一下go build的花式用法。博客地址:/yinzhengjie/p/7000272.html50
51
52
53 Golang进阶之路Day3
54 当然我这里只是介绍��冰山�了Golang�角,对Golang感兴趣的小伙伴,可以看一下Golang官网的文档说明。毕竟官方才是最权�出国内地址:/pkg/!博客威的,�tp://www.c��址:/yinzhengjie/p/7043430.html55
56
57
58
我是块标签
59 我是内联标签
60
61
62
63 一
64
菜单二
65
菜单三
66
67
68
69
第�菜单70 第二章
71
第三��章章
72 ol>
73 <
74
75
北京
76
朝阳区亦庄�t>
77
78 �济开发
�t>
79
海淀区
80
河�台区
81 家庄
82
保定石d>陕西
83
西安
84
安康t>
85
86
87
88
89
91
92
实现- 姓名
94 ��行内�>年龄性�h>
95 ��
96
97
98
99
100
尹正杰
101
�粗效果
102
103
105
尹正杰
106
26
107
108 'tr'表示
109
yinzhengjie
110 d'标签�
111
112
113
yinzhengjie
114
26
115
116
117
118
119
以上客户端使用方法戳我
该脚本并没有完成完整的FTP,只是写了一个引子,放在这里,如果以后我有兴趣了,可能会把它优化一下,我把这段代码贴在这里就是为了提供一个网络socket的传输案例,方便我以后查看,哈哈~
2.后台聊天程序;
该程序主要实现了,不同用户登录服务器可以输入相同的密码,然后大家可以互相通信的小程序。具体代码和注释如下:
1 /*
2 #!/usr/bin/env gorun
3 @author :yinzhengjie4 Blog:/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@6 */
7
8 package main9
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.Conn23 }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 ifok {34 r.Leave(user) //如果存在用户user就提出之前的链接,调用Leave方法实现。35 }36 r.users[user] =conn37 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("01月02日 15:04:05") //这个是对日期定义一个格式化输出。告诉你一个记住它的方法:-01-02 15:04:05对应着 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 }