netty系列之:使用netty搭建websocket客户端

netty系列之:使用netty搭建websocket客户端,第1张

在网速快速提升的时代,浏览器已经成为我们访问各种服务的入口,很难想象如果离开了浏览器,我们的网络世界应该如何运作。现在恨不得把操作系统都搬上浏览器。但是并不是所有的应用都需要浏览器来执行,比如服务器和服务器之间的通信,就需要使用到自建客户端来和服务器进行交互。

本文将会介绍使用netty客户端连接websocket的原理和具体实现。

在介绍netty客户端之前,我们先看一个简单的浏览器客户端连接websocket的例子:

这里使用了浏览器最通用的语言javascript,并使用了浏览器提供的websocket API进行操作,非常的简单。

那么用netty客户端实现websocket的连接是否和javascript使用一样呢?我们一起来 探索 。

先看看netty对websocket的支持类都有哪些,接着我们看下怎么具体去使用这些工具类。

和websocket server一样,client中最核心的类也是handshaker,这里叫做WebSocketClientHandshaker。这个类有什么作用呢?一起来看看。

这个类主要实现的就是client和server端之间的握手。

我们看一下它的最长参数的构造类:

参数中有websocket连接的URI,像是:”ws://flydean.com/mypath”。

有请求子协议的类型subprotocol,有自定义的HTTP headers:customHeaders,有最大的frame payload的长度:maxFramePayloadLength,有强制timeout关闭的时间,有使用HTTP协议进行升级的URI地址。

怎么创建handshaker呢?同样的,netty提供了一个WebSocketClientHandshakerFactory方法。

WebSocketClientHandshakerFactory提供了一个newHandshaker方法,可以方便的创建各种不同版本的handshaker:

可以看到,根据传入协议版本的不同,可以分为WebSocketClientHandshaker13、WebSocketClientHandshaker08、WebSocketClientHandshaker07、WebSocketClientHandshaker00这几种。

通常来说,对于webSocket协议,为了提升传输的性能和速度,降低网络带宽占用量,在使用过程中通常会带上额外的压缩扩展。为了处理这样的压缩扩展,netty同时提供了服务器端和客户端的支持。

对于服务器端来说对应的handler叫做WebSocketServerCompressionHandler,对于客户端来说对应的handler叫做WebSocketClientCompressionHandler。

通过将这两个handler加入对应pipline中,可以实现对websocket中压缩协议扩展的支持。

对于协议的扩展有两个级别分别是permessage-deflate和perframe-deflate,分别对应PerMessageDeflateClientExtensionHandshaker和DeflateFrameClientExtensionHandshaker。

至于具体怎么压缩的,这里就不详细进行讲解了, 感兴趣的小伙伴可以自行了解。

前面讲解了netty对websocket客户端的支持之后,本节将会讲解netty到底是如何使用这些工具进行消息处理的。

首先是按照正常的逻辑创建客户端的Bootstrap,并添加handler。这里的handler就是专门为websocket定制的client端handler。

除了上面提到的WebSocketClientCompressionHandler,就是自定义的handler了。

在自定义handler中,我们需要处理两件事情,一件事情就是在channel ready的时候创建handshaker。另外一件事情就是具体websocket消息的处理了。

首先使用WebSocketClientHandshakerFactory创建handler:

然后在channel active的时候使用handshaker进行握手连接:

然后在进行消息接收处理的时候还需要判断handshaker的状态是否完成,如果未完成则调用handshaker.finishHandshake方法进行手动完成:

当handshake完成之后,就可以进行正常的websocket消息读写操作了。

websocket的消息处理比较简单,将接收到的消息转换成为WebSocketFrame进行处理即可。

本文讲解了netty提供的websocket客户端的支持和具体的对接流程,大家可以再次基础上进行扩展,以实现自己的业务逻辑。

本文的例子可以参考:learn-netty4

