go --socket通讯(TCP服务端与客户端的实现)
这篇文章主要使用Go语言实现一个简单的TCP服务器和客户端。
服务器和客户端之间的协议是 ECHO, 这个RFC 862定义的一个简单协议。
为什么说这个协议很简单呢, 这是因为服务器只需把收到的客户端的请求数据发给这个客户端即可,其它什么功能都不做。
首先声明, 我绝对是一个Golang的初学者,十四、五年的编程时间我主要使用Java来做开发,这篇文章主要记录我学习go网络编程的体验。如果你认为这篇文章有错误或者不好的写法,请在回复中添加你的意见。
简单介绍
尽管OSI(开放系统互联)协议从未被完整地实现过,但它仍对分布式系统的讨论和设计产生了十分重要的影响。它的结构大致为下图所示:
当OSI标准模型正在为实现细节闹得不可开交时,DARPA互联网技术项目却在忙着构建TCP/IP协议。它们取得了极大的成功,并引领了Internet(首字母大写),因为这是个更简单的层次结构:
尽管现在到处都是TCP/IP协议,但它并不是唯一存在的。还有些协议占有重要的地位,比如:
- Firewire
- USB
- Bluetooth
- WiFi
多年的发展,使得IP和TCP/UDP协议基本上就等价于网络协议栈。例如, 蓝牙定义了物理层和协议层,但在其上任然是IP协议栈,可以在许多蓝牙设备之间使用互联网编程技术。同样, 开发4G无线手机技术,如LTE(Long Term Evolution)也将使用IP协议栈。
在OIS或TCP/IP协议栈层与层之间的通信,是通过将数据包从一个层发送到下一个层,最终穿过整个网络的。每一层都有必须保持其自身层的管理信息。从上层接收到的数据包在向下传递时,会添加头信息。在接收端,这些头信息会在向上传递时移除。
例如,TFTP(普通文件传输协议)将文件从一台计算机移动到另一台上。它使用IP协议上的UDP协议,该协议可通过以太网发送。看起来就像这样
最终在以太网上传输的数据,就是图中最底层的那个数据。
为了两个计算机进行通信,就必须建立一个路径,使他们能够在一个会话中发送至少一条消息。有两个主要的模型:
- 面向连接模型, 如TCP
- 无连接模型, 如UDP, IP
服务运行在主机。通常它们的生命期很长,同时被设计成等待请求和响应请求。当前有各种类型的服务,通过各种方法向客户提供服务。互联网的世界基于TCP和UDP这两种通信方法提供许多这些服务,虽然也有其他通信协议如SCTP伺机取代。许多其他类型的服务,例如点对点, 远过程调用, 通信代理, 和许多其他也建立在TCP和UDP之上。
服务存活于主机内。IP地址可以定位主机。但在每台计算机上可能会提供多种服务,需要一个简单的方法对它们加以区分。TCP,UDP,SCTP或者其他协议使用端口号来加以区分。这里使用一个1到65,535的无符号整数,每个服务将这些端口号中的一个或多个相关联。
有很多“标准”的端口。Telnet服务通常使用端口号23的TCP协议。DNS使用端口号53的TCP或UDP协议。FTP使用端口21和20的命令,进行数据传输。HTTP通常使用端口80,但经常使用,端口8000,8080和8088,协议为TCP。X Window系统往往需要端口6000-6007,TCP和UDP协议。
在Unix系统中, /etc/services文件列出了常用的端口。Go语言有一个函数可以获取该文件。
1
|
func LookupPort(network, service string) (port int, err os.Error)
|
Go提供IP, IP掩码, TCPAddr, UDPAddr, 网卡,主机查询这些对象的操作函数。
当你知道如何通过网络和端口ID查找一个服务时,然后呢?如果你是一个客户端,你需要一个API,让您连接到服务,然后将消息发送到该服务,并从服务读取回复。
如果你是一个服务器,你需要能够绑定到一个端口,并监听它。当有消息到来,你需要能够读取它并回复客户端。
net.TCPConn
是允许在TCP客户端和TCP服务器之间的全双工通信的Go类型。两种主要方法是
1
2
|
func (c *TCPConn) Write(b []byte) (n int, err os.Error)
func (c *TCPConn) Read(b []byte) (n int, err os.Error)
|
TCPConn被客户端和服务器用来读写消息。
ECHO服务器
在一个服务器上注册并监听一个端口。然后它阻塞在一个"accept"操作,并等待客户端连接。当一个客户端连接, accept调用返回一个连接(connection)对象。ECHO服务非常简单,只是客户端, 关闭该连接的请求数据写回到客户端,就像回声一样,直到某一方关闭连接。
1
2
|
func ListenTCP(net string, laddr *TCPAddr) (l *TCPListener, err os.Error)
func (l *TCPListener) Accept() (c Conn, err os.Error)
|
net参数可以设置为字符串"tcp", "tcp4"或者"tcp6"中的一个。如果你想监听所有网络接口,IP地址应设置为0。 如果你只是想监听一个特定网络接口,IP地址可以设置为该网络接口的地址。如果端口设置为0,操作系统会为你选择一个端口。否则,你可以选择你自己的。需要注意的是,在Unix系统上,除非你是监控系统,否则不能监听低于1024的端口,小于128的端口是由IETF标准化。该示例程序选择端口1200没有特别的原因。TCP地址如下":1200" - 所有网络接口, 端口1200。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
package main
import (
"flag"
"fmt"
"io"
"net"
"os"
)
var host = flag.String("host", "", "host")
var port = flag.String("port", "3333", "port")
func main() {
flag.Parse()
var l net.Listener
var err error
l, err = net.Listen("tcp", *host+":"+*port)
if err != nil {
fmt.Println("Error listening:", err)
os.Exit(1)
}
defer l.Close()
fmt.Println("Listening on " + *host + ":" + *port)
for {
conn, err := l.Accept()
if err != nil {
fmt.Println("Error accepting: ", err)
os.Exit(1)
}
//logs an incoming message
fmt.Printf("Received message %s -> %s \n", conn.RemoteAddr(), conn.LocalAddr())
// Handle connections in a new goroutine.
go handleRequest(conn)
}
}
func handleRequest(conn net.Conn) {
defer conn.Close()
for {
io.Copy(conn, conn)
}
}
|
执行go run echoserver.go
启动服务器。
ECHO客户端
一旦客户端已经建立TCP服务, 就可以"拨号"了. 如果成功,该调用返回一个用于通信的TCPConn
。客户端和服务器通过它交换消息。通常情况下,客户端使用TCPConn
写入请求到服务器, 并从TCPConn
的读取响应。持续如此,直到任一(或两者)的两侧关闭连接。客户端使用该函数建立一个TCP连接。
1
|
func DialTCP(net string, laddr, raddr *TCPAddr) (c *TCPConn, err os.Error)
|
其中laddr
是本地地址,通常设置为nil。 raddr
是一个服务的远程地址, net是一个字符串,可以根据你的需要设置为"tcp4", "tcp6"或"tcp"中的一个。
在介绍实现时,我们需要介绍同步机制, 因为客户端发送和接收是在两个goroutine中。 main函数中如果不加上同步机制, 客户端还没有发送接收就执行完了。
我们实现了两种同步方式。 当然还有其它方式, 如time.Sleep(60*1000)
或者等待从命令行输入,不过看起来有点傻。
Go格言
Share memory by communicating, don't communicate by sharing memory
使用Channel等待goroutine完成
比如老套的方式通过channel实现同步。 读和写完成后分别往channel中写入"done"。 main读取channel中的值,当两个done都读取到后就知道读写已经完成。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
package main
import (
"flag"
"fmt"
"net"
"os"
"strconv"
)
var host = flag.String("host", "localhost", "host")
var port = flag.String("port", "3333", "port")
func main() {
flag.Parse()
conn, err := net.Dial("tcp", *host+":"+*port)
if err != nil {
fmt.Println("Error connecting:", err)
os.Exit(1)
}
defer conn.Close()
fmt.Println("Connecting to " + *host + ":" + *port)
done := make(chan string)
go handleWrite(conn, done)
go handleRead(conn, done)
fmt.Println(<-done)
fmt.Println(<-done)
}
func handleWrite(conn net.Conn, done chan string) {
for i := 10; i > 0; i-- {
_, e := conn.Write([]byte("hello " + strconv.Itoa(i) + "\r\n"))
if e != nil {
fmt.Println("Error to send message because of ", e.Error())
break
}
}
done <- "Sent"
}
func handleRead(conn net.Conn, done chan string) {
buf := make([]byte, 1024)
reqLen, err := conn.Read(buf)
if err != nil {
fmt.Println("Error to read message because of ", err)
return
}
fmt.Println(string(buf[:reqLen-1]))
done <- "Read"
}
|
net.Dial
建立连接, handleWrite
发送十个请求, handleRead
接收服务器的响应。一旦完成,往channel中写done。
执行go run echoclient.go
启动服务器。
使用WaitGroup等待goroutine完成
上面的方式虽好,但是不够灵活。我们需要明确知道有多少个done。 如果增加若干个goroutine,修改起来比较麻烦。
所以还是使用sync
包的WaitGroup
比较灵活。 它类似Java中的CountDownLatch。
A WaitGroup waits for a collection of goroutines to finish. The main goroutine calls Add to set the number of goroutines to wait for. Then each of the goroutines runs and calls Done when finished. At the same time, Wait can be used to block until all goroutines have finished.
将上面的例子修改如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
package main
import (
"bufio"
"flag"
"fmt"
"net"
"os"
"strconv"
"sync"
)
var host = flag.String("host", "localhost", "host")
var port = flag.String("port", "3333", "port")
func main() {
flag.Parse()
conn, err := net.Dial("tcp", *host+":"+*port)
if err != nil {
fmt.Println("Error connecting:", err)
os.Exit(1)
}
defer conn.Close()
fmt.Println("Connecting to " + *host + ":" + *port)
var wg sync.WaitGroup
wg.Add(2)
go handleWrite(conn, &wg)
go handleRead(conn, &wg)
wg.Wait()
}
func handleWrite(conn net.Conn, wg *sync.WaitGroup) {
defer wg.Done()
for i := 10; i > 0; i-- {
_, e := conn.Write([]byte("hello " + strconv.Itoa(i) + "\r\n"))
if e != nil {
fmt.Println("Error to send message because of ", e.Error())
break
}
}
}
func handleRead(conn net.Conn, wg *sync.WaitGroup) {
defer wg.Done()
reader := bufio.NewReader(conn)
for i := 1; i <= 10; i++ {
line, err := reader.ReadString(byte('\n'))
if err != nil {
fmt.Print("Error to read message because of ", err)
return
}
fmt.Print(line)
}
}
|
wg.Add(2)
设定等待两个goroutines, 然后调用wg.Wait()
等待goroutines完成。 当goroutine完成时, 调用wg.Done()
。 使用起来相当简洁。
参考
- http://tools.ietf.org/html/rfc862
- http://loige.com/simple-echo-server-written-in-go-dockerized/
- http://nathanleclaire.com/blog/2014/02/15/how-to-wait-for-all-goroutines-to-finish-executing-before-continuing/
- http://jan.newmarch.name/go/zh/
- https://talks.golang.org/2012/concurrency.slide
- http://jimmyfrasche.github.io/go-reflection-codex/
- https://sites.google.com/site/gopatterns/
- https://github.com/golang-samples
- http://golang-examples.tumblr.com/
- https://code.google.com/p/go-wiki/wiki/Articles
- https://github.com/mindreframer/golang-stuff
go --socket通讯(TCP服务端与客户端的实现)的更多相关文章
- socket创建TCP服务端和客户端
看情况选择相对应的套接字*面向连接的传输--tcp协议--可靠的--流式套接字(SOCK_STREAM)*面向无连接的传输--udp协议--不可靠的--数据报套接字(SOCK_DGRAM) 在liun ...
- QTcpSocket-Qt使用Tcp通讯实现服务端和客户端
版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址: 本文标题:QTcpSocket-Qt使用Tcp通讯实现服务端和客户端 本文地址:https:// ...
- QUdpSocket-Qt使用Udp通讯实现服务端和客户端
版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址: 本文标题:QUdpSocket-Qt使用Udp通讯实现服务端和客户端 本文地址:https:// ...
- 【转】TCP/UDP简易通信框架源码,支持轻松管理多个TCP服务端(客户端)、UDP客户端
[转]TCP/UDP简易通信框架源码,支持轻松管理多个TCP服务端(客户端).UDP客户端 目录 说明 TCP/UDP通信主要结构 管理多个Socket的解决方案 框架中TCP部分的使用 框架中UDP ...
- swoole创建TCP服务端和客户端
服务端: server.php <?php //创建Server对象,监听 127.0.0.1:9501端口 $serv = new swoole_server("127.0.0 ...
- 基于Select模型的Windows TCP服务端和客户端程序示例
最近跟着刘远东老师的<C++百万并发网络通信引擎架构与实现(服务端.客户端.跨平台)>,Bilibili视频地址为C++百万并发网络通信引擎架构与实现(服务端.客户端.跨平台),重新复习下 ...
- TCP/UDP简易通信框架源码,支持轻松管理多个TCP服务端(客户端)、UDP客户端
目录 说明 TCP/UDP通信主要结构 管理多个Socket的解决方案 框架中TCP部分的使用 框架中UDP部分的使用 框架源码结构 补充说明 源码地址 说明 之前有好几篇博客在讲TCP/UDP通信方 ...
- Java TCP服务端向客户端发送图片
/** * 1.创建TCP服务端,TCP客户端 * 2.服务端等待客户端连接,客户端连接后,服务端向客户端写入图片 * 3.客户端收到后进行文件保存 * @author Administrator * ...
- python创建tcp服务端和客户端
1.tcp服务端server from socket import * from time import ctime HOST = '' PORT = 9999 BUFSIZ = 1024 ADDR ...
随机推荐
- C语言编程题001
有一颗树,一年两个生长周期,春天它长高一倍,夏天长高1m,问N个周期后树有多高?假设从春天开始树高为1m,第0个周期树高为1m. 要求:1.可以同时输入多个生长周期 如:3//表示下面有几个生长周期 ...
- Hadoop架构的初略总结(1)
Hadoop架构的初略总结(1) Hadoop是一个开源的分布式系统基础架构,此架构可以帮助用户可以在不了解分布式底层细节的情况下开发分布式程序. 首先我们要理清楚几个问题. 1.我们为什么需要Had ...
- 【homework #1】第一次作业被虐感受
当大二暑假结束,我发现我还是没有熟练掌握一门编程语言时,我就知道苦日子要来了. 这不,才开学第三周,就已经被虐的体无完肤了.连编译课用C语言写的词法分析,都要写很久.Debug很久才能写出来,更别提大 ...
- jupyter-notebook快捷键的使用
jupyter-notebook快捷键的使用 工具有个键盘图标可以看所有快捷键 Esc + F 在代码中查找.替换 Esc + O 在cell和输出结果间切换. Shift + J 或 Shift + ...
- zookeeper Zookeeper
这是ZooKeeper客户端库的主要类.使用一个ZooKeeper服务,应用程序必须首先实例化ZooKeeper类的对象.所有的迭代都将通过调用ZooKeeper类的方法来完成.除非另有说明,该类的方 ...
- Android之Bitmap 高效加载
一张图片(BitMap)占用的内存=图片长度*图片宽度*单位像素占用的字节数 图片格式(Bitmap.Config) 一张100*100的图片占用内存的大小 ALPHA_8 图片长度*图片宽度 100 ...
- BZOJ4002 [JLOI2015]有意义的字符串 【数学 + 矩乘】
题目链接 BZOJ4002 题解 容易想到\(\frac{b + \sqrt{d}}{2}\)是二次函数\(x^2 - bx + \frac{b^2 - d}{4} = 0\)的其中一根 那么就有 \ ...
- 洛谷P3832 [NOI2017]蚯蚓排队 【链表 + 字符串hash】
题目链接 洛谷P3832 题解 字符串哈希然后丢到hash表里边查询即可 因为\(k \le 50\),1.2操作就暴力维护一下 经复杂度分析会发现直接这样暴力维护是对的 一开始自然溢出WA了,还以为 ...
- svg图片做图标字体
https://icomoon.io 这个网站,把svg变图标
- 《Linux命令行与shell脚本编程大全 第3版》Linux命令行---35
以下为阅读<Linux命令行与shell脚本编程大全 第3版>的读书笔记,为了方便记录,特地与书的内容保持同步,特意做成一节一次随笔,特记录如下: