通信协议

IDEA Maven搭建WebSocket与iOS端的简单实现

2018-10-26  本文已影响100人  睡鼠zzZ

IDEA Maven搭建WebSocket与iOS端的简单实现

本人Java新手,学习过程中尝试Java与移动端的Websocket对接,如有不对的地方,望指正!

本文主要讲WebSocket在Java和iOS部分的实现,使用的开发工具IntelliJ IDEA 和 XCode。

JDK 1.8版本,Maven 3.5.4

基本环境

使用Maven配置工程相关依赖库,iOS端使用Objective-C,依赖SocketRocket(SRWebSocket)三方库实现WebSocket(建议使用CocoaPods导入SocketRocket),并基于SocketRocket封装SocketRocketTool工具类,可以用少量的代码实现Java端和iOS端的WebSocket连接。

本文仅仅实现了简单的WebSocket连接,复杂的功能需要各个开发小伙伴根据业务需求自行处理。

一、Java部分的实现

先附上Java版本的代码地址:GitHub - angletiantang/WebSocket_Java: Java版本的WebSocket Demo

1.创建新工程 Spring Initializr -> Next

1.选择类型
2.设置项目名称 3.配置基本信息
4.创建Maven项目,包含Websocket和MySQL
5.完成创建

2.修改application.yml配置文件

2.1 修改application.properties配置文件,配置application.yml文件。

重命名application.yml文件1 重命名application.yml文件2

2.2 配置application.yml文件

spring:

aop:

    auto: true

proxy-target-class: true

datasource:

    type: com.zaxxer.hikari.HikariDataSource

driver-class-name: com.mysql.cj.jdbc.Driver

url: jdbc:mysql://您的MySQL配置路径?zeroDateTimeBehavior=CONVERT_TO_NULL&useUnicode=true&characterEncoding=UTF8&useSSL=false&serverTimezone=GMT&allowPublicKeyRetrieval=true

username: MySql用户名(一般是root)

password: MySql密码

hikari:

      auto-commit: true

minimum-idle: 2

maximum-pool-size: 10

connection-timeout: 10000

max-lifetime: 600000

idle-timeout: 60000

validation-timeout: 1000

leak-detection-threshold: 30000

server:

  port: 8081

logging.level.com.gjh: DEBUG

hystrix:

command:

default:

execution:

isolation:

thread:

            timeoutInMilliseconds: 60000

sys:

  version: v0.0.1.1

配置application.yml

完成application.yml文件的配置。

2.3 修改pom.xml配置文件

配置pom.xml依赖

pom.xml文件配置:

<dependencies>

  <!--Spring Boot -->

  <!--支持 Web 应用开发,包含 Tomcat 和 spring-mvc。 -->

  <dependency>

  <groupId>org.springframework.boot</groupId>

  <artifactId>spring-boot-starter-web</artifactId>

  <version>${spring-boot.version}</version>

  </dependency>

  <dependency>

  <groupId>commons-io</groupId>

  <artifactId>commons-io</artifactId>

  <version>2.4</version>

  </dependency>

  <dependency>

  <groupId>org.springframework.boot</groupId>

  <artifactId>spring-boot-starter-jdbc</artifactId>

  <version>${spring-boot.version}</version>

  </dependency>

  <dependency>

  <groupId>org.springframework.boot</groupId>

  <artifactId>spring-boot-devtools</artifactId>

  <version>${spring-boot.version}</version>

  <optional>true</optional>

  </dependency>

  <dependency>

  <groupId>org.springframework.boot</groupId>

  <artifactId>spring-boot-dependencies</artifactId>

  <version>${spring-boot.version}</version>

  <type>pom</type>

  <scope>import</scope>

  </dependency>

  <dependency>

  <groupId>mysql</groupId>

  <artifactId>mysql-connector-java</artifactId>

  <version>8.0.11</version>

  </dependency>

  <!-- 连接池配置 -->

  <dependency>

  <groupId>com.zaxxer</groupId>

  <artifactId>HikariCP</artifactId>

  <version>2.7.4</version>

  </dependency>

  <dependency>

  <groupId>com.google.code.gson</groupId>

  <artifactId>gson</artifactId>

  <version>2.8.5</version>

  </dependency>

  <dependency>

  <groupId>com.alibaba</groupId>

  <artifactId>fastjson</artifactId>

  <version>1.2.44</version>

  </dependency>

  <dependency>

  <groupId>org.springframework.boot</groupId>

  <artifactId>spring-boot-starter-test</artifactId>

  <version>${spring-boot.version}</version>

  <scope>test</scope>

  </dependency>

  <!--<dependency>-->

  <!--<groupId>com.fasterxml.jackson.core</groupId>-->

  <!--<artifactId>jackson-databind</artifactId>-->

  <!--<version>2.7.4</version>-->

  <!--</dependency>-->

  <dependency>

  <groupId>org.jetbrains</groupId>

  <artifactId>annotations</artifactId>

  <version>RELEASE</version>

  </dependency>

  <dependency>

  <groupId>org.java-websocket</groupId>

  <artifactId>Java-WebSocket</artifactId>

  <version>1.3.0</version>

  </dependency>

  <dependency>

  <groupId>org.springframework.boot</groupId>

  <artifactId>spring-boot-starter-websocket</artifactId>

  <version>${spring-boot.version}</version>

  </dependency>