C++通过socket编程实现服务端与客户端的通讯,代码如下(个人环境下测试正常,如果遇到运行send发送报错,请检查服务器端口是否被占用,调试的时候请先运行服务端程序在运行客服端,一定要加载库函数ws2_32.lib,发送字符时应该多加一个空字符作为结束字符):

服务器端程序:

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

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

// Server.cpp : Defines the entry point for the console application.

#include "winsock2.h"

#pragma comment(lib, "ws2_32.lib")

#include <iostream>

using namespace std

int main(int argc, char* argv[])

{

const int BUF_SIZE = 64

WSADATAwsd //WSADATA变量

SOCKETsServer //服务器套接

SOCKETsClient //客户端套接字

SOCKADDR_INaddrServ //服务器地址

charbuf[BUF_SIZE] //接收数据缓冲区

charsendBuf[BUF_SIZE]//返回给客户端得数据

intretVal //返回值

//初始化套结字动态库

if (WSAStartup(MAKEWORD(2,2), &wsd) != 0)

{

cout <<"WSAStartup failed!" <<endl

return 1

}

//创建套接字

sServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)

if(INVALID_SOCKET == sServer)

{

cout <<"socket failed!" <<endl

WSACleanup()//释放套接字资源

return -1

}

//服务器套接字地址

addrServ.sin_family = AF_INET

addrServ.sin_port = htons(4999)

addrServ.sin_addr.s_addr = INADDR_ANY

//绑定套接字

retVal = bind(sServer, (LPSOCKADDR)&addrServ, sizeof(SOCKADDR_IN))

if(SOCKET_ERROR == retVal)

{

cout <<"bind failed!" <<endl

closesocket(sServer) //关闭套接字

WSACleanup() //释放套接字资源

return -1

}

//开始监听

retVal = listen(sServer, 1)

if(SOCKET_ERROR == retVal)

{

cout <<"listen failed!" <<endl

closesocket(sServer) //关闭套接字

WSACleanup() //释放套接字资源

return -1

}

//接受客户端请求

sockaddr_in addrClient

int addrClientlen = sizeof(addrClient)

sClient = accept(sServer,(sockaddr FAR*)&addrClient, &addrClientlen)

if(INVALID_SOCKET == sClient)

{

cout <<"accept failed!" <<endl

closesocket(sServer) //关闭套接字

WSACleanup() //释放套接字资源

return -1

}

while(true)

{

//接收客户端数据

ZeroMemory(buf, BUF_SIZE)

retVal = recv(sClient, buf, BUF_SIZE, 0)

if (SOCKET_ERROR == retVal)

{

cout <<"recv failed!" <<endl

closesocket(sServer) //关闭套接字

closesocket(sClient) //关闭套接字

WSACleanup() //释放套接字资源

return -1

}

if(buf[0] == '0')

break

cout <<"客户端发送的数据: " <<buf <<endl

cout <<"向客户端发送数据: "

cin >>sendBuf

send(sClient, sendBuf, strlen(sendBuf), 0)

}

//退出

closesocket(sServer) //关闭套接字

closesocket(sClient) //关闭套接字

WSACleanup() //释放套接字资源

return 0

}

客户端程序:

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

63

64

65

66

67

68

69

70

71

72

#include "winsock2.h"

#include <iostream>

#pragma comment(lib, "ws2_32.lib")

using namespace std

BOOL RecvLine(SOCKET s, char* buf)//读取一行数据

int main(int argc, char* argv[])

{

const int BUF_SIZE = 64

WSADATA wsd//WSADATA变量

SOCKET sHost//服务器套接字

SOCKADDR_IN servAddr//服务器地址

char buf[BUF_SIZE]//接收数据缓冲区

char bufRecv[BUF_SIZE]

int retVal//返回值

//初始化套结字动态库

if (WSAStartup(MAKEWORD(2,2), &wsd) != 0)

{

cout <<"WSAStartup failed!" <<endl

return -1

}

//创建套接字

sHost = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)

if(INVALID_SOCKET == sHost)

{

cout <<"socket failed!" <<endl

WSACleanup()//释放套接字资源

return -1

}

