使用socket实现即时通讯聊天室

760 阅读3分钟

websocket早在几年前就已经很流行了,主要就是用于即时通讯这一方面应用,可以是聊天,也可使是直播流传输等等。

今天,就来说说如何使用 create-react-app + socket.io 实现简单的即时聊天。

Demo地址

准备工作

想要实现即时通讯,还是需要有服务器的支持,这里我使用的是一个简单配置的服务器

服务器配置

还是去年腾讯搞活动买的,还不错,有机会你们也可以去看看。阿里云腾讯云都会时不时的出一些活动,买一个服务器自己玩玩还是可以的。如果有活动,我可以在后面不断更新。

有了服务器以后就是敲代码了。

大爷,咋又是你啊

服务端实现

服务端我这里使用的是Nodejs作为后端语言,使用express+socket.io作为技术支持,具体的代码如下

const express = require("express")
const app = express()
const http = require("http").createServer(app)
var io = require('socket.io')(http);

app.use(express.static(__dirname + '/dist'))
app.get("/", (req, res) => {
  res.header("Access-Control-Allow-Credentials", "true"); 
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With');
  res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
  // res.send(`<h1>Hello World!!!</h1>`)
  res.send(__dirname + '/dist/index.html')
})

const userList = {};
let userCount = 0;
const messageList = []

io.on("connection", socket => {

  socket.on("login", data => {
    console.log(`${data.username} 登录`);
    socket.uid = data.uid
    userList[data.uid] = data.username
    userCount++

    io.emit('users', {
      userCount,
      userList
    })

    // 发给自己
    socket.emit("receive_message", messageList)
  })

  socket.on("disconnect", function() {
    if( !socket.uid ) return
    const user = {
      uid: socket.uid,
      username: userList[socket.uid]
    }
    delete userList[socket.uid]
    userCount--
    // 发送给所有用户 使用 io.emit  
    // 发送给自己 使用 socket.emit
    io.emit('users', {
      userCount,
      userList
    })
    console.log(`${user.username} 登出了`);
  })

    
  socket.on("message", data => {
    if ( !data ) return
    // console.log(`发送信息 -- ${data}`);
    messageList.push({
      username: userList[socket.uid],
      message: data
    })
    if( messageList.length > 30 ){
      messageList.shift()
    }
    // 发送给除了自己以外的其他所有用户
    // socket.broadcast.emit("receive_message", messageList)
    io.emit("receive_message", messageList)
  })
})

http.listen(2000, _ => {
  console.log('This server is running: http://localhost:2000');
})

静态的文件是使用 create-react-app 开发的页面,build之后放到了服务器上面dist目录下面。

要使用socket.io,首先需要创建socket服务

var io = require('socket.io')(http);

接下来就是连接服务端与客户端了。服务端如果想要连接到客户端的用户,那么就需要有方法一直监听到客户端用户访问网站的方法。socket.io中就为我们提供了一个 connection 方法。

io.on("connection", socket => {
  // do something
})

connection 之后所有的操作都是写在这个 connection 的监听之中。

上面的 connection 中的代码需要注意的有几点,知道了这几点,那么socket.io对你就不是难事

  1. io.on('监听事件名字', () => {})方法是监听所有的用户。
  2. connection方法中的 socket 值得是当前用户,所以socket.on('监听事件名字', () => {})是监听当前用户的操作。
  3. io.emit('监听事件名字', 参数)是发送消息给客户端,此时客户端会有一个监听的事件,监听事件名字服务端需要与客户端相同。
  4. socket.broadcast.emit("监听事件名字", 参数) 这个方法可以发送消息给除了自已以外的其他的所有的用户。

客户端与服务端一样。

记住以上四点秘诀,玩转socket不是梦。

客户端实现

客户端使用create-react-app写的页面实现,下面贴出逻辑,就不放样式了

import React, { Component } from 'react';
import './App.css';
import io from 'socket.io-client'
import { Input, Button, Avatar, message } from 'antd'

