IDEA Maven搭建WebSocket與iOS端的簡單實(shí)現(xiàn)
本人Java新手钝腺,學(xué)習(xí)過程中嘗試Java與移動(dòng)端的Websocket對(duì)接,如有不對(duì)的地方柔吼,望指正掰邢!
本文主要講WebSocket在Java和iOS部分的實(shí)現(xiàn)牺陶,使用的開發(fā)工具IntelliJ IDEA 和 XCode。
JDK 1.8版本辣之,Maven 3.5.4
使用Maven配置工程相關(guān)依賴庫掰伸,iOS端使用Objective-C,依賴SocketRocket(SRWebSocket)三方庫實(shí)現(xiàn)WebSocket(建議使用CocoaPods導(dǎo)入SocketRocket)怀估,并基于SocketRocket封裝SocketRocketTool工具類狮鸭,可以用少量的代碼實(shí)現(xiàn)Java端和iOS端的WebSocket連接。
本文僅僅實(shí)現(xiàn)了簡單的WebSocket連接多搀,復(fù)雜的功能需要各個(gè)開發(fā)小伙伴根據(jù)業(yè)務(wù)需求自行處理歧蕉。
一、Java部分的實(shí)現(xiàn)
先附上Java版本的代碼地址:GitHub - angletiantang/WebSocket_Java: Java版本的WebSocket Demo
1.創(chuàng)建新工程 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 應(yīng)用開發(fā)催跪,包含 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.你的項(xiàng)目 路徑下創(chuàng)建package
創(chuàng)建GetHttpSessionConfigurator.java懊蒸,RequestListener.java 荣倾,WebSocketConfig.java,WebSocketController.java四個(gè)java文件骑丸,用來實(shí)現(xiàn)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 關(guān)鍵部分代碼
Java端代碼到這里救基本OK了铸豁,點(diǎn)擊Debug運(yùn)行,出現(xiàn)下面的效果證明運(yùn)行OK黄鳍。
二推姻、iOS部分的實(shí)現(xiàn)
附iOS OC版本的代碼地址:GitHub - angletiantang/WebSocket_OC: OC版本的WebSocket Demo,基于SocketRocket實(shí)現(xiàn),上層進(jìn)行簡單的封裝
1.創(chuàng)建iOS工程
推薦使用CocoaPods集成SocketRocket三方庫,如果不想使用可以直接把SocketRocket源碼拖入工程框沟。
之后使用.xcworkspace文件打開工程。
2.實(shí)現(xiàn)代碼部分
導(dǎo)入SocketRocketTool.h和SocketRocketTool.m文件增炭。
2.1 SocketRocketTool代碼使用
使用SocketRocketTool忍燥,第一步引入頭文件。
第二步設(shè)置實(shí)現(xiàn)代理方法隙姿。
2.1 SocketRocketTool代碼實(shí)現(xiàn)
SocketRocketTool.h 中的代碼
#import <Foundation/Foundation.h>
#import <SocketRocket.h>
@protocol SocketRocketToolDelegate <NSObject>
@optional
// 收到id類型消息的回調(diào)
- (void)webSocket:(SRWebSocket *_Nullable)webSocket didReceiveMessage:(id _Nullable )message;
// 收到j(luò)son string類型消息的回調(diào)
- (void)webSocket:(SRWebSocket *_Nullable)webSocket didReceiveMessageWithString:(NSString *_Nullable)string;
// 收到data類型消息的回調(diào)
- (void)webSocket:(SRWebSocket *_Nullable)webSocket didReceiveMessageWithData:(NSData *_Nullable)data;
// 收到連接錯(cuò)誤的回調(diào)
- (void)webSocket:(SRWebSocket *_Nullable)webSocket didFailWithError:(NSError *_Nullable)error;
// 收到連接關(guān)閉的回調(diào)
- (void)webSocket:(SRWebSocket *_Nullable)webSocket didCloseWithCode:(NSInteger)code reason:(nullable NSString *)reason wasClean:(BOOL)wasClean;
// 收到Ping-Pong的回調(diào)
- (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已經(jīng)打開
- (void)webSocketDidOpen:(SRWebSocket *_Nullable)webSocket;
// webSocket已經(jīng)關(guān)閉
- (void)webSocketDidClose:(SRWebSocket *_Nullable)webSocket;
@end;
@interface SocketRocketTool : NSObject
// 代理屬性
@property(nonatomic,weak) id<SocketRocketToolDelegate> delegate;
// 觀察隊(duì)列
@property (nonatomic,strong) NSMutableSet *observerQueue;
//
@property (nonatomic,strong) NSString *wsURLString;
// 單例對(duì)象
+ (instancetype)sharedInstance;
// 連接webSocket
- (void)connect;
// 重連webSocket
- (void)reconnect;
// 關(guān)閉WebSocket的連接
- (void)closeWebSocket;
// 添加觀察
- (void)socketAddObserver:(id _Nullable )observer;
// 移除觀察
- (void)socketRemoveObserver:(id _Nullable )observer;
// 發(fā)送json數(shù)據(jù)
- (BOOL)sendString:(NSString *)string error:(NSError **)error;
// 發(fā)送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;
// 發(fā)送ping的計(jì)時(shí)器
@property(nonatomic,strong)NSTimer *pingTimer;
// 重新連接的計(jì)時(shí)器
@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
{
? ? // 發(fā)出連接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-行數(shù):%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];
? ? }
}
// 發(fā)送消息的方法
- (BOOL)sendString:(NSString *)string error:(NSError **)error{
? ? // webSocket沒有打開的狀態(tài)下
? ? if (self.socket.readyState != SR_OPEN) {
? ? ? ? if (self.delegate&&[self.delegate respondsToSelector:@selector(webSocketDidClose:)]) {
? ? ? ? ? ? [self.delegate webSocketDidClose:self.socket];
? ? ? ? }
? ? ? ? NSLog(@"發(fā)送json時(shí)webSocket沒有打開!");
? ? ? ? return NO;
? ? }
? ? if ([self stringIsNull:string]) {
? ? ? ? NSLog(@"[方法:%s-行數(shù):%d]發(fā)送json數(shù)據(jù)為空!",__FUNCTION__,__LINE__);
? ? ? ? return NO;
? ? }
? ? NSLog(@"\n[方法:%s-行數(shù):%d]\n發(fā)送消息:\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(@"發(fā)送data時(shí)webSocket沒有打開!");
? ? ? ? return NO;
? ? }
? ? if (data.length==0) {
? ? ? ? NSLog(@"[方法:%s-行數(shù):%d]發(fā)送data數(shù)據(jù)為空!",__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-行數(shù):%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-行數(shù):%d]\nwebSocketDidOpen!\n",__FUNCTION__,__LINE__);
? ? // 連接webSocket成功時(shí)發(fā)出的通知
//? ? NSNotification *notification = [[NSNotification alloc]initWithName: kWebSocketConnectDidSuccessNoti object:nil userInfo:nil];
//? ? [[NSNotificationCenter defaultCenter]postNotification:notification];
? ? // webSockeet連接每一秒發(fā)送一個(gè)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-行數(shù):%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-行數(shù):%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斷開連接發(fā)出的通知
//? ? 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
三梅垄、運(yùn)行工程
注意需要保證電腦連接 和 手機(jī)連接在同一個(gè)局域網(wǎng)之內(nèi)。
然后運(yùn)行工程输玷,Websocket成功建立連接队丝。
至此,Java端和iOS移動(dòng)端的Websocket已經(jīng)成功建立連接欲鹏。
如果有不對(duì)的地方希望大家指出机久!
聯(lián)系方式:
QQ:871810101
郵箱:871810101@qq.com