//设置服务器地址和端口

servAddr.sin_family =AF_INET

servAddr.sin_addr.s_addr = inet_addr("127.0.0.1")

servAddr.sin_port = htons((short)4999)

int nServAddlen = sizeof(servAddr)

//连接服务器

retVal=connect(sHost,(LPSOCKADDR)&servAddr, sizeof(servAddr))

if(SOCKET_ERROR == retVal)

{

cout <<"connect failed!" <<endl

closesocket(sHost)//关闭套接字

WSACleanup()//释放套接字资源

return -1

}

while(true)

{

//向服务器发送数据

ZeroMemory(buf, BUF_SIZE)

cout <<" 向服务器发送数据: "

cin >>buf

retVal = send(sHost, buf, strlen(buf), 0)

if (SOCKET_ERROR == retVal)

{

cout <<"send failed!" <<endl

closesocket(sHost)//关闭套接字

WSACleanup()//释放套接字资源

return -1

}

//RecvLine(sHost, bufRecv)

ZeroMemory(bufRecv, BUF_SIZE)

recv(sHost, bufRecv,BUF_SIZE , 0)// 接收服务器端的数据, 只接收5个字符

cout <<endl <<"从服务器接收数据:"<<bufRecv

cout<<"\n"

}

//退出

closesocket(sHost)//关闭套接字

WSACleanup()//释放套接字资源

return 0

}

netty框架是用在服务器端,客户端是嵌入式编程,通过自定义的tcp通信协议进行连接的,现在需求是这样的,服务器端只是用来和客户端进行通信,现在有第三方如微信端进行支付成功后在数据库里生成了一条数据,表示要往某个客户端发送指令,以下两种方式可供参考:

1、微信端生成通讯指令后调用TCP端的接口(负责通讯程序和数据库交互的),在接口程序中通过定义Socket连到通讯程序服务器端,根据通道编号去发送,但是这种会导致服务器端的tcp客户端连接变得更多。

2、直接在netty框架中定义了scheduleAtF。

当然也可借助第三方工具来完成推送。例如极光推送,极光推送具有以下功能:

1、多种消息类型

开发者可以轻松地通过极光发送各个移动平台的系统通知,还可以在控制台编辑多种富文本展示模板; 极光还提供自定义消息的透传,客户端接到消息内容后根据自己的逻辑自由处理。

2、用户和推送统计

完整的消息生命周期查询,并且可以形成“推送报表”与“用户统计报表”呈现给开发者,用来观察推送的效果和应用发展趋势。

3、短信补充

通过极光后台推送APP通知消息,对于一些重要又不能遗漏的信息可以调用极光短信的后台对未收到的客户端发送短信通知,保证消息的可靠性。

4、A/B 测试

合理的推送能够激活用户,提高用户粘性,使用A/B分组测试的科学方法,根据测试反馈的结果,帮助开发者选择最优化的推送方案。

5、极光推送安全包

为金融、新闻、政务及其他对推送安全要求极高的客户提供安全严谨、稳定可靠的信息推送解决方案

6、可定制的私有云

对于安全性要求更高,希望推送数据和系统存储在自己服务器的客户,及个性化需求需要定制开发的,性能更高要求的,或者想拥有自己推送平台的甚至要求源码授权二次开发的开发者,极光提供全功能的私有云解决方案。

深圳市和讯华谷信息技术有限公司(极光 Aurora Mobile,纳斯达克股票代码:JG)成立于2011年,是中国领先的开发者服务提供商,专注于为开发者提供稳定高效的消息推送、一键认证以及流量变现等服务,助力开发者的运营、增长与变现。同时,极光的行业应用已经拓展至市场洞察、金融风控与商业地理服务,助力各行各业优化决策、提升效率。


欢迎分享,转载请注明来源:夏雨云

原文地址:https://www.xiayuyun.com/zonghe/215758.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-04-05
下一篇2023-04-05

发表评论

登录后才能评论

评论列表(0条)

    保存