网络编程是并发大显身手的一个领域,由于服务器是最典型的需要同时处理很多连接的程序,这些连接一般来自于彼此独立的客户端。

本小节,我们会讲解go语言的net包,这个包提供编写一个网络客户端或者服务器程序的基本组件,通信可以是使用TCP,UDP或者Unix domain sockets。

我们的第一个例子是一个顺序执行的时钟服务器,它会每隔一秒钟将当前时间写到客户端:

// Clock1 is a TCP server that periodically writes the time.
package main import (
"io"
"log"
"net"
"time"
) func main() {
listener, err := net.Listen("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
} for {
conn, err := listener.Accept()
if err != nil {
log.Print(err) // e.g., connection aborted
continue
}
handleConn(conn) // handle one connection at a time
}
} func handleConn(c net.Conn) {
defer c.Close()
for {
_, err := io.WriteString(c, time.Now().Format("15:04:05\n"))
if err != nil {
return // e.g., client disconnected
}
time.Sleep(1 * time.Second)
}
}

  


 

Listen函数创建了一个net.Listener的对象,这个对象会监听一个网络端口上到来的连接,在这个例子里我们用的是TCP的localhost:8000端口。

listener对象的Accept方法会直接阻塞,直到一个新的连接被创建,然后会返回一个net.Conn对象来表示这个连接。

handleConn函数会处理一个完整的客户端连接。在一个for死循环中,用time.Now()获取当前时刻,然后写到客户端。由于net.Conn实现了io.Writer接口,我们可以直接向其写入内容。这个死循环会一直执行,直到写入失败。最可能的原因是客户端主动断开连接。这种情况下handleConn函数会用defer调用关闭服务器侧的连接,然后返回到主函数,继续等待下一个连接请求。

time.Time.Format方法提供了一种格式化日期和时间信息的方式。它的参数是一个格式化模板,标识如何来格式化时间,而这个格式化模板限定为Mon Jan 2 03:04:05PM 2006 UTC-0700。有8个部分(周几,月份,一个月的第几天,等等)。可以以任意的形式来组合前面这个模板;出现在模板中的部分会作为参考来对时间格式进行输出。在上面的例子中我们只用到了小时、分钟和秒。

time包里定义了很多标准时间格式,比如time.RFC1123。在进行格式化的逆向操作time.Parse时,也会用到同样的策略。(译注:这是go语言和其它语言相比比较奇葩的一个地方。。你需要记住格式化字符串是1月2日下午3点4分5秒零六年UTC-0700,而不像其它语言那样Y-m-d H:i:s一样,当然了这里可以用1234567的方式来记忆,倒是也不麻烦)

为了连接例子里的服务器,我们需要一个客户端程序,比如netcat这个工具(nc命令),这个工具可以用来执行网络连接操作。

