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
2.修改application.yml配置文件
2.1 修改application.properties配置文件,配置application.yml文件。
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文件的配置。
2.3 修改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
创建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);
}
}
}
使用方法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)
{
}
}
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();
}
}
WebSocketController.java 关键部分代码
Java端代码到这里救基本OK了,点击Debug运行,出现下面的效果证明运行OK。
二、iOS部分的实现
附iOS OC版本的代码地址:GitHub - angletiantang/WebSocket_OC: OC版本的WebSocket Demo,基于SocketRocket实现,上层进行简单的封装
1.创建iOS工程
推荐使用CocoaPods集成SocketRocket三方库,如果不想使用可以直接把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成功建立连接。
至此,Java端和iOS移动端的Websocket已经成功建立连接。
如果有不对的地方希望大家指出!
联系方式:
QQ:871810101
邮箱:871810101@qq.com