數據通訊的方式有不少種,其中websocket就是一種用於IM的經常使用數據通訊方式,如在線客服、QQ、微信等,或多或少都使用到了這一技術。因此瞭解以及掌握websocket是頗有必要的
廢話很少說,先上圖:css
WebSocket 是 HTML5 開始提供的一種在單個 TCP 鏈接上進行全雙工通信的協議。html
WebSocket 使得客戶端和服務器之間的數據交換變得更加簡單,容許服務端主動向客戶端推送數據。在 WebSocket API 中,瀏覽器和服務器只須要完成一次握手,二者之間就直接能夠建立持久性的鏈接,並進行雙向數據傳輸。node
在 WebSocket API 中,瀏覽器和服務器只須要作一個握手的動做,而後,瀏覽器和服務器之間就造成了一條快速通道。二者之間就直接能夠數據互相傳送。react
在實現過程衝,因爲原生的WebSocket存在兼容問題,因此通常在真正項目開發中,每每會用到一些三方經常使用的庫,如socket.io
、socket.io-client
等。web
簡單地經過nodejs+koa+socket.io去進行模擬實現,其代碼以下:
server.jsjson
const http=require('http'); const Koa=require('koa'); const io=require('socket.io'); const uuid=require('uuid/v4'); // let server=new Koa(); // let httpServer=http.createServer(server.callback()); httpServer.listen(8080); // let wsServer=io.listen(httpServer); wsServer.on('connection', sock=>{ console.log('connected'); const ID=uuid(); sock.emit('ID', ID); sock.on('msg', (user, msg)=>{ wsServer.emit('broadcast', ID, user, msg); }); });
客戶端主要有三部分組成:登陸、發送消息、展現消息,其中登陸對於的組件是Login.js
,消息相關的組件是Msg.js
。看下項目總體目錄機構:redux
. +-- public +-- node_modules +-- _src | +-- _assets | +-- _components | +-- Login.js | +-- Msg.js | +-- _store | +-- index.js | +-- user.js | +-- msg.js | +-- actions.js | +-- App.js | +-- index.js | +-- socket.js +-- package.json
/src/socket.js:單例實現全應用只有一個其實例對象,代碼以下:bootstrap
import io from 'socket.io-client'; const HOST="ws://localhost:8080/"; export default io(HOST);
src/store/index.js:統一管理不一樣的reducer,代碼以下:瀏覽器
import {createStore,combineReducers} from 'redux' import user from './user' import msg from './msg' export default createStore(combineReducers({ user, msg }))
src/store/user.js:負責管理用戶狀態(設置用戶ID、暱稱等)的reducer,代碼以下:服務器
import {SET_USER_ID,SET_USER_NAME} from '../actions' export default function(state = {ID:null,name:null},action){ switch(action.type){ case SET_USER_ID: return { ...state, ID:action.value } case SET_USER_NAME: return{ ...state, name:action.value } default: return state } }
src/store/msg.js:負責處理初始化消息、添加消息等狀態的reducer,代碼以下:
import {ADD_MSG} from '../actions' export default function(state = [],action){ switch(action.type){ case ADD_MSG: return [ ...state, action.msg ] default: return state } } /src/actions.js:統一管理整個應用的全部action,代碼以下: //用戶相關 export const SET_USER_ID = "set_user_id"; export const SET_USER_NAME = "set_user_name"; //消息相關 export const ADD_MSG = "add_msg"; //------------------------------- export function setUserId(ID){ return{ type:SET_USER_ID, value:ID }; } export function setUserName(name){ return{ type:SET_USER_NAME, value:name }; } export function addMsg(msg){ return{ type:ADD_MSG, msg } }
src/index.js:整個程序的入口,代碼以下:
import React from 'react'; import ReactDOM from 'react-dom'; import {BrowserRouter as Router,Route} from 'react-router-dom' import './index.css'; import App from './App'; import * as serviceWorker from './serviceWorker'; import { async } from 'q'; import {Provider} from 'react-redux' import store from './store' import 'bootstrap/dist/css/bootstrap.css' ReactDOM.render( ( <Provider store={store}> <Router> <Route component={App}/> </Router> </Provider> ), document.getElementById('root') ); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://bit.ly/CRA-PWA serviceWorker.unregister();
src/App.js:應用的主組件,負責路由的配置,數據的初始化,代碼以下:
import React, { Component } from 'react'; import {connect} from 'react-redux' import {Route,Redirect} from 'react-router-dom' import Login from './components/Login' import Msg from './components/Msg' import {setUserId,addMsg} from './actions' import socket from './socket' class App extends Component { constructor(){ super() } componentDidMount(){ //獲得初始化用戶ID socket.on('ID',(ID) => { this.props.setUserId(ID); }) //接收消息 socket.on('broadcast',(from, fromUser, msg) => { this.props.addMsg({from, fromUser, msg}) }) } render() { return ( <div> { this.props.user.name ? '':( <Redirect to="/"/> ) } <Route path="/" exact component={Login}/> <Route path="/msg" component={Msg}/> </div> ); } } export default connect((state,props) => Object.assign({},props,state),{ setUserId, addMsg })(App);
src/components/Login.js:登陸組件,代碼以下:
import React, { Component } from 'react'; import {connect} from 'react-redux' import {setUserName} from '../actions' class Login extends Component { constructor(){ super(); this.rnd = Math.floor(Math.random()*1000000); } login = () => { let username = this.refs.username.value; this.props.setUserName(username); this.refs.username.value = ""; this.props.history.push('/msg'); } render() { return ( <div className="panel panel-primary"> <div className="panel-heading"> <h2 className="panel-title"> 登陸 </h2> </div> <div className="panel-body"> <div className="from-group"> <label htmlFor={'username'+this.rnd}></label> <input className="from-control" type="text" id={'username'+this.rnd} ref="username" placeholder="請輸入用戶名"/> </div> <div className="from-group" style={{'marginTop':'10px'}}> <button type="button" className="btn btn-default" onClick={this.login}>登陸</button> </div> </div> </div> ); } } export default connect((state,props) => Object.assign({},props,state),{ setUserName })(Login);
src/components/Msg.js:發送消息、展現消息的組件,代碼以下:
import React, { Component } from 'react'; import {connect} from 'react-redux' import msg from '../store/msg'; import socket from '../socket' class Msg extends Component { send = () => { console.log('this.props.user.username:',this.props.user.name) socket.emit('msg',this.props.user.name,this.refs.msg.value); this.refs.msg.value = "" } render() { return ( <div> <div> <div className="from-group"> <textarea className="from-control" ref="msg"></textarea> </div> <div className="from-group"> <button className="btn btn-default" type="button" onClick={this.send}>發送</button> </div> </div> <ul> { this.props.msg.map(({from,fromUser,msg},index) => ( <li key={index}> <h3 className="list-group-item-heading" style={{color:from==this.props.user.ID ? 'red':''}}>{fromUser}</h3> <p className="list-group-item-text">{msg}</p> </li> )) } </ul> </div> ); } } export default connect((state,props) => Object.assign({},props,state),{ })(Msg);