</dependencies>

完成pom.xml文件的配置。

2.4 Java编码

在src-main-java-com.你的项目 路径下创建package

创建package 创建相关文件

创建GetHttpSessionConfigurator.java,RequestListener.java ,WebSocketConfig.java,WebSocketController.java四个java文件,用来实现Websocket。

其中GetHttpSessionConfigurator.java用来获取httpSession,继承于Configurator

package com.websocket.demo.config;

import javax.servlet.http.HttpSession;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
import javax.websocket.server.ServerEndpointConfig.Configurator;

public class GetHttpSessionConfigurator extends Configurator
{
    public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) {
        HttpSession httpSession = (HttpSession) request.getHttpSession();
        if(httpSession != null){
            config.getUserProperties().put(HttpSession.class.getName(), httpSession);
        }
    }
}

GetHttpSessionConfigurator.java

使用方法java WebSocket之获取HttpSession,登录用户的所有信息-博客-最代码

RequestListener.java 中的代码部分

package com.websocket.demo.config;

import javax.servlet.ServletRequestEvent;

import javax.servlet.ServletRequestListener;

import javax.servlet.annotation.WebListener;

import javax.servlet.http.HttpServletRequest;

import org.springframework.stereotype.Component;

@WebListener//配置Listener

@Component

public class RequestListener implements ServletRequestListener

{

    public void requestInitialized(ServletRequestEvent sre)

    {

        //将所有request请求都携带上httpSession

        ((HttpServletRequest) sre.getServletRequest()).getSession();

    }

    public RequestListener()

    {

    }

    public void requestDestroyed(ServletRequestEvent arg0)

    {

    }

}

RequestListener.java

WebSocketConfig.java 代码

package com.websocket.demo.config;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration

public class WebSocketConfig {

    @Bean

    public ServerEndpointExporter serverEndpointExporter() {

        return new ServerEndpointExporter();

    }

}

WebSocketConfig.java

WebSocketController.java 关键部分代码

建立成功
连接关闭 收到消息 发生错误 发送消息

Java端代码到这里救基本OK了,点击Debug运行,出现下面的效果证明运行OK。

启动运行OK

二、iOS部分的实现

附iOS OC版本的代码地址:GitHub - angletiantang/WebSocket_OC: OC版本的WebSocket Demo,基于SocketRocket实现,上层进行简单的封装

1.创建iOS工程

创建新工程1 创建新工程2 创建新工程3

推荐使用CocoaPods集成SocketRocket三方库,如果不想使用可以直接把SocketRocket源码拖入工程。

搜索SocketRocket
搜索结果 创建和编辑Podfile文件 编辑Podfile文件 pod install安装
导入SocketRocket完成

之后使用.xcworkspace文件打开工程。

2.实现代码部分

导入SocketRocketTool.h和SocketRocketTool.m文件。

2.1 SocketRocketTool代码使用

使用SocketRocketTool,第一步引入头文件。

引入头文件

第二步设置实现代理方法。

设置代理 实现代理方法

2.1 SocketRocketTool代码实现

SocketRocketTool.h 中的代码

#import <Foundation/Foundation.h>

#import <SocketRocket.h>

@protocol SocketRocketToolDelegate <NSObject>

@optional

// 收到id类型消息的回调

- (void)webSocket:(SRWebSocket *_Nullable)webSocket didReceiveMessage:(id _Nullable )message;

