使用 androidNodeJs 和 Socket.io 创建一个实时聊天应用程序

Posted 小陈乱敲代码

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用 androidNodeJs 和 Socket.io 创建一个实时聊天应用程序相关的知识,希望对你有一定的参考价值。

介绍

WebSockets 是非常漂亮的工具,它允许我们在现代 Web 应用程序中建立实时通信。事实上,这个机制非常强大,它被用来构建不同类型的应用程序,如实时聊天或通知系统等。

在本文中,我们将向您展示如何使用 android nodeJs 和 Socket.io 构建一个实时聊天应用程序

入门

我们的聊天应用分为两部分:

1-服务器端:一个节点js服务器,为服务器实现socket.io

2-客户端:创建android应用程序并为客户端实现socket.io

我们的 NodeJs 服务器

好吧,为了清楚起见,我们的项目架构将由 2 个文件组成:
package.json 将处理我们的 node js 应用程序的所有依赖项, index.js 将是我们的主服务器。

创建这两个文件后,我们打开项目
目录下的命令行并执行此命令

npm install --save  express socket.io  

现在在我们的 index.js 文件中,我们将构建我们的服务器并进行所有配置,使其看起来像这样

const express = require('express'),
http = require('http'),
app = express(),
server = http.createServer(app),
io = require('socket.io').listen(server);
app.get('/', (req, res) => 

res.send('Chat Server is running on port 3000')
);


server.listen(3000,()=>

console.log('Node app is running on port 3000')

);

要确保我们的服务器正在运行,请转到我们项目目录下的命令行并执行此命令

node index.js

注意:使用 node 命令我们可以运行任何使用 node 环境创建的服务器,但问题是我们每次更新 index.js 文件时都必须运行相同的命令,所以为了使事情更简单,我们可以使用 nodemon 命令,它会自动每次我们进行更改时重新启动我们的服务器

所以要安装 nodemon 去你的命令行并运行

npm install -g nodemon

为了确保我们的项目正在运行,我们应该在控制台中看到这个日志

现在是最好的部分!

我们现在将尝试在我们的服务器中实现一些 socket.io 方法来处理我们聊天应用程序的所有事件,包括用户连接状态和消息。

在我们的 index.js 文件中,我们添加了第一个实现,它将检测我们是否有用户连接到我们的服务器

io.on('connection', (socket) => 

console.log('user connected')

socket.on('join', function(userNickname) 

        console.log(userNickname +" : has joined the chat "  )

        socket.broadcast.emit('userjoinedthechat',userNickname +" : has joined the chat ")
    );

);

实际上 socket.io 机制是基于监听和触发事件的,在第一个实现中,我们完成了 (on) 方法,该方法接受两个参数 (‘eventname’,callback) 定义了一个名为 connection 的事件的侦听器,这个事件将是从客户端触发,以便节点 js 可以处理它,之后我们定义了一个方法,该方法将侦听一个名为“加入”的发出事件,并将在控制台中记录加入聊天的用户的名称。

现在,当节点 js 检测到用户时,它会使用方法 emit 向客户端触发一个名为“userjoinedthechat”的事件,注意 socket.broadcast.emit 会将事件发送给连接到服务器的每个用户,除了 sender 。

如果我们想将消息发送给包括发送者在内的所有用户,我们只需要使用 io.emit() 而不是 socket.emit()。

现在为了处理消息,我们添加了这几行,我们可以看到我们在回调函数中添加了额外的参数,即用户昵称和消息内容,实际上这些信息将在触发事件’messagedetection’时从客户端发送

 socket.on('messagedetection', (senderNickname,messageContent) => 

       //log the message in console 

       console.log(senderNickname+" :" +messageContent)
        //create a message object

      let  message = "message":messageContent, "senderNickname":senderNickname

// send the message to the client side  

       socket.emit('message', message )

      );

最后,当用户与客户端断开连接时,该事件将由该实现处理。

 socket.on('disconnect', function() 
    console.log( 'user has left ')
    socket.broadcast.emit( "userdisconnect" ,' user has left')


);

现在我们的服务器已经准备好了, index.js 文件应该是这样的

const express = require('express'),
http = require('http'),
app = express(),
server = http.createServer(app),
io = require('socket.io').listen(server);
app.get('/', (req, res) => 

res.send('Chat Server is running on port 3000')
);
io.on('connection', (socket) => 

console.log('user connected')

socket.on('join', function(userNickname) 

        console.log(userNickname +" : has joined the chat "  );

        socket.broadcast.emit('userjoinedthechat',userNickname +" : has joined the chat ");
    )


socket.on('messagedetection', (senderNickname,messageContent) => 

       //log the message in console 

       console.log(senderNickname+" : " +messageContent)

      //create a message object 

      let  message = "message":messageContent, "senderNickname":senderNickname

       // send the message to all users including the sender  using io.emit() 

      io.emit('message', message )

      )

socket.on('disconnect', function() 

        console.log(userNickname +' has left ')

        socket.broadcast.emit( "userdisconnect" ,' user has left')




    )




)






server.listen(3000,()=>

console.log('Node app is running on port 3000')

)

我们的 Android 应用程序(Socket 客户端)

要开始打开 ​​android studio 并创建一个带有空活动的新项目,然后打开 app build.gradle 文件并添加这些依赖项,然后同步您的项目。

compile 'com.android.support:recyclerview-v7:25.3.1'
compile('com.github.nkzawa:socket.io-client:0.5.0') 
    exclude group: 'org.json', module: 'json'

现在关于这些行:

第一个是回收器视图,我们将使用它来显示我们的消息列表,第二个是库,它将为我们提供客户端的 socket.io 实现,以便我们可以触发或监听事件。

不要忘记在 manifest.xml 中启用 INTERNET 权限

<uses-permission android:name="android.permission.INTERNET" ></uses-permission>

在activity_main.xml中,我们将添加一个EditText供用户输入他的昵称和一个允许他进入聊天框的按钮

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.example.aymen.androidchat.MainActivity">

   <EditText 
      android:id="@+id/nickname"android:layout_centerInParent="true"android:textSize="30dp"android:hint="Enter your nickname !"android:layout_width="match_parent"android:layout_height="wrap_content" /><Buttonandroid:layout_below="@+id/nickname"android:id="@+id/enterchat"android:text="Go to chat "android:layout_width="match_parent"android:layout_height="wrap_content" />

 </RelativeLayout>

使预览看起来像这样

现在你的 MainActivity.java 应该是这样的

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

public class MainActivity extends AppCompatActivity 


    private Button btn;
    private EditText nickname;
    public static final String NICKNAME = "usernickname";
    @Overrideprotected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //call UI components  by id
        btn = (Button)findViewById(R.id.enterchat) ;
        nickname = (EditText) findViewById(R.id.nickname);

        btn.setOnClickListener(new View.OnClickListener() 
            @Override
            public void onClick(View v) 
                //if the nickname is not empty go to chatbox activity and add the nickname to the intent extra


    if(!nickname.getText().toString().isEmpty())

              Intent i  = new Intent(MainActivity.this,ChatBoxActivity.class);

                     //retreive nickname from EditText and add it to intent extra
                     i.putExtra(NICKNAME,nickname.getText().toString());

                     startActivity(i);
                 
            
        );

    

现在创建第二个名为 ChatBoxActivity 的空活动,并在 activity_chat_box.xml 添加这些行

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.example.aymen.androidchat.ChatBoxActivity">
<LinearLayout
android:weightSum="3"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerViewandroid:layout_weight="3"android:id="@+id/messagelist"android:layout_width="match_parent"android:layout_height="wrap_content"android:clipToPadding="false"android:scrollbars="vertical"/><Viewandroid:layout_marginTop="5mm"android:id="@+id/separator"android:layout_width="match_parent"android:layout_height="1dp"android:background="@android:color/darker_gray"/>

<LinearLayoutandroid:weightSum="3"android:orientation="horizontal"android:layout_width="match_parent"android:layout_height="wrap_content">

            <EditText

                android:id="@+id/message"android:layout_weight="3"android:layout_width="wrap_content"android:hint="your message"

                android:layout_height="match_parent" />

            <Button

                android:id="@+id/send"android:layout_width="wrap_content"android:layout_height="wrap_content"android:background="#00000000"android:text="send"
                /></LinearLayout>

    </LinearLayout>