const socket = io('http://118.24.6.33:2000');

class App extends Component {
  constructor(props){
    super(props)

    this.state = {
      showLogin: true,
      users: {
        userCount: 0,
        userList: {}
      },
      messageList: []
    }
  }
  
  login = () => {
    const username = this.refs.input.input.value.trim()
    const { userList } = this.state.users
    
    if( username.length ){
      for (const k in userList) {
        if( userList[k] === username ){
          message.info("聊天室已经有这个用户了,请重新起一个名字")
          return
        }
      }
      
      this.uid = this.get_uid()
      socket.emit("login", {
        username,
        uid: this.uid
      })

      this.setState({
        showLogin: false
      })
    } else {
      message.info("请输入一个用户名!!")
    }
  }

  get_uid = _ => {
    return `${new Date().getTime()}${Math.floor(Math.random() * 89999)}`
  }

  send = _ => {
    this.message = this.refs.message.input.value
    if (this.message.trim().length === 0) {
      message.info("你还啥子都还没有输入就行发送了嘛")
      return
    }
    const id = `${new Date().getTime()}${Math.floor(Math.random() * 9999)}`
    const data = {
      message: this.message.trim(),
      uid: this.uid,
      id
    }
    socket.emit('message', data)

    // ant design 中清空输入的内容
    this.refs.message.state.value = ''
    setTimeout(_ => this.refs.messages.scrollBy(0, 999999), 100)
  }

  componentDidMount(){
    socket.on("users", data => {
      this.setState({users: data})
    })

    socket.on("receive_message", data => {
      this.setState({messageList: data})
    })
  }
  
  render(){
    const { showLogin, users, messageList } = this.state
    const { userCount, userList } = users
    
    if (showLogin) {
      return (
        <div className="App">
          <Input placeholder="输入一个名字撒" allowClear ref='input' onPressEnter={this.login}/>
          <Button onClick={this.login} className="login">登录</Button>
        </div>
      );
    } else {
      return (
        <div className="room">
          <div className='inner'>
            <header>欢迎来到踏浪聊天室,当前聊天室共{userCount}人</header>
            <div className="content">
              <ul className="user-list">
                {
                  Object.entries(userList).map(v => {
                    return <li
                      className="user-list-item"
                      key={v[0]}
                    >
                      <Avatar style={{ color: '#f56a00', backgroundColor: '#fde3cf' }}>
                        {v[1].substring(0, 2)}
                      </Avatar>
                      {v[1]}
                    </li>
                  })
                }
              </ul>
              
              <ul className="message-list" ref="messages">
                {
                  messageList.map(v => <li
                    key={v.message.id}
                    className={v.message.uid === this.uid ? "message-list-item me" : "message-list-item"}
                  >
                    {v.message.uid === this.uid && <span className="message-content">{v.message.message}</span>}
                    <Avatar style={{ color: '#f56a00', backgroundColor: '#fde3cf' }}>
                      {v.username && v.username.substring(0, 2)}
                    </Avatar>
                    {v.message.uid !== this.uid && <span className="message-content">{v.message.message}</span>}
                  </li>)
                }
              </ul>
            </div>
            <footer>
              <Input
                placeholder="请输入消息"
                ref='message'
                onPressEnter={this.send}
              />
              <Button className="send" onClick={this.send}>发送</Button>
            </footer>
          </div>
        </div>
      )
    }
  }
}

export default App

客户端可是使用的socket.io。不过使用的是专门为客户端提供的socket.io-client。客户端首先需要连接到服务器,通过 const socket = io('http://118.24.6.33:2000'); 就可以创建一个与服务端链接的 socket 请求。

接下来就是在 componentDidMount 中编写监听事件,同时 socket.on() 实现监听。

在事件中使用 socket.emit() 实现向后端发送消息。

整的逻辑的实现就是如此,摸清逻辑,后面的就不难了。

上面只是使用可socket.io的一些简单的API,关于更多的方法可以前往socket.io官网

最后,可以前往github查看源码