├── NetAssist.exe ├── README.md ├── app ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── luliang │ │ └── rxsocket │ │ ├── MainActivity.java │ │ └── RxSocket.java │ └── res │ ├── layout │ └── activity_main.xml │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties └── settings.gradle /NetAssist.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiangLuDev/RxSocket/5a90b89956ddc36e21290f71a03cd7ea102aed2c/NetAssist.exe -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Socket连接-RxSocket 2 | #### 功能简介 3 | > - 服务器断开、网络错误等各种方式导致连接失败都会自动一直重连上服务器。 4 | > - 心跳反馈,设置一个时间,每隔一个时间向服务器发送数据,保持在线。 5 | 6 | ### 使用方式(Android端) 7 | > Android端扫码下载体验 8 | 9 | ![RxSocket.png](https://upload-images.jianshu.io/upload_images/2635045-a02398bfe2bf384d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/200) 10 | #### 1.初始化RxSocket 11 | > PS.此项目使用Rx2 12 | ```java 13 | //初始化 14 | RxSocket rxSocket = RxSocket.getInstance(); 15 | ``` 16 | #### 2.重连机制连接 17 | ```java 18 | /** 19 | * 重连机制的订阅 20 | * 参数1:服务器地址 21 | * 参数2:端口号 22 | */ 23 | rxSocket.reconnection(HOST, PORT) 24 | .subscribe(s -> Log.d("server response data", s)); 25 | ``` 26 | #### 3.心跳重连机制连接(不可动态改变心跳数据) 27 | ```java 28 | /** 29 | * 心跳、重连机制的订阅 30 | * 参数1:服务器地址 31 | * 参数2:端口号 32 | * 参数3:心跳发送时间 33 | * 参数4:心跳发送信息 34 | */ 35 | rxSocket.reconnectionAndHeartBeat(HOST, PORT, 5, "---Hello---") 36 | .subscribe(s -> Log.d("server response data", s)); 37 | ``` 38 | 39 | #### 4.心跳重连机制连接(可动态改变心跳数据) 40 | > 动态改变心跳数据主要针对于,比如电量cpu内存温度等情况需要动态设置心跳数据。 41 | ```java 42 | /** 43 | * 心跳、重连机制的订阅(心跳数据动态改变) 44 | * 参数1:服务器地址 45 | * 参数2:端口号 46 | * 参数3:心跳发送时间 47 | */ 48 | rxSocket.reconnectionAndHeartBeat(HOST, PORT, 5) 49 | .flatMap(aLong -> mRxSocket.send(mEtHeartText.getText().toString())) 50 | .compose(mRxSocket.heartBeatChange()) 51 | .subscribe(s -> Log.d("server response data", s)); 52 | ``` 53 | 54 | #### 5.发送数据 55 | ``` java 56 | mSubscribe = rxSocket.send("hello").subscribe() 57 | ``` 58 | #### 6.应用退出或者不需要socket取消订阅 59 | ``` java 60 | //取消订阅 61 | mSubscribe.dispose(); 62 | ``` 63 | ### 使用方式(服务端) 64 | > 使用此软件就不用自己写服务器,先模拟自己测试完毕再跟服务器联调。 65 | > [服务端模拟软件下载](https://github.com/LiangLuDev/RxSocket/blob/167699bdca5a44308affb8d97036e309500adcff/NetAssist.exe)(仅支持Windows系统) 66 | > 按照图片标注设置就行了。测试是否接收到数据能否发送数据就行了。 67 | 68 | ![网络调试助手.png](https://upload-images.jianshu.io/upload_images/2635045-f1f82da32fc39bed.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/800) 69 | ### 意见反馈 70 | 如果遇到问题或者好的建议,请反馈到:issue、927195249@qq.com 或者LiangLuDev@gmail.com 71 | 72 | 如果觉得对你有用的话,赞一下吧! 73 | 74 | 75 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 26 5 | defaultConfig { 6 | applicationId "com.luliang.rxsocket" 7 | minSdkVersion 15 8 | targetSdkVersion 26 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | compileOptions { 20 | targetCompatibility 1.8 21 | sourceCompatibility 1.8 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation fileTree(dir: 'libs', include: ['*.jar']) 27 | implementation 'com.android.support:appcompat-v7:26.1.0' 28 | implementation 'com.android.support.constraint:constraint-layout:1.1.0' 29 | testImplementation 'junit:junit:4.12' 30 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 31 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 32 | 33 | /** 34 | * RxJava 35 | */ 36 | implementation "io.reactivex.rxjava2:rxjava:2.0.1" 37 | implementation "io.reactivex.rxjava2:rxandroid:2.0.1" 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/luliang/rxsocket/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.luliang.rxsocket; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.text.TextUtils; 7 | import android.view.View; 8 | import android.widget.Button; 9 | import android.widget.EditText; 10 | import android.widget.TextView; 11 | import android.widget.Toast; 12 | 13 | import io.reactivex.ObservableSource; 14 | import io.reactivex.disposables.Disposable; 15 | import io.reactivex.functions.Function; 16 | 17 | public class MainActivity extends AppCompatActivity { 18 | Button mBtnSend; 19 | Button mBtnConnect; 20 | Button mBtnHeartConnect; 21 | Button mBtnHeartDataConnect; 22 | EditText mEtSendText; 23 | EditText mEtHeartText; 24 | EditText mEtHost; 25 | EditText mEtPort; 26 | TextView mTvResponse; 27 | private Disposable mSubscribe; 28 | 29 | 30 | @SuppressLint("SetTextI18n") 31 | @Override 32 | protected void onCreate(Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | setContentView(R.layout.activity_main); 35 | 36 | 37 | mBtnSend = findViewById(R.id.btn_send); 38 | mBtnConnect = findViewById(R.id.btn_connect); 39 | mBtnHeartConnect = findViewById(R.id.btn_heart_connect); 40 | mBtnHeartDataConnect = findViewById(R.id.btn_heart_data_connect); 41 | mEtSendText = findViewById(R.id.et_send_text); 42 | mEtHost = findViewById(R.id.et_host); 43 | mEtPort = findViewById(R.id.et_port); 44 | mEtHeartText = findViewById(R.id.et_heart_text); 45 | mTvResponse = findViewById(R.id.tv_response); 46 | 47 | 48 | //初始化 49 | RxSocket mRxSocket = RxSocket.getInstance(); 50 | 51 | mBtnConnect.setOnClickListener(view -> { 52 | if (TextUtils.isEmpty(mEtHost.getText())) { 53 | Toast.makeText(MainActivity.this, "请输入服务器地址", Toast.LENGTH_SHORT).show(); 54 | return; 55 | } 56 | if (TextUtils.isEmpty(mEtPort.getText())) { 57 | Toast.makeText(MainActivity.this, "请输入端口号", Toast.LENGTH_SHORT).show(); 58 | return; 59 | } 60 | /** 61 | * 重连机制的订阅 62 | * 参数1:服务器地址 63 | * 参数2:端口号 64 | */ 65 | mRxSocket.reconnection(mEtHost.getText().toString(), Integer.parseInt(mEtPort.getText().toString())) 66 | .subscribe(s -> mTvResponse.setText("接收数据:" + s)); 67 | }); 68 | 69 | mBtnHeartConnect.setOnClickListener(view -> { 70 | if (TextUtils.isEmpty(mEtHost.getText())) { 71 | Toast.makeText(MainActivity.this, "请输入服务器地址", Toast.LENGTH_SHORT).show(); 72 | return; 73 | } 74 | if (TextUtils.isEmpty(mEtPort.getText())) { 75 | Toast.makeText(MainActivity.this, "请输入端口号", Toast.LENGTH_SHORT).show(); 76 | return; 77 | } 78 | /** 79 | * 心跳、重连机制的订阅(心跳数据不可改变) 80 | * 参数1:服务器地址 81 | * 参数2:端口号 82 | * 参数3:心跳发送时间 83 | * 参数4:心跳发送信息 84 | */ 85 | mRxSocket.reconnectionAndHeartBeat(mEtHost.getText().toString(), Integer.parseInt(mEtPort.getText().toString()), 5, "---Hello---") 86 | .subscribe(s -> mTvResponse.setText("接收数据:" + s)); 87 | }); 88 | 89 | mBtnHeartDataConnect.setOnClickListener(view -> { 90 | if (TextUtils.isEmpty(mEtHost.getText())) { 91 | Toast.makeText(MainActivity.this, "请输入服务器地址", Toast.LENGTH_SHORT).show(); 92 | return; 93 | } 94 | if (TextUtils.isEmpty(mEtPort.getText())) { 95 | Toast.makeText(MainActivity.this, "请输入端口号", Toast.LENGTH_SHORT).show(); 96 | return; 97 | } 98 | if (TextUtils.isEmpty(mEtHeartText.getText())) { 99 | Toast.makeText(MainActivity.this, "请输入心跳传输数据", Toast.LENGTH_SHORT).show(); 100 | return; 101 | } 102 | /** 103 | * 心跳、重连机制的订阅(心跳数据动态改变) 104 | * 参数1:服务器地址 105 | * 参数2:端口号 106 | * 参数3:心跳发送时间 107 | */ 108 | mRxSocket.reconnectionAndHeartBeat(mEtHost.getText().toString(), Integer.parseInt(mEtPort.getText().toString()), 5) 109 | .flatMap(aLong -> mRxSocket.send(mEtHeartText.getText().toString())) 110 | .compose(mRxSocket.heartBeatChange()) 111 | .subscribe(s -> mTvResponse.setText("接收数据:" + s)); 112 | }); 113 | 114 | 115 | mBtnSend.setOnClickListener(new View.OnClickListener() { 116 | @Override 117 | public void onClick(View view) { 118 | mSubscribe = mRxSocket.send(mEtSendText.getText().toString()).subscribe(); 119 | } 120 | }); 121 | } 122 | 123 | 124 | @Override 125 | protected void onDestroy() { 126 | if (mSubscribe != null) { 127 | mSubscribe.dispose(); 128 | } 129 | super.onDestroy(); 130 | 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /app/src/main/java/com/luliang/rxsocket/RxSocket.java: -------------------------------------------------------------------------------- 1 | package com.luliang.rxsocket; 2 | 3 | 4 | import android.util.Log; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.BufferedWriter; 8 | import java.io.IOException; 9 | import java.io.InputStreamReader; 10 | import java.io.OutputStreamWriter; 11 | import java.net.Socket; 12 | import java.util.concurrent.TimeUnit; 13 | import java.util.concurrent.atomic.AtomicReference; 14 | 15 | import io.reactivex.Observable; 16 | import io.reactivex.ObservableEmitter; 17 | import io.reactivex.ObservableOnSubscribe; 18 | import io.reactivex.ObservableSource; 19 | import io.reactivex.ObservableTransformer; 20 | import io.reactivex.android.schedulers.AndroidSchedulers; 21 | import io.reactivex.functions.Function; 22 | import io.reactivex.schedulers.Schedulers; 23 | 24 | 25 | /** 26 | * @author LuLiang 27 | * @github https://github.com/LiangLuDev 28 | * RxSocket 连接处理 29 | */ 30 | 31 | 32 | public class RxSocket { 33 | 34 | 35 | private static final AtomicReference INSTANCE = new AtomicReference<>(); 36 | 37 | private Socket socket; 38 | 39 | private boolean connect = false; 40 | private boolean threadStart = false; 41 | 42 | private BufferedReader bufferedReader; 43 | private BufferedWriter bufferedWriter; 44 | 45 | public static RxSocket getInstance() { 46 | for (; ; ) { 47 | RxSocket current = INSTANCE.get(); 48 | if (null != current) { 49 | return current; 50 | } 51 | current = new RxSocket(); 52 | if (INSTANCE.compareAndSet(null, current)) { 53 | return current; 54 | } 55 | } 56 | } 57 | 58 | /** 59 | * 重连机制的订阅 60 | * 61 | * @param host 62 | * @param port 63 | * @return 64 | */ 65 | public Observable reconnection(String host, int port) { 66 | return connect(host, port) 67 | .compose(this.read()) 68 | .retry() 69 | .compose(io_main()); 70 | } 71 | 72 | /** 73 | * 心跳、重连机制的订阅(心跳数据初始化后不再变化,适用于心跳包数据不改变的情况) 74 | * 75 | * @param host 76 | * @param port 77 | * @param period 心跳频率 78 | * @param data 心跳数据 79 | * @return 80 | */ 81 | public Observable reconnectionAndHeartBeat(String host, int port, int period, String data) { 82 | return connect(host, port) 83 | .compose(this.heartBeat(period, data)) 84 | .compose(this.read()) 85 | .retry() 86 | .compose(io_main()); 87 | } 88 | 89 | /** 90 | * 心跳、重连机制的订阅(心跳数据可以动态改变,适用于心跳包数据动态变化的情况) 91 | * 92 | * @param host 93 | * @param port 94 | * @param period 心跳频率 95 | * @return 96 | */ 97 | public Observable reconnectionAndHeartBeat(String host, int port, final int period) { 98 | return connect(host, port) 99 | .flatMap(new Function>() { 100 | @Override 101 | public ObservableSource apply(Boolean aBoolean) throws Exception { 102 | return interval(period); 103 | } 104 | }); 105 | } 106 | 107 | /** 108 | * 心跳包数据动态改变后记得调用该变换方式 109 | * 110 | * @return 111 | */ 112 | public ObservableTransformer heartBeatChange() { 113 | return new ObservableTransformer() { 114 | @Override 115 | public ObservableSource apply(Observable upstream) { 116 | return upstream.flatMap(new Function>() { 117 | @Override 118 | public ObservableSource apply(Boolean aBoolean) throws Exception { 119 | return Observable.create(new ObservableOnSubscribe() { 120 | @Override 121 | public void subscribe(ObservableEmitter emitter) throws Exception { 122 | startThread(emitter); 123 | } 124 | }); 125 | } 126 | }) 127 | .retry() 128 | .subscribeOn(Schedulers.io()) 129 | .observeOn(AndroidSchedulers.mainThread()); 130 | } 131 | }; 132 | } 133 | 134 | /** 135 | * 开始连接,这一步如果失败会一直重连,周期为30秒(30秒的意思不是说30秒重连一次,而是30秒内都在重连,30秒内没连接上会重新订阅重新连接,等于一直在重连) 136 | * 137 | * @param host 138 | * @param port 139 | * @return 140 | */ 141 | private Observable connect(final String host, final int port) { 142 | return Observable.create(new ObservableOnSubscribe() { 143 | @Override 144 | public void subscribe(final ObservableEmitter emitter) throws Exception { 145 | //TODO 根据项目需求加上是否有可用网络做判断 146 | 147 | close(); 148 | 149 | socket = new Socket(host, port); 150 | 151 | emitter.onNext(connect = true); 152 | emitter.onComplete(); 153 | } 154 | }) 155 | .retry(); 156 | } 157 | 158 | /** 159 | * 心跳 160 | * 161 | * @param period 心跳频率 162 | * @param data 心跳数据 163 | * @return 164 | */ 165 | private ObservableTransformer heartBeat(final int period, final String data) { 166 | return new ObservableTransformer() { 167 | @Override 168 | public ObservableSource apply(Observable upstream) { 169 | return upstream.flatMap(new Function>() { 170 | @Override 171 | public ObservableSource apply(Boolean aBoolean) throws Exception { 172 | return interval(period) 173 | .flatMap(new Function>() { 174 | @Override 175 | public ObservableSource apply(Long aLong) throws Exception { 176 | return send(data); 177 | } 178 | }); 179 | } 180 | }); 181 | } 182 | }; 183 | } 184 | 185 | /** 186 | * 心跳频率 187 | * 188 | * @param period 189 | * @return 190 | */ 191 | private Observable interval(int period) { 192 | //从0开始是因为这个心跳服务可以立刻开始传递下去,那么socket基本可以实现2秒内重新连接上(不加心跳的话,基本实现秒连) 193 | return Observable.interval(0, period, TimeUnit.SECONDS); 194 | } 195 | 196 | /** 197 | * 读取线程开启 198 | * 199 | * @param emitter 200 | */ 201 | private void startThread(final ObservableEmitter emitter) { 202 | if (!connect || threadStart) return; 203 | 204 | Log.d("socket", "读取线程开启"); 205 | new ReadThread(new SocketCallBack() { 206 | @Override 207 | public void onReceive(String result) { 208 | //如果服务器断开,这里会返回null,而rxjava2不允许发射一个null值,固会抛出空指针,利用其重新订阅服务 209 | emitter.onNext(result); 210 | } 211 | }).start(); 212 | } 213 | 214 | /** 215 | * 发送数据,超过五秒发送失败(指定在io线程发送数据) 216 | * 217 | * @param data 218 | * @return 219 | */ 220 | public Observable send(String data) { 221 | return Observable.just(data) 222 | .flatMap(new Function>() { 223 | @Override 224 | public ObservableSource apply(String s) throws Exception { 225 | if (connect && null != bufferedWriter) { 226 | bufferedWriter.write(s.concat("\r\n")); 227 | bufferedWriter.flush(); 228 | 229 | return Observable.just(true); 230 | } 231 | return Observable.just(false); 232 | } 233 | }) 234 | .subscribeOn(Schedulers.io()) 235 | .timeout(5, TimeUnit.SECONDS, Observable.just(false)); 236 | } 237 | 238 | /** 239 | * 读取数据 240 | * 241 | * @param 242 | * @return 243 | */ 244 | private ObservableTransformer read() { 245 | return new ObservableTransformer() { 246 | @Override 247 | public ObservableSource apply(Observable upstream) { 248 | return upstream.flatMap(new Function>() { 249 | @Override 250 | public ObservableSource apply(T t) throws Exception { 251 | return Observable.create(new ObservableOnSubscribe() { 252 | @Override 253 | public void subscribe(ObservableEmitter emitter) throws Exception { 254 | startThread(emitter); 255 | } 256 | }); 257 | } 258 | }); 259 | } 260 | }; 261 | } 262 | 263 | /** 264 | * 关闭socket 265 | * 266 | * @throws IOException 267 | */ 268 | private void close() throws IOException { 269 | Log.d("socket", "初始化Socket"); 270 | 271 | connect = false; 272 | threadStart = false; 273 | 274 | if (null != socket) { 275 | socket.close(); 276 | socket = null; 277 | } 278 | 279 | if (null != bufferedReader) { 280 | bufferedReader.close(); 281 | bufferedReader = null; 282 | } 283 | 284 | if (null != bufferedWriter) { 285 | bufferedWriter.close(); 286 | bufferedWriter = null; 287 | } 288 | } 289 | 290 | /** 291 | * 读取数据线程 292 | */ 293 | private class ReadThread extends Thread { 294 | 295 | private SocketCallBack callBack; 296 | 297 | private ReadThread(SocketCallBack callBack) { 298 | this.callBack = callBack; 299 | } 300 | 301 | @Override 302 | public void run() { 303 | try { 304 | threadStart = true; 305 | 306 | bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream())); 307 | bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); 308 | 309 | while (connect) { 310 | callBack.onReceive(bufferedReader.readLine()); 311 | } 312 | } catch (Exception e) { 313 | // e.printStackTrace(); 314 | } 315 | } 316 | } 317 | 318 | /** 319 | * 线程切换 320 | * 321 | * @param 322 | * @return 323 | */ 324 | public static ObservableTransformer io_main() { 325 | return upstream -> upstream.subscribeOn(Schedulers.io()) 326 | .observeOn(AndroidSchedulers.mainThread()); 327 | } 328 | 329 | private interface SocketCallBack { 330 | 331 | void onReceive(String result); 332 | } 333 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 14 | 15 | 19 | 20 | 25 | 26 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 48 | 49 | 55 | 56 | 62 | 63 | 64 | 65 | 66 | 71 | 72 | 78 | 79 | 85 | 86 | 87 | 88 | 89 | 94 | 95 | 101 | 102 | 108 | 109 | 110 | 111 | 112 | 113 |