$ go build gopl.io/ch8/clock1
$ ./clock1 &
$ nc localhost 8000
13:58:54
13:58:55
13:58:56
13:58:57
^C
客户端将服务器发来的时间显示了出来,我们用Control+C来中断客户端的执行,在Unix系统上,你会看到^C这样的响应。
如果你的系统没有装nc这个工具,你可以用telnet来实现同样的效果,或者也可以用我们下面的这个用go写的简单的telnet程序,用net.Dial就可以简单地创建一个TCP连接:
// Netcat1 is a read-only TCP client.
package main import (
"io"
"log"
"net"
"os"
) func main() {
conn, err := net.Dial("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
mustCopy(os.Stdout, conn)
} func mustCopy(dst io.Writer, src io.Reader) {
if _, err := io.Copy(dst, src); err != nil {
log.Fatal(err)
}
}

  

这个程序会从连接中读取数据,并将读到的内容写到标准输出中,直到遇到end of file的条件或者发生错误。

mustCopy这个函数我们在本节的几个例子中都会用到。

让我们同时运行两个客户端来进行一个测试,这里可以开两个终端窗口,下面左边的是其中的一个的输出,右边的是另一个的输出:

$ go build gopl.io/ch8/netcat1
$ ./netcat1
13:58:54 $ ./netcat1
13:58:55
13:58:56
^C
13:58:57
13:58:58
13:58:59
^C
$ killall clock1

  

killall命令是一个Unix命令行工具,可以用给定的进程名来杀掉所有名字匹配的进程。

第二个客户端必须等待第一个客户端完成工作,这样服务端才能继续向后执行;因为我们这里的服务器程序同一时间只能处理一个客户端连接。我们这里对服务端程序做一点小改动,使其支持并发:在handleConn函数调用的地方增加go关键字,让每一次handleConn的调用都进入一个独立的goroutine。

for {
conn, err := listener.Accept()
if err != nil {
log.Print(err) // e.g., connection aborted
continue
}
go handleConn(conn) // handle connections concurrently
}

  现在多个客户端可以同时接收到时间了:

$ go build gopl.io/ch8/clock2
$ ./clock2 &
$ go build gopl.io/ch8/netcat1
$ ./netcat1
14:02:54 $ ./netcat1
14:02:55 14:02:55
14:02:56 14:02:56
14:02:57 ^C
14:02:58
14:02:59 $ ./netcat1
14:03:00 14:03:00
14:03:01 14:03:01
^C 14:03:02
^C
$ killall clock2

  

Goroutines和Channels(二)的更多相关文章

  1. ARTS-S golang goroutines and channels(二)

    向tcp服务端发消息 package main import ( "io" "log" "net" "os" ) fun ...

  2. 如果这种方式导致程序明显变慢或者引起其他问题,我们要重新思考来通过 goroutines 和 channels 来解决问题

    https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/09.3.md 9.3 锁和 sync 包 在一些复杂的程序中,通常通 ...

  3. Goroutines和Channels

    原文链接 https://golangbot.com/goroutines/ Goroutines Goroutines 可以被认为是多个函数或方法同时允许.可以认为是一个轻量级的线程.与线程的花费相 ...

  4. Goroutines和Channels(五)

    Channels也可以用于将多个goroutine连接在一起,一个Channel的输出作为下一个Channel的输入.这种串联的Channels就是所谓的管道(pipeline).下面的程序用两个ch ...

  5. Goroutines和Channels(四)

    如果说goroutine是Go语言程序的并发体的话,那么channels则是它们之间的通信机制. 一个channel是一个通信机制,它可以让一个goroutine通过它给另一个goroutine发送值 ...

  6. Goroutines和Channels(三)

    clock服务器每一个连接都会起一个goroutine.在本节中我们会创建一个echo服务器,这个服务在每个连接中会有多个goroutine.大多数echo服务仅仅会返回他们读取到的内容,就像下面这个 ...

  7. Goroutines和Channels(一)

    Go语言中的并发程序可以用两种手段来实现.本章讲解goroutine和channel,其支持“顺序通信进程”(communicating sequential processes)或被简称为CSP.C ...

  8. ARTS-S golang goroutines and channels(一)

    先用golang实现一个简单的tcp服务端,假定文件名为clock1.go // clock1.go package main import ( "fmt" "io&qu ...

  9. Golang开发者常见的坑

    Golang开发者常见的坑 目录 [−] 初级 开大括号不能放在单独的一行 未使用的变量 未使用的Imports 简式的变量声明仅可以在函数内部使用 使用简式声明重复声明变量 偶然的变量隐藏Accid ...

随机推荐

  1. css定位浮动总结

    定位:定位在中间,放大缩小时也不会跑偏. position:absolute; top: 50%; left: 50%; margin: -270px 0 0 -455px; 解释:定位后,设百分比的 ...

  2. C语言typeof详解

    前言:     typeof关键字是C语言中的一个新扩展,这个特性在linux内核中应用非常广泛. 一,说明     typeof的参数可以是两种形式:表达式或类型. 1,表达式的的例子:       ...

  3. c# 日期函数[string.Format----GetDateTimeFormats]格式

    DateTime dt = DateTime.Now;Label1.Text = dt.ToString();//2005-11-5 13:21:25Label2.Text = dt.ToFileTi ...

  4. VS2010/MFC编程入门之三十二(常用控件:标签控件Tab Control 上)

    前面两节鸡啄米讲了树形控件Tree Control,本节开始讲解标签控件Tab Control,也可以称为选项卡控件. 标签控件简介 标签控件也比较常见.它可以把多个页面集成到一个窗口中,每个页面对应 ...

  5. 关于在ASP.NET中使用JavaScript的建议

    一个很恼人的情况,就是当你使用JS在一个ASP,NET应用程序中引用一个在模板页初始化的服务器控件的时候: 比如,我们在模板页有一个TextBox的服务器控件,而且我们想要去获取他的Text:如果你使 ...

  6. python isinstance()与type()的区别

    例如在继承上的区别: isinstance() 会认为子类是一种父类类型,考虑继承关系. type() 不会认为子类是一种父类类型,不考虑继承关系. class A: pass class B(A): ...

  7. python 运行脚本报错 from keyword import iskeyword as _iskeyword ImportError: cannot import name iskeyword,说明python环境坏了,得重装,尚不知具体原因,

    C:\Python27\Scripts>python task_test.pyTraceback (most recent call last):  File "task_test.p ...

  8. 如何写出一个让人很难发现的bug?

    程序员的日常三件事:写bug.改bug.背锅.连程序员都自我调侃道,为什么每天都在加班?因为我的眼里常含bug. 那么如何写出一个让(坑)人(王)很(之)难(王)发现的bug呢? - 1 -新手开发+ ...

  9. Centos下使用php调用shell脚本

    我们在实际项目中或许会遇到php调用shell脚本的需求.下面就用简单案例在Centos环境下实践 准备 查看php.ini中配置是否打开安全模式 //php.ini safe_mode = //这个 ...

  10. 03: vuejs 事件、模板、过滤器

    目录:Vue其他篇 01: vue.js安装 02: vue.js常用指令 03: vuejs 事件.模板.过滤器 目录: 1.1 事件 1.2 模板 1.3 自定义过滤器 1.4 过度 1.5 支付 ...