</RelativeLayout>

你的预览应该是这样的

现在在实现套接字客户端之前,我们应该创建一个适配器来处理和显示我们的消息,为此我们需要创建一个名为 item.xml 的文件和一个名为 message 的 java 类,它有两个简单的字符串属性 (nickname,message)。

在我们的项目目录中,与活动一起创建一个名为 Message.java 的文件:

public class Message 

    private String nickname; 
    private String message ;

    public  Message()

    
    public Message(String nickname, String message) 
        this.nickname = nickname;
        this.message = message;
    

    public String getNickname() 
        return nickname;
    

    public void setNickname(String nickname) 
        this.nickname = nickname;
    

    public String getMessage() 
        return message;
    

    public void setMessage(String message) 
        this.message = message;
    

现在在布局目录下创建一个名为 item.xml 的文件并添加这些行

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="horizontal" 
android:layout_width="match_parent"android:layout_height="match_parent">
<TextViewandroid:id="@id/nickname"android:textSize="15dp"android:textStyle="bold"android:text="Nickname : 
"android:layout_width="wrap_content"android:layout_height="wrap_content" />
<TextViewandroid:id="@id/message"android:textSize="15dp"android:text=" message 
"android:layout_width="wrap_content"android:layout_height="wrap_content" />
</LinearLayout>

创建一个名为 ChatBoxAdapter.java 的文件并将这些行

package com.example.aymen.androidchat;

import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.List;


public class ChatBoxAdapter  extends RecyclerView.Adapter<ChatBoxAdapter.MyViewHolder> 
    private List<Message> MessageList;

    public  class MyViewHolder extends RecyclerView.ViewHolder 
        public TextView nickname;
        public TextView message;


        public MyViewHolder(View view) 
            super(view);

            nickname = (TextView) view.findViewById(R.id.nickname);
            message = (TextView) view.findViewById(R.id.message);





        
    
// in this adaper constructor we add the list of messages as a parameter so that 
// we will passe  it when making an instance of the adapter object in our activity 



public ChatBoxAdapter(List<Message>MessagesList) 

        this.MessageList = MessagesList;


    

    @Overridepublic int getItemCount() 
        return MessageList.size();
    
    @Overridepublic ChatBoxAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) 
        View itemView = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item, parent, false);



        return new ChatBoxAdapter.MyViewHolder(itemView);
    

    @Overridepublic void onBindViewHolder(final ChatBoxAdapter.MyViewHolder holder, final int position) 

 //binding the data from our ArrayList of object to the item.xml using the viewholder 



        Message m = MessageList.get(position);
        holder.nickname.setText(m.getNickname());

        holder.message.setText(m.getMessage() );




    




现在有了所有设置,我们可以在 ChatBoxActivity.java 中实现套接字客户端,这就是我们要继续的方式:

1.从intent extra中获取用户昵称

2.调用并实现所有与回收器视图相关的方法,包括适配器实例化

3.声明和定义socket客户端与服务器建立连接的主机

4.处理从服务器触发的所有事件

5.当用户连接、断开或发送消息时发出事件

但在此之前让我们检查一切是否正常,所以在我们的 ChatBoxActivity 中,我们将声明套接字对象并在方法 onCreate 中添加套接字连接,以便在调用活动时套接字客户端将直接触发事件连接

public class ChatBoxActivity extends AppCompatActivity 

    //declare socket object

private Socket socket;
private String Nickname ;

@Overrideprotected 
void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_chat_box);

// get the nickame of the user



  Nickname= (String)getIntent().getExtras().getString(MainActivity.NICKNAME);

//connect you socket client to the server

try 


//if you are using a phone device you should connect to same local network as your laptop and disable your pubic firewall as well 

 socket = IO.socket("http://yourlocalIPaddress:3000");

 //create connection 

socket.connect()

// emit the event join along side with the nickname

socket.emit('join',Nickname); 


         catch (URISyntaxException e) 
            e.printStackTrace();

        

    

现在运行您的模拟器并在第一个活动中输入昵称,然后单击 go to chat 您将在服务器控制台中看到一个日志,表明用户已成功与服务器建立连接,我们可以看到触发事件的侦听器加入我们的服务器工作正常,以便记录连接用户的名称

