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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容