golang中怎么处理socket长连接
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了golang中怎么处理socket长连接相关的知识,希望对你有一定的参考价值。
实际上需要 3 个 goroutine,一个 read,一个 send,还有一个 handle。read goroutine 读,然后写入 recevice chan。
write goroutine 把 send chan 的东西写。
handle goroutine 是 conn 的主要处理逻辑,负责把 recevice chan 的东西读出来 call 业务逻辑。
业务逻辑中要写数据就直接写入 send chan。
这样就可以保证,业务逻辑的读写都是在 handle goroutine 上处理,而避免 race 产生。
如果需要定时任务(比如心跳),就在 handle goroutine 上加上一个 timer.C;
如果需要 goroutine 下发任务,在 handle goroutine 增加一个 task chan,hanlde 收到 task 后处理业务;
如果需要输出结果,那就增加 result chan,业务逻辑把数据输出即可。
----------------------------
还有,如果我开2个goroutine的话,client断开连接了,假设recv goroutine先发生err并且close(fd),那在send goroutine中该如何处理呢?有可能不应该这样处理,那应该怎么处理呢?
如果 net.Conn Close() 了,不论 Read() 阻塞还是 Write() 阻塞都会立即收到 err 返回。
一般来说,Write() 是不可能主动知道连接断开的,除非是 SetDeadline() 猜测对方断掉了,指定时间内没有写成功就认为是断开。Read() 是可以主动收到对方发来的断开(TCP FIN),但也没办法知道异常的断开(当然也可以设置超时)。
无论是谁,是确实收到 FIN 还是 Deadline 猜测断开,只要 Close() 大家就知道连接断开了。
handle goroutine 还有一个用处就是:你的程序主动结束的时候,能正确的 close conn,让对方知道你是真的断开了,而不用去猜。 参考技术A Socket通信的原理还是比较简单的, 它大致分为以下几个步骤。 服务器端的步骤如下。 (1)建立服务器端的Socket,开始侦听整个网络中的连接请求。 (2)当检测到来自客户端的连接请求时,向客户端发送收到连接请求的信息,并建立与客户端之间的...本回答被提问者采纳
Socket的长连接和短连接
讨论Socket必讨论长连接和短连接
一、长连接和短连接的概念
1、长连接与短连接的概念:前者是整个通讯过程,客户端和服务端只用一个Socket对象,长期保持Socket的连接;后者是每次请求,都新建一个Socket,处理完一个请求就直接关闭掉Socket。所以,其实区分长短连接就是:整个客户和服务端的通讯过程是利用一个Socket还是多个Socket进行的。
可能你会想:这还不简单,长连接不就是不关Socket嘛,短连接不就是每次都关Socket每次都new Socket嘛。然而事实其实并没有那么简单的,请继续看下面的整理
2、关闭流而保持Socket正常?
在网上百度了一下,发现很多人都是以关闭流还是关闭Socket来区分长连接和短连接的,其实,个人感觉这种区分方法并没有什么意义:因为这里面有一个事实是,流关闭之后,便不能进行消息的发送(对应关闭输出流)或者接受(对应关闭输入流),因为其实关闭了对应的流,对应连接也就关闭了(这里所说的连接是发送消息的通道!),所以,流关闭而保持Socket开启,是没有达到长连接的效果,贴上测试代码:
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
|
//发送核心方法 public String send(String send) throws IOException { String rtn = null ; BufferedWriter writer = null ; OutputStreamWriter ow = null ; OutputStream os = null ; try { os = socket.getOutputStream(); ow = new OutputStreamWriter(os); writer = new BufferedWriter(ow); char [] sendChar = send.toCharArray(); ArrayList<Integer> list = new ArrayList<Integer>(); for ( char ch:sendChar){ list.add(( int )ch); } //进行加密操作 list = encry(list); Iterator<Integer> it = list.iterator(); while (it.hasNext()){ writer.write(it.next()); } writer.flush(); rtn = "发送成功!" ; } finally { //注意:直接关闭流将会导致socket关闭,只能通过shutdownOutput/input的方式关闭流 //另外,流关闭之后,相当于关闭底层的连接,除非新new个socket,否则和客户端的连接相当于断开 // if(writer!=null){ // writer.close(); // } // if(ow!=null){ // ow.close(); // } // if(os!=null){ //os.close(); // } //socket.shutdownOutput();流关闭之后,相当于关闭底层的连接,除非新<br>new个socket,否则和客户端的连接相当于断开 } return rtn; } |
这是我写的一个测试的发送消息的核心方法,在关闭了对应的流(无论是输出或者输入)之后,下一次调用getInputStream或者getOutputStream会抛出异常说:Socket is closed;这里讲明一个事实:Socket和流联系着,流关闭了,Socket其实也就相当于关闭状态!
其实这个也很好理解,Socket本来就是依靠流进行关闭的,流,就只有一个,你关闭了流,Socket赖以通讯的渠道也就关闭了,与客户的连接也断开了,所以抛出异常是很合理的。
所以,流关闭而要求Socket正常通讯是不可能的!
所以,如何实现长连接?
二、长连接的正确实现方式
1、不关闭流实现长连接?
前面讨论了,流关闭了而不关闭Socket,还是无法达到长连接的效果的,所以,要长连接,流必须不能关闭!那么,是不是直接不关闭流,然后每次要发消息就直接往流里面任进去数据,然后调用flush()方法强制刷新就行了?其实不行的,这样客户端是无法正常接收信息的,你会发觉就算服务端flush了,客户端还是会一直在read方法那里阻塞!具体原因各位可以看一下java api文档的截图:
文档说明了,如果流一直可用,而且没有读到流的末尾(就是对应着对方流已经关闭或者网络断开!),read会一直阻塞!其实这样做也是可以解释清楚的:本来服务端的read方法就不知道Server端的消息什么时候发送完,说不定我以为数据发送完 了,但其实是因为网络延迟而导致部分数据延后到来(况且也不可能所有数据同时到达),所以,read方法只能一直在阻塞等待对方的应答。所以,怎么实现长连接?
2、实现长连接的方法
A、客户端自动退出开读取的动作。前面说了,就算服务端调用了flush方法进行输出刷新,客户端也不一定能退出read的动作,所以还是会阻塞。所以,退出动作必须有客户端程序自己完成,我们可以在服务端没发送完一段消息并且刷新前就进行一个写入结束符号的标志,客户端解析到结束符号时,变可直接退出read的循环读取操作,避免一直阻塞。
B、可以调用有读取一定字节到某个数组的read方法(不过好像这个不太行,毕竟每次消息的长度好像会变的),当然,这只是针对消息定长的情况。
下面贴上长连接实现后的代码(其实就是比前面的代码加多了读入结束标记符号)
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
|
//发送核心方法 public String send(String send) throws IOException { String rtn = null ; BufferedWriter writer = null ; OutputStreamWriter ow = null ; OutputStream os = null ; try { os = socket.getOutputStream(); ow = new OutputStreamWriter(os); writer = new BufferedWriter(ow); char [] sendChar = send.toCharArray(); ArrayList<Integer> list = new ArrayList<Integer>(); for ( char ch:sendChar){ list.add(( int )ch); } //进行加密操作 list = encry(list); Iterator<Integer> it = list.iterator(); while (it.hasNext()){ writer.write(it.next()); } //写入结束标志符号:% writer.write( \'%\' ); writer.flush(); rtn = "发送成功!" ; } finally { //注意:直接关闭流将会导致socket关闭,只能通过shutdownOutput/input的方式关闭流 //另外,流关闭之后,相当于关闭底层的连接,除非新new个socket,否则和客户端的连接相当于断开 // if(writer!=null){ // writer.close(); // } // if(ow!=null){ // ow.close(); // } // if(os!=null){ //os.close(); // } //socket.shutdownOutput();流关闭之后,相当于关闭底层的连接,除非新new个socket,否则和客户端的连接相当于断开 } return rtn; } |
三、短连接
短连接就基本没什么好讲的啦,只是每次关闭Socket和流时需要注意一下事情:
1、虽然前面说了流关闭了,Socket就不可用了,但是,我们还是要显式的关闭Socket的,因为在Socekt中还有中状态:叫做半连接状态,当我们只是用到输出流的时候,我们关闭了输出流,并且不能直接调用close方法,只能调用shutDown对应方法(具体请查看java API),其实输入流还是连接着的(只是我们没有用到而已!),这时候,如果没有显式关闭Soceket,很容易导致内存泄露,所以,所有流Socket都要显式关闭
2、短连接和长连接有不同的用途:对于某次服务只需要一次回话的客户,使用短连接显得简单;但是,如果该次服务需要很多交互式的操作通信,那还是长连接比较高性能,毕竟,Socket的打开和关闭都是很耗性能的。
四、总结
1、对应流关闭,Socket的对应输入(出)数据的通道也就关闭,此时无法达到长连接效果;
2、关闭Socket,记得显式关闭流与socket,顺序是线管流再关socket.
3、要实先长连接,一般需要发送结束标记符号来告诉客户端服务端的某段消息已经发送完毕,否则客户端会一直阻塞在read方法。
好,长短连接的整理到这里,不足地方请各位大佬指正哈!
参考地址:https://www.cnblogs.com/lcplcpjava/p/6581179.html
以上是关于golang中怎么处理socket长连接的主要内容,如果未能解决你的问题,请参考以下文章