// 收到json string类型消息的回调

- (void)webSocket:(SRWebSocket *_Nullable)webSocket didReceiveMessageWithString:(NSString *_Nullable)string;

// 收到data类型消息的回调

- (void)webSocket:(SRWebSocket *_Nullable)webSocket didReceiveMessageWithData:(NSData *_Nullable)data;

// 收到连接错误的回调

- (void)webSocket:(SRWebSocket *_Nullable)webSocket didFailWithError:(NSError *_Nullable)error;

// 收到连接关闭的回调

- (void)webSocket:(SRWebSocket *_Nullable)webSocket didCloseWithCode:(NSInteger)code reason:(nullable NSString *)reason wasClean:(BOOL)wasClean;

// 收到Ping-Pong的回调

- (void)webSocket:(SRWebSocket *_Nullable)webSocket didReceivePingWithData:(nullable NSData *)data;

- (void)webSocket:(SRWebSocket *_Nullable)webSocket didReceivePong:(nullable NSData *)pongData;

//

- (BOOL)webSocketShouldConvertTextFrameToString:(SRWebSocket *_Nullable)webSocket NS_SWIFT_NAME(webSocketShouldConvertTextFrameToString(_:));

// webSocket已经打开

- (void)webSocketDidOpen:(SRWebSocket *_Nullable)webSocket;

// webSocket已经关闭

- (void)webSocketDidClose:(SRWebSocket *_Nullable)webSocket;

@end;

@interface SocketRocketTool : NSObject

// 代理属性

@property(nonatomic,weak) id<SocketRocketToolDelegate> delegate;

// 观察队列

@property (nonatomic,strong) NSMutableSet *observerQueue;

//

@property (nonatomic,strong) NSString *wsURLString;

// 单例对象

+ (instancetype)sharedInstance;

// 连接webSocket

- (void)connect;

// 重连webSocket

- (void)reconnect;

// 关闭WebSocket的连接

- (void)closeWebSocket;

// 添加观察

- (void)socketAddObserver:(id _Nullable )observer;

// 移除观察

- (void)socketRemoveObserver:(id _Nullable )observer;

// 发送json数据

- (BOOL)sendString:(NSString *)string error:(NSError **)error;

// 发送data

- (BOOL)sendData:(nullable NSData *)data error:(NSError **)error;

@end


SocketRocketTool.m 中的代码

#import "SocketRocketTool.h"

// 接受SRWebSocketDelegate

@interface SocketRocketTool()<SRWebSocketDelegate>

// SRWebSocket

@property (nonatomic,strong)SRWebSocket *socket;

// 发送ping的计时器

@property(nonatomic,strong)NSTimer *pingTimer;

// 重新连接的计时器

@property(nonatomic,strong)NSTimer *reconnetTimer;

@end

static const NSTimeInterval WebSocketHeartBeatTimeInterval = 1.0;

@implementation SocketRocketTool

// 单例方法

static SocketRocketTool * instance = nil;

+ (instancetype)sharedInstance

{

    static dispatch_once_t onceToken ;

    dispatch_once(&onceToken, ^{

        instance = [[super allocWithZone:NULL] init];

    }) ;

    return instance ;

}

+ (id)allocWithZone:(struct _NSZone *)zone

{

    return [SocketRocketTool sharedInstance];

}

- (id)copyWithZone:(struct _NSZone *)zone

{

    return [SocketRocketTool sharedInstance];

}

#pragma mark SRWebSocket  Open&Close&Send

// 连接webSocket

- (void)connect

{

    // 发出连接webSocket的通知,需不需要使用由自己决定

//    NSNotification *notification = [[NSNotification alloc]initWithName:kWebSocketWillConnectNoti object:nil userInfo:nil];

//    [[NSNotificationCenter defaultCenter]postNotification:notification];

    if (![self isNullObject:self.socket])

    {

        [self.socket close];

        self.socket = nil;

    }

    self.socket = [[SRWebSocket alloc] initWithURLRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:self.wsURLString]]];

    self.observerQueue=[[NSMutableSet alloc] init];

    self.socket.delegate=self;

    [self.socket open];

    NSLog(@"[方法:%s-行数:%d]WebSocket_Host_URL:%@",__FUNCTION__,__LINE__,self.wsURLString);

}