现在一切正常,我们不应该忘记,当我们的服务器处理一个事件时,它也会广播其他 costum 事件,因此这些触发的事件应该在客户端处理,因为我们将为事件“userjoinedthechat”创建第一个侦听器这是服务器处理事件“加入”时触发的自定义事件。

在我们的 ChatBoxActivity 中,我们将添加这些行

socket.on("userjoinedthechat", new Emitter.Listener() 
    @Overridepublic void call(final Object... args) 
        runOnUiThread(new Runnable() 
            @Overridepublic void run() 
                String data = (String) args[0];
                // get the extra data from the fired event and display a toast 
                Toast.makeText(ChatBoxActivity.this,data,Toast.LENGTH_SHORT).show();

            
        );
    

现在我们同时运行 2 个模拟器,我们从双方输入两个不同的昵称,我们可以看到两个模拟器之一表明用户已成功加入聊天

现在是我们应用程序中最好的部分,即聊天消息:

要显示消息,我们必须以这种方式进行

1.将onclickListener添加到按钮发送并从EditText中获取消息内容,然后使用emit()方法以及发送者的昵称和消息内容发出事件“messagedetection”

2.事件将由服务器处理并广播给所有用户

3.在android中添加一个socket监听器来监听服务器触发的事件“message”

4.从额外数据中提取昵称和消息,并创建对象Message的新实例

5.将实例添加到消息的ArrayList中,并通知适配器更新recycler视图

但在此之前,让我们设置我们的回收器视图、适配器、消息文本字段和按钮发送。

在 ChatBoxActivity 中添加以下声明

public RecyclerView myRecylerView ;
public List<Message> MessageList ;
public ChatBoxAdapter chatBoxAdapter;
public  EditText messagetxt ;
public  Button send ;

在 onCreate 方法中添加这些行

messagetxt = (EditText) findViewById(R.id.message) ;
send = (Button)findViewById(R.id.send);
MessageList = new ArrayList<>();
myRecylerView = (RecyclerView) findViewById(R.id.messagelist);
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
myRecylerView.setLayoutManager(mLayoutManager);
myRecylerView.setItemAnimator(new DefaultItemAnimator());

现在在您的 ChatBoxActivity 中,按钮操作应如下所示

send.setOnClickListener(new View.OnClickListener() 
    @Overridepublic void onClick(View v) 
        //retrieve the nickname and the message content and fire the event messagedetection


  if(!messagetxt.getText().toString().isEmpty())

            socket.emit("messagedetection",Nickname,messagetxt.getText().toString());

            messagetxt.setText(" ");         
    




    
);

听者应该是这样的

socket.on("message", new Emitter.Listener() 
    @Overridepublic void call(final Object... args) 
        runOnUiThread(new Runnable() 
            @Overridepublic void run() 
                JSONObject data = (JSONObject) args[0];
                try 
                    //extract data from fired event

              String nickname = data.getString("senderNickname");
              String message = data.getString("message");

           // make instance of message

          Message m = new Message(nickname,message);


          //add the message to the messageList

          MessageList.add(m);

          // add the new updated list to the adapter 
          chatBoxAdapter = new ChatBoxAdapter(MessageList);

           // notify the adapter to update the recycler view

          chatBoxAdapter.notifyDataSetChanged();

           //set the adapter for the recycler view 

          myRecylerView.setAdapter(chatBoxAdapter);


                 catch (JSONException e) 
                    e.printStackTrace();
                


            
        );
    
);

正如我们在下面的屏幕截图中所见,一切正常

在结束本教程之前,我们必须创建最后一个功能,即检测用户是否与聊天框断开连接。

在我们的 ChatBoxActivity 中重写 onDestroy() 方法并添加这些行

@Override
protected void onDestroy() 
    super.onDestroy();
    socket.disconnect(); 

并且对于听众

socket.on("userdisconnect", new Emitter.Listener() 
    @Overridepublic void call(final Object... args) 
        runOnUiThread(new Runnable() 
            @Overridepublic void run() 
                String data = (String) args[0];

                Toast.makeText(ChatBoxActivity.this,data,Toast.LENGTH_SHORT).show();

            
        );
    
);

最后我们的 ChatBoxActivity 看起来像这样

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.github.nkzawa.emitter.Emitter;
import com.github.nkzawa.socketio.client.IO;
import com.github.nkzawa.socketio.client.Socket;

import org.json.JSONException;
import org.json.JSONObject;

import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;

public class ChatBoxActivity extends AppCompatActivity 
    public RecyclerView myRecylerView ;
    public List<Message> MessageList ;
    public ChatBoxAdapter chatBoxAdapter;
    public  EditText messagetxt ;
    public  Button send ;
    //declare socket objectprivate Socket socket;

    public String Nickname ;
    @Overrideprotected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_chat_box);

        messagetxt = (EditText) findViewById(R.id.message) ;
        send = (Button)findViewById(R.id.send);
        // get the nickame of the user
        Nickname= (String)getIntent().getExtras().getString(MainActivity.NICKNAME);
        //connect you socket client to the servertry 
            socket = IO.socket("http://yourlocalIPaddress:3000");
            socket.connect();
            socket.emit("join", Nickname);
         catch (URISyntaxException e) 
            e.printStackTrace();

        
       //setting up recyler
        MessageList = new ArrayList<>();
        myRecylerView = (RecyclerView) findViewById(R.id.messagelist);
        RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
        myRecylerView.setLayoutManager(mLayoutManager);
        myRecylerView.setItemAnimator(new DefaultItemAnimator());



        // message send action
        send.setOnClickListener(new View.OnClickListener() 
            @Overridepublic void onClick(View v) 
                //retrieve the nickname and the message content and fire the event messagedetectionif(!messagetxt.getText().toString().isEmpty())
                    socket.emit("messagedetection",Nickname,messagetxt.getText().toString());

                    messagetxt.setText(" ");
                


            
        );

        //implementing socket listeners
        socket.on("userjoinedthechat", new Emitter.Listener() 
            @Overridepublic void call(final Object... args) 
                runOnUiThread(new Runnable() 
                    @Overridepublic void run() 
                        String data = (String) args[0];

                        Toast.makeText(ChatBoxActivity.this,data,Toast.LENGTH_SHORT).show();

                    
                );
            
        );
        socket.on("userdisconnect", new Emitter.Listener() 
            @Overridepublic void call(final Object... args) 
                runOnUiThread(new Runnable() 
                    @Overridepublic void run() 
                        String data = (String) args[0];

                        Toast.makeText(ChatBoxActivity.this,data,Toast.LENGTH_SHORT).show();

                    
                );
            
        );
        socket.on("message", new Emitter.Listener() 
            @Overridepublic void call(final Object... args) 
                runOnUiThread(new Runnable() 
                    @Overridepublic void run() 
                        JSONObject data = (JSONObject) args[0];
                        try 
                            //extract data from fired event

                            String nickname = data.getString("senderNickname");
                            String message = data.getString("message");

                            // make instance of message

                            Message m = new Message(nickname,message);


                            //add the message to the messageList

                            MessageList.add(m);

                            // add the new updated list to the dapter
                            chatBoxAdapter = new ChatBoxAdapter(MessageList);

                            // notify the adapter to update the recycler view

                            chatBoxAdapter.notifyDataSetChanged();

                            //set the adapter for the recycler view

                            myRecylerView.setAdapter(chatBoxAdapter);

                         catch (JSONException e) 
                            e.printStackTrace();
                        


                    
                );
            
        );
    

    @Override

protected void onDestroy() 
        super.onDestroy();

        socket.disconnect(); 
    

结论

在这个例子中,我们很好地了解了 socket.io 以及 node js 和 android 的用法,我们也尝试解释一些基础知识并了解 socket.io 的机制以及如何在一个节点之间建立双向通信客户端和服务器,请注意 socket.io 中还有其他工具,例如房间和命名空间,它们可能对制作精美的 Web 和移动应用程序非常有帮助。

以上是关于使用 androidNodeJs 和 Socket.io 创建一个实时聊天应用程序的主要内容,如果未能解决你的问题,请参考以下文章

使用java实现的socket代理(支持socket4和socket5)

在nodejs中使用socket.io和net socket

使用 jQuery 和 socket.io

使用 Socket.IO 进行授权和握手

Socket的简单使用

SOCKET简单爬虫实现代码和使用方法