Golang的主要 设计目标之一就是面向大规模后端服务程序,网络通信这块是服务端 程序必不可少也是至关重要的一部分。在日常应用中,我们也可以看到Go中的net以及其subdirectories下的包均是“高频+刚需”,而TCP socket则是网络编程的主流,即便您没有直接使用到net中有关TCP Socket方面的接口,但net/http总是用到了吧,http底层依旧是用tcp socket实现的。

网络编程方面,我们最常用的就是tcp socket编程了,在posix标准出来后,socket在各大主流OS平台上都得到了很好的支持。关于tcp programming,最好的资料莫过于W. Richard Stevens 的网络编程圣经《UNIX网络 编程 卷1:套接字联网API》 了,书中关于tcp socket接口的各种使用、行为模式、异常处理讲解的十分细致。Go是自带runtime的跨平台编程语言,Go中暴露给语言使用者的tcp socket api是建立OS原生tcp socket接口之上的。由于Go runtime调度的需要,golang tcp socket接口在行为特点与异常处理方面与OS原生接口有着一些差别。这篇博文的目标就是整理出关于Go tcp socket在各个场景下的使用方法、行为特点以及注意事项。

一、模型

从tcp socket诞生后,网络编程架构模型也几经演化,大致是:“每进程一个连接” –> “每线程一个连接” –> “Non-Block + I/O多路复用(linux epoll/windows iocp/freebsd darwin kqueue/solaris Event Port)”。伴随着模型的演化,服务程序愈加强大,可以支持更多的连接,获得更好的处理性能。

目前主流web server一般均采用的都是”Non-Block + I/O多路复用”(有的也结合了多线程、多进程)。不过I/O多路复用也给使用者带来了不小的复杂度,以至于后续出现了许多高性能的I/O多路复用框架, 比如libevent、libev、libuv等,以帮助开发者简化开发复杂性,降低心智负担。不过Go的设计者似乎认为I/O多路复用的这种通过回调机制割裂控制流 的方式依旧复杂,且有悖于“一般逻辑”设计,为此Go语言将该“复杂性”隐藏在Runtime中了:Go开发者无需关注socket是否是 non-block的,也无需亲自注册文件描述符的回调,只需在每个连接对应的goroutine中以“block I/O”的方式对待socket处理即可,这可以说大大降低了开发人员的心智负担。一个典型的Go server端程序大致如下:

//go-tcpsock/server.go
func handleConn(c net.Conn) {defer c.Close()for {// read from the connection// ... ...// write to the connection//... ...}
}func main() {l, err := net.Listen("tcp", ":8888")if err != nil {fmt.Println("listen error:", err)return}for {c, err := l.Accept()if err != nil {fmt.Println("accept error:", err)break}// start a new goroutine to handle// the new connection.go handleConn(c)}
}

用户层眼中看到的goroutine中的“block socket”,实际上是通过Go runtime中的netpoller通过Non-block socket + I/O多路复用机制“模拟”出来的,真实的underlying socket实际上是non-block的,只是runtime拦截了底层socket系统调用的错误码,并通过netpoller和goroutine 调度让goroutine“阻塞”在用户层得到的Socket fd上。比如:当用户层针对某个socket fd发起read操作时,如果该socket fd中尚无数据,那么runtime会将该socket fd加入到netpoller中监听,同时对应的goroutine被挂起,直到runtime收到socket fd 数据ready的通知,runtime才会重新唤醒等待在该socket fd上准备read的那个Goroutine。而这个过程从Goroutine的视角来看,就像是read操作一直block在那个socket fd上似的。具体实现细节在后续场景中会有补充描述。

二、TCP连接的建立

众所周知,TCP Socket的连接的建立需要经历客户端和服务端的三次握手的过程。连接建立过程中,服务端是一个标准的Listen + Accept的结构(可参考上面的代码),而在客户端Go语言使用net.Dial或DialTimeout进行连接建立:

阻塞Dial:

conn, err := net.Dial("tcp", "google.com:80")
if err != nil {//handle error
}
// read or write on conn

或是带上超时机制的Dial:

conn, err := net.DialTimeout("tcp", ":8080", 2 * time.Second)
if err != nil {//handle error
}
// read or write on conn

对于客户端而言,连接的建立会遇到如下几种情形:


1、网络不可达或对方服务未启动

如果传给Dial的Addr是可以立即判断出网络不可达,或者Addr中端口对应的服务没有启动,端口未被监听,Dial会几乎立即返回错误,比如:

//go-tcpsock/conn_establish/client1.go
... ...
func main() {log.Println("begin dial...")conn, err := net.Dial("tcp", ":8888")if err != nil {log.Println("dial error:", err)return}defer conn.Close()log.Println("dial ok")
}

如果本机8888端口未有服务程序监听,那么执行上面程序,Dial会很快返回错误:

$go run client1.go
2015/11/16 14:37:41 begin dial...
2015/11/16 14:37:41 dial error: dial tcp :8888: getsockopt: connection refused

2、对方服务的listen backlog满

还有一种场景就是对方服务器很忙,瞬间有大量client端连接尝试向server建立,server端的listen backlog队列满,server accept不及时((即便不accept,那么在backlog数量范畴里面,connect都会是成功的,因为new conn已经加入到server side的listen queue中了,accept只是从queue中取出一个conn而已),这将导致client端Dial阻塞。我们还是通过例子感受Dial的行为特点:

服务端代码:

//go-tcpsock/conn_establish/server2.go
... ...
func main() {l, err := net.Listen("tcp", ":8888")if err != nil {log.Println("error listen:", err)return}defer l.Close()log.Println("listen ok")var i intfor {time.Sleep(time.Second * 10)if _, err := l.Accept(); err != nil {log.Println("accept error:", err)break}i++log.Printf("%d: accept a new connection\n", i)}
}

客户端代码:

//go-tcpsock/conn_establish/client2.go
... ...
func establishConn(i int) net.Conn {conn, err := net.Dial("tcp", ":8888")if err != nil {log.Printf("%d: dial error: %s", i, err)return nil}log.Println(i, ":connect to server ok")return conn
}func main() {var sl []net.Connfor i := 1; i < 1000; i++ {conn := establishConn(i)if conn != nil {sl = append(sl, conn)}}time.Sleep(time.Second * 10000)
}

从程序可以看出,服务端在listen成功后,每隔10s钟accept一次。客户端则是串行的尝试建立连接。这两个程序在Darwin下的执行 结果:

$go run server2.go
2015/11/16 21:55:41 listen ok
2015/11/16 21:55:51 1: accept a new connection
2015/11/16 21:56:01 2: accept a new connection
... ...$go run client2.go
2015/11/16 21:55:44 1 :connect to server ok
2015/11/16 21:55:44 2 :connect to server ok
2015/11/16 21:55:44 3 :connect to server ok
... ...2015/11/16 21:55:44 126 :connect to server ok
2015/11/16 21:55:44 127 :connect to server ok
2015/11/16 21:55:44 128 :connect to server ok2015/11/16 21:55:52 129 :connect to server ok
2015/11/16 21:56:03 130 :connect to server ok
2015/11/16 21:56:14 131 :connect to server ok
... ...

可以看出Client初始时成功地一次性建立了128个连接,然后后续每阻塞近10s才能成功建立一条连接。也就是说在server端 backlog满时(未及时accept),客户端将阻塞在Dial上,直到server端进行一次accept。至于为什么是128,这与darwin 下的默认设置有关:

$sysctl -a|grep kern.ipc.somaxconn
kern.ipc.somaxconn: 128

如果我在ubuntu 14.04上运行上述server程序,我们的client端初始可以成功建立499条连接。

如果server一直不accept,client端会一直阻塞么?我们去掉accept后的结果是:在Darwin下,client端会阻塞大 约1分多钟才会返回timeout:

2015/11/16 22:03:31 128 :connect to server ok
2015/11/16 22:04:48 129: dial error: dial tcp :8888: getsockopt: operation timed out

而如果server运行在ubuntu 14.04上,client似乎一直阻塞,我等了10多分钟依旧没有返回。 阻塞与否看来与server端的网络实现和设置有关。

3、网络延迟较大,Dial阻塞并超时

如果网络延迟较大,TCP握手过程将更加艰难坎坷(各种丢包),时间消耗的自然也会更长。Dial这时会阻塞,如果长时间依旧无法建立连接,则Dial也会返回“ getsockopt: operation timed out”错误。


在连接建立阶段,多数情况下,Dial是可以满足需求的,即便阻塞一小会儿。但对于某些程序而言,需要有严格的连接时间限定,如果一定时间内没能成功建立连接,程序可能会需要执行一段“异常”处理逻辑,为此我们就需要DialTimeout了。下面的例子将Dial的最长阻塞时间限制在2s内,超出这个时长,Dial将返回timeout error:

//go-tcpsock/conn_establish/client3.go
... ...
func main() {log.Println("begin dial...")conn, err := net.DialTimeout("tcp", "104.236.176.96:80", 2*time.Second)if err != nil {log.Println("dial error:", err)return}defer conn.Close()log.Println("dial ok")
}

执行结果如下(需要模拟一个延迟较大的网络环境):

$go run client3.go
2015/11/17 09:28:34 begin dial...
2015/11/17 09:28:36 dial error: dial tcp 104.236.176.96:80: i/o timeout

三、Socket读写

连接建立起来后,我们就要在conn上进行读写,以完成业务逻辑。前面说过Go runtime隐藏了I/O多路复用的复杂性。语言使用者只需采用goroutine+Block I/O的模式即可满足大部分场景需求。Dial成功后,方法返回一个net.Conn接口类型变量值,这个接口变量的动态类型为一个*TCPConn:

//$GOROOT/src/net/tcpsock_posix.go
type TCPConn struct {conn
}

TCPConn内嵌了一个unexported类型:conn,因此TCPConn”继承”了conn的Read和Write方法,后续通过Dial返回值调用的Write和Read方法均是net.conn的方法:

//$GOROOT/src/net/net.go
type conn struct {fd *netFD
}func (c *conn) ok() bool { return c != nil && c.fd != nil }// Implementation of the Conn interface.// Read implements the Conn Read method.
func (c *conn) Read(b []byte) (int, error) {if !c.ok() {return 0, syscall.EINVAL}n, err := c.fd.Read(b)if err != nil && err != io.EOF {err = &OpError{Op: "read", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err}}return n, err
}// Write implements the Conn Write method.
func (c *conn) Write(b []byte) (int, error) {if !c.ok() {return 0, syscall.EINVAL}n, err := c.fd.Write(b)if err != nil {err = &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err}}return n, err
}

下面我们先来通过几个场景来总结一下conn.Read的行为特点。


1、Socket中无数据

连接建立后,如果对方未发送数据到socket,接收方(Server)会阻塞在Read操作上,这和前面提到的“模型”原理是一致的。执行该Read操作的goroutine也会被挂起。runtime会监视该socket,直到其有数据才会重新
调度该socket对应的Goroutine完成read。由于篇幅原因,这里就不列代码了,例子对应的代码文件:go-tcpsock/read_write下的client1.go和server1.go。

2、Socket中有部分数据

如果socket中有部分数据,且长度小于一次Read操作所期望读出的数据长度,那么Read将会成功读出这部分数据并返回,而不是等待所有期望数据全部读取后再返回。

Client端:

//go-tcpsock/read_write/client2.go
... ...
func main() {if len(os.Args) <= 1 {fmt.Println("usage: go run client2.go YOUR_CONTENT")return}log.Println("begin dial...")conn, err := net.Dial("tcp", ":8888")if err != nil {log.Println("dial error:", err)return}defer conn.Close()log.Println("dial ok")time.Sleep(time.Second * 2)data := os.Args[1]conn.Write([]byte(data))time.Sleep(time.Second * 10000)
}

Server端:

//go-tcpsock/read_write/server2.go
... ...
func handleConn(c net.Conn) {defer c.Close()for {// read from the connectionvar buf = make([]byte, 10)log.Println("start to read from conn")n, err := c.Read(buf)if err != nil {log.Println("conn read error:", err)return}log.Printf("read %d bytes, content is %s\n", n, string(buf[:n]))}
}
... ...

我们通过client2.go发送”hi”到Server端:
运行结果:

$go run client2.go hi
2015/11/17 13:30:53 begin dial...
2015/11/17 13:30:53 dial ok$go run server2.go
2015/11/17 13:33:45 accept a new connection
2015/11/17 13:33:45 start to read from conn
2015/11/17 13:33:47 read 2 bytes, content is hi
...

Client向socket中写入两个字节数据(“hi”),Server端创建一个len = 10的slice,等待Read将读取的数据放入slice;Server随后读取到那两个字节:”hi”。Read成功返回,n =2 ,err = nil。

3、Socket中有足够数据

如果socket中有数据,且长度大于等于一次Read操作所期望读出的数据长度,那么Read将会成功读出这部分数据并返回。这个情景是最符合我们对Read的期待的了:Read将用Socket中的数据将我们传入的slice填满后返回:n = 10, err = nil。

我们通过client2.go向Server2发送如下内容:abcdefghij12345,执行结果如下:

$go run client2.go abcdefghij12345
2015/11/17 13:38:00 begin dial...
2015/11/17 13:38:00 dial ok$go run server2.go
2015/11/17 13:38:00 accept a new connection
2015/11/17 13:38:00 start to read from conn
2015/11/17 13:38:02 read 10 bytes, content is abcdefghij
2015/11/17 13:38:02 start to read from conn
2015/11/17 13:38:02 read 5 bytes, content is 12345

client端发送的内容长度为15个字节,Server端Read buffer的长度为10,因此Server Read第一次返回时只会读取10个字节;Socket中还剩余5个字节数据,Server再次Read时会把剩余数据读出(如:情形2)。

4、Socket关闭

如果client端主动关闭了socket,那么Server的Read将会读到什么呢?这里分为“有数据关闭”和“无数据关闭”。

“有数据关闭”是指在client关闭时,socket中还有server端未读取的数据,我们在go-tcpsock/read_write/client3.go和server3.go中模拟这种情况:

$go run client3.go hello
2015/11/17 13:50:57 begin dial...
2015/11/17 13:50:57 dial ok$go run server3.go
2015/11/17 13:50:57 accept a new connection
2015/11/17 13:51:07 start to read from conn
2015/11/17 13:51:07 read 5 bytes, content is hello
2015/11/17 13:51:17 start to read from conn
2015/11/17 13:51:17 conn read error: EOF

从输出结果来看,当client端close socket退出后,server3依旧没有开始Read,10s后第一次Read成功读出了5个字节的数据,当第二次Read时,由于client端 socket关闭,Read返回EOF error。

通过上面这个例子,我们也可以猜测出“无数据关闭”情形下的结果,那就是Read直接返回EOF error。

5、读取操作超时

有些场合对Read的阻塞时间有严格限制,在这种情况下,Read的行为到底是什么样的呢?在返回超时错误时,是否也同时Read了一部分数据了呢?这个实验比较难于模拟,下面的测试结果也未必能反映出所有可能结果。我们编写了client4.go和server4.go来模拟这一情形。

//go-tcpsock/read_write/client4.go
... ...
func main() {log.Println("begin dial...")conn, err := net.Dial("tcp", ":8888")if err != nil {log.Println("dial error:", err)return}defer conn.Close()log.Println("dial ok")data := make([]byte, 65536)conn.Write(data)time.Sleep(time.Second * 10000)
}//go-tcpsock/read_write/server4.go
... ...
func handleConn(c net.Conn) {defer c.Close()for {// read from the connectiontime.Sleep(10 * time.Second)var buf = make([]byte, 65536)log.Println("start to read from conn")c.SetReadDeadline(time.Now().Add(time.Microsecond * 10))n, err := c.Read(buf)if err != nil {log.Printf("conn read %d bytes,  error: %s", n, err)if nerr, ok := err.(net.Error); ok && nerr.Timeout() {continue}return}log.Printf("read %d bytes, content is %s\n", n, string(buf[:n]))}
}

在Server端我们通过Conn的SetReadDeadline方法设置了10微秒的读超时时间,Server的执行结果如下:

$go run server4.go2015/11/17 14:21:17 accept a new connection
2015/11/17 14:21:27 start to read from conn
2015/11/17 14:21:27 conn read 0 bytes,  error: read tcp 127.0.0.1:8888->127.0.0.1:60970: i/o timeout
2015/11/17 14:21:37 start to read from conn
2015/11/17 14:21:37 read 65536 bytes, content is

虽然每次都是10微秒超时,但结果不同,第一次Read超时,读出数据长度为0;第二次读取所有数据成功,没有超时。反复执行了多次,没能出现“读出部分数据且返回超时错误”的情况。


和读相比,Write遇到的情形一样不少,我们也逐一看一下。


1、成功写

前面例子着重于Read,client端在Write时并未判断Write的返回值。所谓“成功写”指的就是Write调用返回的n与预期要写入的数据长度相等,且error = nil。这是我们在调用Write时遇到的最常见的情形,这里不再举例了。

2、写阻塞

TCP连接通信两端的OS都会为该连接保留数据缓冲,一端调用Write后,实际上数据是写入到OS的协议栈的数据缓冲的。TCP是全双工通信,因此每个方向都有独立的数据缓冲。当发送方将对方的接收缓冲区以及自身的发送缓冲区写满后,Write就会阻塞。我们来看一个例子:client5.go和server.go。

//go-tcpsock/read_write/client5.go
... ...
func main() {log.Println("begin dial...")conn, err := net.Dial("tcp", ":8888")if err != nil {log.Println("dial error:", err)return}defer conn.Close()log.Println("dial ok")data := make([]byte, 65536)var total intfor {n, err := conn.Write(data)if err != nil {total += nlog.Printf("write %d bytes, error:%s\n", n, err)break}total += nlog.Printf("write %d bytes this time, %d bytes in total\n", n, total)}log.Printf("write %d bytes in total\n", total)time.Sleep(time.Second * 10000)
}//go-tcpsock/read_write/server5.go
... ...
func handleConn(c net.Conn) {defer c.Close()time.Sleep(time.Second * 10)for {// read from the connectiontime.Sleep(5 * time.Second)var buf = make([]byte, 60000)log.Println("start to read from conn")n, err := c.Read(buf)if err != nil {log.Printf("conn read %d bytes,  error: %s", n, err)if nerr, ok := err.(net.Error); ok && nerr.Timeout() {continue}}log.Printf("read %d bytes, content is %s\n", n, string(buf[:n]))}
}
... ...

Server5在前10s中并不Read数据,因此当client5一直尝试写入时,写到一定量后就会发生阻塞:

$go run client5.go2015/11/17 14:57:33 begin dial...
2015/11/17 14:57:33 dial ok
2015/11/17 14:57:33 write 65536 bytes this time, 65536 bytes in total
2015/11/17 14:57:33 write 65536 bytes this time, 131072 bytes in total
2015/11/17 14:57:33 write 65536 bytes this time, 196608 bytes in total
2015/11/17 14:57:33 write 65536 bytes this time, 262144 bytes in total
2015/11/17 14:57:33 write 65536 bytes this time, 327680 bytes in total
2015/11/17 14:57:33 write 65536 bytes this time, 393216 bytes in total
2015/11/17 14:57:33 write 65536 bytes this time, 458752 bytes in total
2015/11/17 14:57:33 write 65536 bytes this time, 524288 bytes in total
2015/11/17 14:57:33 write 65536 bytes this time, 589824 bytes in total
2015/11/17 14:57:33 write 65536 bytes this time, 655360 bytes in total

在Darwin上,这个size大约在679468bytes。后续当server5每隔5s进行Read时,OS socket缓冲区腾出了空间,client5就又可以写入了:

$go run server5.go
2015/11/17 15:07:01 accept a new connection
2015/11/17 15:07:16 start to read from conn
2015/11/17 15:07:16 read 60000 bytes, content is
2015/11/17 15:07:21 start to read from conn
2015/11/17 15:07:21 read 60000 bytes, content is
2015/11/17 15:07:26 start to read from conn
2015/11/17 15:07:26 read 60000 bytes, content is
....client端:2015/11/17 15:07:01 write 65536 bytes this time, 720896 bytes in total
2015/11/17 15:07:06 write 65536 bytes this time, 786432 bytes in total
2015/11/17 15:07:16 write 65536 bytes this time, 851968 bytes in total
2015/11/17 15:07:16 write 65536 bytes this time, 917504 bytes in total
2015/11/17 15:07:27 write 65536 bytes this time, 983040 bytes in total
2015/11/17 15:07:27 write 65536 bytes this time, 1048576 bytes in total
.... ...

3、写入部分数据

Write操作存在写入部分数据的情况,比如上面例子中,当client端输出日志停留在“write 65536 bytes this time, 655360 bytes in total”时,我们杀掉server5,这时我们会看到client5输出以下日志:

...
2015/11/17 15:19:14 write 65536 bytes this time, 655360 bytes in total
2015/11/17 15:19:16 write 24108 bytes, error:write tcp 127.0.0.1:62245->127.0.0.1:8888: write: broken pipe
2015/11/17 15:19:16 write 679468 bytes in total

显然Write并非在655360这个地方阻塞的,而是后续又写入24108后发生了阻塞,server端socket关闭后,我们看到Wrote返回er != nil且n = 24108,程序需要对这部分写入的24108字节做特定处理。

4、写入超时

如果非要给Write增加一个期限,那我们可以调用SetWriteDeadline方法。我们copy一份client5.go,形成client6.go,在client6.go的Write之前增加一行timeout设置代码:

conn.SetWriteDeadline(time.Now().Add(time.Microsecond * 10))

启动server6.go,启动client6.go,我们可以看到写入超时的情况下,Write的返回结果:

$go run client6.go
2015/11/17 15:26:34 begin dial...
2015/11/17 15:26:34 dial ok
2015/11/17 15:26:34 write 65536 bytes this time, 65536 bytes in total
... ...
2015/11/17 15:26:34 write 65536 bytes this time, 655360 bytes in total
2015/11/17 15:26:34 write 24108 bytes, error:write tcp 127.0.0.1:62325->127.0.0.1:8888: i/o timeout
2015/11/17 15:26:34 write 679468 bytes in total

可以看到在写入超时时,依旧存在部分数据写入的情况。


综上例子,虽然Go给我们提供了阻塞I/O的便利,但在调用Read和Write时依旧要综合需要方法返回的n和err的结果,以做出正确处理。net.conn实现了io.Reader和io.Writer接口,因此可以试用一些wrapper包进行socket读写,比如bufio包下面的Writer和Reader、io/ioutil下的函数等。

Goroutine safe

基于goroutine的网络架构模型,存在在不同goroutine间共享conn的情况,那么conn的读写是否是goroutine safe的呢?在深入这个问题之前,我们先从应用意义上来看read操作和write操作的goroutine-safe必要性。

对于read操作而言,由于TCP是面向字节流,conn.Read无法正确区分数据的业务边界,因此多个goroutine对同一个conn进行read的意义不大,goroutine读到不完整的业务包反倒是增加了业务处理的难度。对与Write操作而言,倒是有多个goroutine并发写的情况。不过conn读写是否goroutine-safe的测试不是很好做,我们先深入一下runtime代码,先从理论上给这个问题定个性:

net.conn只是*netFD的wrapper结构,最终Write和Read都会落在其中的fd上:

type conn struct {fd *netFD
}

netFD在不同平台上有着不同的实现,我们以net/fd_unix.go中的netFD为例:

// Network file descriptor.
type netFD struct {// locking/lifetime of sysfd + serialize access to Read and Write methodsfdmu fdMutex// immutable until Closesysfd       intfamily      intsotype      intisConnected boolnet         stringladdr       Addrraddr       Addr// wait serverpd pollDesc
}

我们看到netFD中包含了一个runtime实现的fdMutex类型字段,从注释上来看,该fdMutex用来串行化对该netFD对应的sysfd的Write和Read操作。从这个注释上来看,所有对conn的Read和Write操作都是有fdMutex互斥的,从netFD的Read和Write方法的实现也证实了这一点:

func (fd *netFD) Read(p []byte) (n int, err error) {if err := fd.readLock(); err != nil {return 0, err}defer fd.readUnlock()if err := fd.pd.PrepareRead(); err != nil {return 0, err}for {n, err = syscall.Read(fd.sysfd, p)if err != nil {n = 0if err == syscall.EAGAIN {if err = fd.pd.WaitRead(); err == nil {continue}}}err = fd.eofError(n, err)break}if _, ok := err.(syscall.Errno); ok {err = os.NewSyscallError("read", err)}return
}func (fd *netFD) Write(p []byte) (nn int, err error) {if err := fd.writeLock(); err != nil {return 0, err}defer fd.writeUnlock()if err := fd.pd.PrepareWrite(); err != nil {return 0, err}for {var n intn, err = syscall.Write(fd.sysfd, p[nn:])if n > 0 {nn += n}if nn == len(p) {break}if err == syscall.EAGAIN {if err = fd.pd.WaitWrite(); err == nil {continue}}if err != nil {break}if n == 0 {err = io.ErrUnexpectedEOFbreak}}if _, ok := err.(syscall.Errno); ok {err = os.NewSyscallError("write", err)}return nn, err
}

每次Write操作都是受lock保护,直到此次数据全部write完。因此在应用层面,要想保证多个goroutine在一个conn上write操作的Safe,需要一次write完整写入一个“业务包”;一旦将业务包的写入拆分为多次write,那就无法保证某个Goroutine的某“业务包”数据在conn发送的连续性。

同时也可以看出即便是Read操作,也是lock保护的。多个Goroutine对同一conn的并发读不会出现读出内容重叠的情况,但内容断点是依 runtime调度来随机确定的。存在一个业务包数据,1/3内容被goroutine-1读走,另外2/3被另外一个goroutine-2读 走的情况。比如一个完整包:world,当goroutine的read slice size < 5时,存在可能:一个goroutine读到 “worl”,另外一个goroutine读出”d”。

四、Socket属性

原生Socket API提供了丰富的sockopt设置接口,但Golang有自己的网络架构模型,golang提供的socket options接口也是基于上述模型的必要的属性设置。包括

  • SetKeepAlive
  • SetKeepAlivePeriod
  • SetLinger
  • SetNoDelay (默认no delay)
  • SetWriteBuffer
  • SetReadBuffer

不过上面的Method是TCPConn的,而不是Conn的,要使用上面的Method的,需要type assertion:

tcpConn, ok := c.(*TCPConn)
if !ok {//error handle
}tcpConn.SetNoDelay(true)

对于listener socket, golang默认采用了 SO_REUSEADDR,这样当你重启 listener程序时,不会因为address in use的错误而启动失败。而listen backlog的默认值是通过获取系统的设置值得到的。不同系统不同:mac 128, linux 512等。

五、关闭连接

和前面的方法相比,关闭连接算是最简单的操作了。由于socket是全双工的,client和server端在己方已关闭的socket和对方关闭的socket上操作的结果有不同。看下面例子:

//go-tcpsock/conn_close/client1.go
... ...
func main() {log.Println("begin dial...")conn, err := net.Dial("tcp", ":8888")if err != nil {log.Println("dial error:", err)return}conn.Close()log.Println("close ok")var buf = make([]byte, 32)n, err := conn.Read(buf)if err != nil {log.Println("read error:", err)} else {log.Printf("read % bytes, content is %s\n", n, string(buf[:n]))}n, err = conn.Write(buf)if err != nil {log.Println("write error:", err)} else {log.Printf("write % bytes, content is %s\n", n, string(buf[:n]))}time.Sleep(time.Second * 1000)
}//go-tcpsock/conn_close/server1.go
... ...
func handleConn(c net.Conn) {defer c.Close()// read from the connectionvar buf = make([]byte, 10)log.Println("start to read from conn")n, err := c.Read(buf)if err != nil {log.Println("conn read error:", err)} else {log.Printf("read %d bytes, content is %s\n", n, string(buf[:n]))}n, err = c.Write(buf)if err != nil {log.Println("conn write error:", err)} else {log.Printf("write %d bytes, content is %s\n", n, string(buf[:n]))}
}
... ...

上述例子的执行结果如下:

$go run server1.go
2015/11/17 17:00:51 accept a new connection
2015/11/17 17:00:51 start to read from conn
2015/11/17 17:00:51 conn read error: EOF
2015/11/17 17:00:51 write 10 bytes, content is$go run client1.go
2015/11/17 17:00:51 begin dial...
2015/11/17 17:00:51 close ok
2015/11/17 17:00:51 read error: read tcp 127.0.0.1:64195->127.0.0.1:8888: use of closed network connection
2015/11/17 17:00:51 write error: write tcp 127.0.0.1:64195->127.0.0.1:8888: use of closed network connection

从client1的结果来看,在己方已经关闭的socket上再进行read和write操作,会得到”use of closed network connection” error;
从server1的执行结果来看,在对方关闭的socket上执行read操作会得到EOF error,但write操作会成功,因为数据会成功写入己方的内核socket缓冲区中,即便最终发不到对方socket缓冲区了,因为己方socket并未关闭。因此当发现对方socket关闭后,己方应该正确合理处理自己的socket,再继续write已经无任何意义了。

六、小结

本文比较基础,但却很重要,毕竟golang是面向大规模服务后端的,对通信环节的细节的深入理解会大有裨益。另外Go的goroutine+阻塞通信的网络通信模型降低了开发者心智负担,简化了通信的复杂性,这点尤为重要。

本文代码实验环境:go 1.5.1 on Darwin amd64以及部分在ubuntu 14.04 amd64。

本文demo代码在这里可以找到。

© 2015, bigwhite. 版权所有.

Related posts:

  1. Go程序调试、分析与优化
  2. 搭建自己的ngrok服务
  3. ngrok原理浅析
  4. 关于Go,你可能不注意的7件事
  5. Golang跨平台交叉编译
查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 如何消除视频中的人声来制作配音视频

    如何消除视频中的人声来制作配音视频 首先需要准备好几款软件(Adobe Audition、视频剪辑软件、格式工厂),视频剪辑软件可以选择Adobe Premiere Pro或爱剪辑或瞄影工厂等,本文选用通俗易懂的瞄影工厂介绍。(免费版下载链接已提供,请读者自行下载安装,在此不多赘述 )由于…...

    2024/4/20 17:13:18
  2. Web前端新手入门:web前端介绍

    一、什么是web前端?web(互联网总称):是一种基于超文本和HTTP的、全球性的、动态交互的、跨平台的分布式图形信息系统。用户在查找或浏览信息的时候,展现出图形化,用直观的界面方式表达出来。前端:对于网站(页面)来说,有2部分,一个是网站的页面设计,用户的视觉效果等(…...

    2024/4/20 17:13:17
  3. 视频批量伪原创剪辑器3.1版

    增加了视频合成功能。有需要的同学快去下载体验吧!2.0版视频教程 https://github.com/suifengqjn/videoWater/blob/master/SREADME.md3.0版视频教程https://github.com/suifengqjn/videoWater/blob/master/README_3.md界面展示功能介绍视频自媒体批量视频伪原创剪辑器,秒杀市…...

    2024/4/21 11:13:57
  4. 混合编程之Go语言调用C函数

    Go语言在抽象性和底层操作能力之间进行了较好的权衡,不失C语言的功能而又没有C的繁琐,因此对付历史遗留问题混合编程是无法逃避的事实。下面就以最简单的Go语言调用C函数的示例来记录cgo最基础的使用过程。myabs.h#ifndef __MYABS_H__ #define __MYABS_H__int myabs(int x);#…...

    2024/4/20 11:15:17
  5. web前端工程师的优势

    web前端开发工程师可以说是一个全新的职业,在IT整个行业中真正受到重视的时间没有超过5年,也正因为这样,大家越来越想了解web前端工程师的前景究竟怎么样?web前端培训就业前景如何?web前端工程师的就业情况如何?web前端开发工程师的演变之路,不知道大家了解多少?说实话…...

    2024/5/5 6:24:22
  6. 录美妆视频用什么软件好?怎么录制高清画质?

    很多美妆博主的视频画质都非常好,怎么样才能录制那么高清的美妆视频?今天教大家用超级捕快录制一个高清的美妆视频,并分享一些实用的美妆视频后期剪辑技巧! 1.用超级捕快进行电脑屏幕录像,并选择录制的视频格式 打开软件后,选择“电脑屏幕录像”功能。可以导出WMV、AVI、…...

    2024/4/21 11:13:53
  7. web前端开发工程师面试题大全

    最近看到web群里的人,特别关心面试web前端开发工程师时,面试官都会问那些问题,今天我整理了一份,web前端开发工程师岗位面试题的大全,大家可看看,做为参考。一、HTML 常见题目01、Doctype作用?严格模式与混杂模式如何区分?它们有何意义?02、HTML5 为什么只需要写 <…...

    2024/4/21 11:13:53
  8. Go入门——第一次GO编程

    开始一段新的旅程GO 安装包下载地址为:https://golang.org/dl/Windows 下可以使用 .msi 后缀(在下载列表中可以找到该文件,如go1.9.2.windows-amd64.msi)的安装包来安装。 默认情况下.msi文件会安装在 c:\Go 目录下。 PATH 环境变量也会自动给你安装好(这个确实很先进啊,…...

    2024/4/21 11:13:52
  9. 会声会影2020秘钥视频编辑制作使用教程

    现在自媒体人,图片编辑和视频剪辑是必学的一门功课。随着国内互联网行业的迅猛发展,各种视频播放、剪辑、格式转换软件雨后春笋般层出不穷。尤其是近几年,手机上的app都已经实现了这些功能,随时随地使用,实在方便。 这里给大家介绍的是一款国外的视频编辑软件:会声会影。…...

    2024/4/20 11:51:26
  10. web前端开发工程师的三种级别

    随着信息技术不断发展,前端技术的发展也经历了不同的阶段。前端概念随着移动智能手机的普及被正式提出,混合APP开始被广泛开发。近年来,由于前端技术开始实现工程化,一些企业前端开发任务逐渐向后端拓展,逻辑思维能力也逐渐成为前端开发人员必备的能力。从2018年开始,用户…...

    2024/4/21 11:13:49
  11. Go Web编程(Go语言性能好、语法简单、开发效率高!)

    Go Web编程(Go语言性能好、语法简单、开发效率高!) 谢孟军 著 ISBN 978-7-121-20091-5 2013年6月出版 定价65.00元 352页 16开编辑推荐 Go语言性能好、语法简单、开发效率高!一起来探索Go 语言吧! 本书特别适合从事PHP/Python/Ruby/Node.js等Web开发的读者,通过本书可以了…...

    2024/4/21 11:13:48
  12. 想学习视频剪辑,修图的朋友

    想学习视频剪辑,修图的朋友,加q群297781935。学习的软件有ps、ai、cdr、3d max、c4d、maya、flash、dw、ae、pr、cad、会声会影、电子商务、办公软件。全天在线交流视频讲课,提供素材、插件。我们的博客http://www.xswlpx.com/...

    2024/4/21 11:13:47
  13. 怎样成长为一个优秀的 Web 前端开发工程师?

    链接:http://www.zhihu.com/question/19554845/answer/16755476 来源:知乎首先,优秀的Web前端开发工程师要在知识体系上既要有广度和深度!做到这两点,其实很难。所以很多大公司即使出高薪也很难招聘到理想的前端开发工程师。技术非黑即白,只有对和错,而技巧则见仁见智。…...

    2024/4/20 17:41:52
  14. Go Web编程一: Go Web 基础

    原文链接 http://ironxu.com/779Go Web 基础概念与代码阅读1. Go 搭建简单的web 服务Go 语言里面提供了一个完善的 net/http 包,通过http 包可以很方便的就搭建起来一个可以运行的Web服务。同时使用这个包能很简单地对Web的路由,静态文件,模版,cookie等进行设置和操作。$GO…...

    2024/4/20 17:13:33
  15. 网红用什么软件录视频?从小白到大神,学会这些录屏技巧就够了!

    快手、抖音等平台很多网红做视频直播录屏,录好的视频没有水印,而且画质还挺好的。现在很多小伙伴对录制视频的需求也越来越大了,大家都说超级捕快还不错,它支持录制1080P的高清无水印视频,除了录直播视频,还有各种游戏解说视频、美妆视频、微课视频等等都是OK的。其实用对…...

    2024/4/20 8:00:20
  16. WEB前端开发初学者必看的学习路线(附思维导图)

    很多同学想学习WEB前端开发,虽然互联网有很多的教程、网站、书籍,可是却又不知从何开始如何选取。看完网友高等游民白乌鸦无私分享的原标题为《写给同事的前端学习路线》这篇文章,相信你会有所收获。前端开发入门学习有:HTML、CSS、JavaScript(简称JS)这三个部分。所以在…...

    2024/4/20 17:13:30
  17. 第九课 输出语句

    第九课 输出语句【输出语句cout】编程中经常要在屏幕上输出变量或表达式计算结果的值。例如已知半径,计算圆的面积,计算的结果要显示出来才有意义。 C++语言中有几种方法可以把变量或表达式计算结果的值在屏幕上显示出来,使用“输出流”的cout语句是比较简洁方便的方法,语法…...

    2024/4/20 17:13:29
  18. 一款好用的视频剪切合并软件

    此款视频合并剪切工具软件很好用,处理速度快,没有水印,没有视频时间限制,非常好用官网地址:http://www.yyzsoft.com/videocutter.html...

    2024/4/20 0:55:03
  19. 2018几大主流的UI/JS框架——前端框架

    2016年开始应该是互联网飞速发展的几年,同时也是Web前端开发非常火爆的一年,Web 前端技术发展速度让人感觉几乎不是继承式的迭代,而是一次次的变革和创造。这一年中有很多热门的前端开发框架,下面源码时代web小编为大家总结2016年至今最受欢迎的几款前端框架。在这互联网飞…...

    2024/4/20 17:13:28
  20. Go语言版黑白棋(一):游戏说明

    1 界面参考图片资源下载:https://pan.baidu.com/s/1i6TyX5V2 游戏说明棋盘为88的方格布局,开局时在棋盘正中有摆好的四枚棋子,黑白各2枚,交叉放置,由执黑棋的一方先落子,双方交替下子,棋子落在方格内,一局游戏结束后双方更换执子颜色。合法的棋步包括:在一个空格新落下…...

    2024/4/20 17:13:26

最新文章

  1. IDEA 算法要点和难点,实际案例应用,代码实例和解析

    "IDEA" 全称是 "International Data Encryption Algorithm"(国际数据加密算法),它是一种对称密钥块加密算法。IDEA 是由瑞士联邦理工学院(ETH Zrich)的 Xuejia Lai 和 James L. Massey 在 1990 年提出的。该算法设计用于替代 DES(Data Encryption St…...

    2024/5/5 6:25:19
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. 【LeetCode热题100】【二叉树】二叉树的中序遍历

    题目链接&#xff1a;94. 二叉树的中序遍历 - 力扣&#xff08;LeetCode&#xff09; 中序遍历就是先遍历左子树再遍历根最后遍历右子树 class Solution { public:void traverse(TreeNode *root) {if (!root)return;traverse(root->left);ans.push_back(root->val);tra…...

    2024/5/3 21:52:01
  4. 文件系统 FTP Ubuntu 安装入门介绍

    FTP 环境: Ubuntu 14.04 blog zh_CN ubuntu14.04 Install 全新安装&#xff1a;apt-get install vsftpd 重新安装:apt-get --reinstall install vsftpd 卸载并清除配置文件&#xff1a;apt-get --purge remove vsftpd Start & Restart $ service vsftpd start $ se…...

    2024/5/2 18:20:21
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/4 23:54:56
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/5/4 23:54:56
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/5/4 23:54:56
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/5/4 23:55:17
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/5/4 23:54:56
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/5/4 23:55:05
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/5/4 23:54:56
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/5/4 23:55:16
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/5/4 23:54:56
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/5/4 18:20:48
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/5/4 23:54:56
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/5/4 23:55:17
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/5/4 23:55:06
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/5/4 23:54:56
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/4 23:55:06
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/5/4 2:59:34
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/5/4 23:55:16
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/5/4 23:54:58
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/5/4 23:55:01
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/5/4 23:54:56
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57