-(void)socketAddObserver:(id)observer{

    if (![self.observerQueue containsObject:observer]) {

        [self.observerQueue addObject:observer];

    }

}

-(void)socketRemoveObserver:(id)observer{

    if ([self.observerQueue containsObject:observer]) {

        [self.observerQueue removeObject:observer];

    }

}

// 发送消息的方法

- (BOOL)sendString:(NSString *)string error:(NSError **)error{

    // webSocket没有打开的状态下

    if (self.socket.readyState != SR_OPEN) {

        if (self.delegate&&[self.delegate respondsToSelector:@selector(webSocketDidClose:)]) {

            [self.delegate webSocketDidClose:self.socket];

        }

        NSLog(@"发送json时webSocket没有打开!");

        return NO;

    }

    if ([self stringIsNull:string]) {

        NSLog(@"[方法:%s-行数:%d]发送json数据为空!",__FUNCTION__,__LINE__);

        return NO;

    }

    NSLog(@"\n[方法:%s-行数:%d]\n发送消息:\n%@\n",__FUNCTION__,__LINE__,string);

    [self.socket send:string];

    return YES;

}

- (BOOL)sendData:(nullable NSData *)data error:(NSError **)error{

    if (self.socket.readyState != SR_OPEN) {

        if (self.delegate && [self.delegate respondsToSelector:@selector(webSocketDidClose:)]) {

            [self.delegate webSocketDidClose:self.socket];

        }

        NSLog(@"发送data时webSocket没有打开!");

        return NO;

    }

    if (data.length==0) {

        NSLog(@"[方法:%s-行数:%d]发送data数据为空!",__FUNCTION__,__LINE__);

        return NO;

    }

    [self.socket send:data ];

    return YES;

}

#pragma mark - SRWebSocketDelegate

- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message

{

    NSString * aMessage = (NSString*)message?:@"";

    if (![self stringIsNull:aMessage])

    {

        NSDictionary *dic = @{@"message":aMessage};

        NSLog(@"webSocket根源收到的消息:%@",dic);

//        NSNotification *notification = [[NSNotification alloc]initWithName:kWebSocketReciveMessgeNoti object:nil userInfo:dic];

//        [[NSNotificationCenter defaultCenter]postNotification:notification];

    }else

    {

        NSLog(@"[方法:%s-行数:%d] message is null !",__FUNCTION__,__LINE__);

    }

    if (self.delegate&&[self.delegate respondsToSelector:@selector(webSocket:didReceiveMessage:)]) {

        [self.delegate webSocket:webSocket didReceiveMessage:message];

    }

}

- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessageWithString:(NSString *)string{

    if (self.delegate&&[self.delegate respondsToSelector:@selector(webSocket:didReceiveMessageWithString:)]) {

        [self.delegate webSocket:webSocket didReceiveMessageWithString:string];

    }

}

- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessageWithData:(NSData *)data{

    if (self.delegate&&[self.delegate respondsToSelector:@selector(webSocket:didReceiveMessageWithData:)]) {

        [self.delegate webSocket:webSocket didReceiveMessageWithData:data];

    }

}

- (void)webSocketDidOpen:(SRWebSocket *)webSocket{

    NSLog(@"[方法:%s-行数:%d]\nwebSocketDidOpen!\n",__FUNCTION__,__LINE__);

    // 连接webSocket成功时发出的通知

//    NSNotification *notification = [[NSNotification alloc]initWithName: kWebSocketConnectDidSuccessNoti object:nil userInfo:nil];

//    [[NSNotificationCenter defaultCenter]postNotification:notification];

    // webSockeet连接每一秒发送一个Ping指令

    [self startPing];

//    NSDictionary * testDic = @{@"key1":@"value1",@"key2":@"value2"};

//    NSString * dicString = [DictionaryToJsonTool dictionaryToJSONString:testDic];

    NSError *error;

    [self sendString:@"testString" error:&error];

    if (self.delegate&&[self.delegate respondsToSelector:@selector(webSocketDidOpen:)]) {

        [self.delegate webSocketDidOpen:webSocket];

    }

}

- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error{

    NSLog(@"[方法:%s-行数:%d] [webSocket connect fail error resson:]%@\n[closed createTime]%@[closed host]%@\n",__FUNCTION__, __LINE__,error.description,[NSDate date],webSocket.url);

    if (self.delegate&&[self.delegate respondsToSelector:@selector(webSocket:didFailWithError:)]) {

        [self.delegate webSocket:webSocket didFailWithError:error];

    }

}

- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(nullable NSString *)reason wasClean:(BOOL)wasClean{

    NSLog(@"[方法:%s-行数:%d][webSocketClosed with reason:]%@\n[closed createTime:]%@\n[closed host:]%@\n" ,__FUNCTION__, __LINE__,reason,[NSDate date],webSocket.url);

    if (self.delegate&&[self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) {

        [self.delegate webSocket:webSocket didCloseWithCode:code reason:reason wasClean:wasClean];

    }

    // webSocket断开连接发出的通知

//    NSNotification *notification = [[NSNotification alloc]initWithName: kWebSocketConnectDidCloseNoti object:nil userInfo:nil];

//    [[NSNotificationCenter defaultCenter]postNotification:notification];

}

- (void)webSocket:(SRWebSocket *)webSocket didReceivePingWithData:(nullable NSData *)data{

    if (self.delegate&&[self.delegate respondsToSelector:@selector(webSocket:didReceivePingWithData:)]) {

        [self.delegate webSocket:webSocket didReceivePingWithData:data];

    }

}

- (void)webSocket:(SRWebSocket *)webSocket didReceivePong:(nullable NSData *)pongData{

    if (self.delegate&&[self.delegate respondsToSelector:@selector(webSocket:didReceivePong:)]) {

        [self.delegate webSocket:webSocket didReceivePong:pongData];

    }

}

#pragma -mark Heartbeat

-(void)startPing{

    if (_pingTimer) {

        [_pingTimer invalidate];

        _pingTimer = nil;

    }

    if (_reconnetTimer) {

        [_reconnetTimer invalidate];

        _reconnetTimer = nil;

    }

    _pingTimer = [NSTimer scheduledTimerWithTimeInterval:WebSocketHeartBeatTimeInterval target:self selector:@selector(sendPing:) userInfo:nil repeats:YES];

    [[NSRunLoop currentRunLoop] addTimer:_pingTimer forMode:NSRunLoopCommonModes];

}

-(void)sendPing:(id)sender{

    if (self.socket.readyState == SR_OPEN)

    {

        NSError *error;

        [self.socket sendPing:nil];

        if (error) {

            NSLog(@"%s:%d %@", __FUNCTION__, __LINE__,error);

        }

    }else

    {

        [_pingTimer invalidate];

        _pingTimer = nil;

        [self reconnect];

    }

}

- (void)destoryHeartBeat{

    if (_pingTimer) {

        [_pingTimer invalidate];

        _pingTimer = nil;

    }

}

#pragma -mark Reconnect

-(void)reconnect{

    // 连接

    [self connect];

    NSLog(@"[%s:%d]reconnecting! ",__FUNCTION__,__LINE__);

    [self closeWebSocket];

    _reconnetTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(startReconnect) userInfo:nil repeats:YES];

    [[NSRunLoop currentRunLoop] addTimer:_reconnetTimer forMode:NSRunLoopCommonModes];

}

-(void)startReconnect

{

    self.socket = nil;

    [self connect];

    NSLog(@"%s:%d socket reconnecting!", __FUNCTION__, __LINE__);

}

-(void)closeWebSocket{

    if (self.socket){

        [self.socket close];

        self.socket = nil;

        [self destoryHeartBeat];

    }

}

#pragma -mark util

- (BOOL)stringIsNull:(NSString *)string

{

    if (![string isKindOfClass:[NSString class]]) {

        return YES;

    }

    if (!string || [string isKindOfClass:[NSNull class]] || string.length == 0 || [string isEqualToString:@""]) {

        return YES;

    }else{

        return NO;

    }

}

- (BOOL)isNullObject:(id)anObject

{

    if (!anObject || [anObject isKindOfClass:[NSNull class]]) {

        return YES;

    }else{

        return NO;

    }

}

@end


三、运行工程

注意需要保证电脑连接 和 手机连接在同一个局域网之内。

然后运行工程,Websocket成功建立连接。

XCode打印 IDEA打印

至此,Java端和iOS移动端的Websocket已经成功建立连接。

如果有不对的地方希望大家指出!

联系方式:

QQ:871810101

邮箱:871810101@qq.com

上一篇下一篇

猜你喜欢

热点阅读