zoukankan      html  css  js  c++  java
  • 安卓socket聊天

    安卓基于Socket通信(服务器配合)

    1.话不多说进入正题,先创建服务端,在Android Studio中创建Java代码,如下图所示:

    选择Java Library 需要改名字的自己随意

    2.创建Client Manager客户端管理类来管理客户端的消息,因为省时间就直接从我上篇博客的代码基础上进行的修改~代码如下所示:(自己编写代码块提交后总有乱码...所以只能把自己的代码复制粘贴进来啦~格式有点奇怪,但是没有乱码~)

    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * Created by sp01 on 2017/4/28.
     */
    // 客户端的管理类
    public class ClientManager {
    
        private static Map<String,Socket> clientList = new HashMap<>();
        private static ServerThread serverThread = null;
    
        private static class ServerThread implements Runnable {
    
            private int port = 10010;
            private boolean isExit = false;
            private ServerSocket server;
    
            public ServerThread() {
                try {
                    server = new ServerSocket(port);
                    System.out.println("启动服务成功" + "port:" + port);
                } catch (IOException e) {
                    System.out.println("启动server失败,错误原因:" + e.getMessage());
                }
            }
    
            @Override
            public void run() {
                try {
                    while (!isExit) {
                        // 进入等待环节
                        System.out.println("等待手机的连接... ... ");
                        final Socket socket = server.accept();
                        // 获取手机连接的地址及端口号
                        final String address = socket.getRemoteSocketAddress().toString();
                        System.out.println("连接成功,连接的手机为:" + address);
    
                        new Thread(new Runnable() {
                            @Override
                            public void run() {
                                try {
                                    // 单线程索锁
                                    synchronized (this){
                                        // 放进到Map中保存
                                        clientList.put(address,socket);
                                    }
                                    // 定义输入流
                                    InputStream inputStream = socket.getInputStream();
                                    byte[] buffer = new byte[1024];
                                    int len;
                                    while ((len = inputStream.read(buffer)) != -1){
                                        String text = new String(buffer,0,len);
                                        System.out.println("收到的数据为:" + text);
                                        // 在这里群发消息
                                        sendMsgAll(text);
                                    }
    
                                }catch (Exception e){
                                    System.out.println("错误信息为:" + e.getMessage());
                                }finally {
                                    synchronized (this){
                                        System.out.println("关闭链接:" + address);
                                        clientList.remove(address);
                                    }
                                }
                            }
                        }).start();
    
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
            public void Stop(){
                isExit = true;
                if (server != null){
                    try {
                        server.close();
                        System.out.println("已关闭server");
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        public static ServerThread startServer(){
            System.out.println("开启服务");
            if (serverThread != null){
                showDown();
            }
            serverThread = new ServerThread();
            new Thread(serverThread).start();
            System.out.println("开启服务成功");
            return serverThread;
        }
    
        // 关闭所有server socket 和 清空Map
        public static void showDown(){
            for (Socket socket : clientList.values()) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            serverThread.Stop();
            clientList.clear();
        }
    
        // 群发的方法
        public static boolean sendMsgAll(String msg){
            try {
                for (Socket socket : clientList.values()) {
                    OutputStream outputStream = socket.getOutputStream();
                    outputStream.write(msg.getBytes("utf-8"));
                }
                return true;
            }catch (Exception e){
                e.printStackTrace();
            }
            return false;
        }
    
    }

    代码看起来比较简单,用了尽可能方便理解的书写,也写好了一些注释,应该不难理解所以就不具体解释了,对Server Socket有不理解的地方,请参考我的上篇博客~希望能有所帮助,但需要解释的地方可能只有一点吧,群发的方法对收到的消息全部进行广播式的发送,那么不就发送的人也会收到消息了嘛?(可能有人感觉会有数据显示重复的情况)我想说的是,真正历史记录都会在服务端进行数据保存和处理这样想就行了,我在Android端做了一个RecyclerView的加载不同行布局实现模拟聊天界面,发送和接收的历史消息都会显示在列表上,本人发送的内容在左侧,其他人发送的消息被显示在右侧。

    3.在MaClass.java(主入口类)中开启服务:

    public class MyClass {
    
        public static void main(String[]args){
            // 开启服务器
            ClientManager.startServer();
        }
    
    }
     

    4.到这里为止,服务端的代码就完成了很简单,有人运行代码时,会出现控制台中文乱码情况,解决办法我转发的博客中有介绍,但是考虑到有人很懒,不想在麻烦去找,我就直接在下面介绍了,很简单只需要一句话:

    tasks.withType(JavaCompile) {
        options.encoding = "UTF-8"
    }
     

    复制代码块,放进蓝色的gradle位置中(Java lib包内)dependencies{}下方位置,在Rebuild一下就好了。

    5.新建并编写Android客户端工程,大致内容就是一个EditText输入框,点击按钮发送数据,上方为一个加载不同行布局的RecyclerView,实现历史记录阅览,下面是activity_main.xml的内容:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context="sq.test_socketchat.MainActivity">
    
        <android.support.v7.widget.RecyclerView
            android:id="@+id/rv"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="9"/>
    
        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1">
    
            <EditText
                android:id="@+id/et"
                android:layout_weight="8"
                android:layout_width="0dp"
                android:hint="输入内容"
                android:layout_height="match_parent" />
    
            <Button
                android:id="@+id/btn"
                android:text="发送"
                android:layout_margin="3dp"
                android:layout_weight="2"
                android:layout_width="0dp"
                android:layout_height="match_parent" />
    
        </LinearLayout>
    
    </LinearLayout>

    显示效果如上图所示。

    6.接下来是准备工作,首先写一个MyBean,用来存储名字,消息内容,消息时间,以及加载哪种布局:

    /**
     * Created by sp01 on 2017/4/28.
     */
    public class MyBean {
        private String data;
        private String time,name;
        private int number;
    
        public MyBean() {
        }
    
        public MyBean(String data, int number,String time,String name) {
            this.data = data;
            this.number = number;
            this.name = name;
            this.time = time;
        }
    
        public String getTime() {
            return time;
        }
    
        public void setTime(String time) {
            this.time = time;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getData() {
            return data;
        }
    
        public void setData(String data) {
            this.data = data;
        }
    
        public int getNumber() {
            return number;
        }
    
        public void setNumber(int number) {
            this.number = number;
        }
    }

    7.同样是准备工作,两个不同布局的item的书写,第一种内容显示在左侧第二种则在右侧,直接复制我的就好:

    第一个item:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:background="#c8fffa"
        android:layout_margin="5dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    
        <TextView
            android:id="@+id/tv"
            android:layout_gravity="left"
            android:textSize="20sp"
            android:text="lalala"
            android:layout_margin="5dp"
            android:textColor="#000000"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    
        <LinearLayout
            android:layout_gravity="left"
            android:orientation="horizontal"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">
    
            <TextView
                android:id="@+id/tv_name"
                android:text="name_xx"
                android:layout_margin="5dp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />
    
            <TextView
                android:id="@+id/tv_time"
                android:layout_margin="5dp"
                android:text="1993-09-28"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />
    
        </LinearLayout>
        
    </LinearLayout>
     
    
     
    
    第二个item:
    
     
    
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:background="#fcfdd9"
        android:layout_margin="5dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    
        <TextView
            android:id="@+id/tv2"
            android:layout_gravity="right"
            android:textSize="20sp"
            android:text="lalala"
            android:layout_margin="5dp"
            android:textColor="#000000"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    
        <LinearLayout
            android:layout_gravity="right"
            android:orientation="horizontal"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">
    
            <TextView
                android:id="@+id/tv_name2"
                android:text="name_xx"
                android:layout_margin="5dp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />
    
            <TextView
                android:id="@+id/tv_time2"
                android:layout_margin="5dp"
                android:text="1993-09-28"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />
    
        </LinearLayout>
    
    </LinearLayout>
     

    8.接下来是书写MyAdapter内的代码(RecyclerView加载不同行布局很简单就不过多强调了):

    import android.content.Context;
    import android.support.v7.widget.RecyclerView;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.TextView;
    import java.util.ArrayList;
    
    /**
     * Created by sp01 on 2017/4/28.
     */
    public class MyAdapter extends RecyclerView.Adapter {
    
        private Context context;
        private ArrayList<MyBean> data;
        private static final int TYPEONE = 1;
        private static final int TYPETWO = 2;
    
        public MyAdapter(Context context) {
            this.context = context;
        }
    
        public void setData(ArrayList<MyBean> data) {
            this.data = data;
            notifyDataSetChanged();
        }
    
        @Override
        public int getItemViewType(int position) {
            return data.get(position).getNumber();
        }
    
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            RecyclerView.ViewHolder holder = null;
            switch (viewType){
                case TYPEONE:
                    View view = LayoutInflater.from(context).inflate(R.layout.item,parent,false);
                    holder = new OneViewHolder(view);
                    break;
                case TYPETWO:
                    View view1 = LayoutInflater.from(context).inflate(R.layout.item2,parent,false);
                    holder = new TwoViewHolder(view1);
                    break;
            }
            return holder;
        }
    
        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            int itemViewType = getItemViewType(position);
            switch (itemViewType){
                case TYPEONE:
                    OneViewHolder oneViewHolder = (OneViewHolder) holder;
                    oneViewHolder.tv1.setText(data.get(position).getData());
                    oneViewHolder.name1.setText(data.get(position).getName());
                    oneViewHolder.time1.setText(data.get(position).getTime());
                    break;
                case TYPETWO:
                    TwoViewHolder twoViewHolder = (TwoViewHolder) holder;
                    twoViewHolder.tv2.setText(data.get(position).getData());
                    twoViewHolder.name2.setText(data.get(position).getName());
                    twoViewHolder.time2.setText(data.get(position).getTime());
                    break;
            }
        }
    
        @Override
        public int getItemCount() {
            return  data != null && data.size() > 0 ? data.size() : 0;
        }
        
        class OneViewHolder extends RecyclerView.ViewHolder{
           private TextView tv1;
            private TextView name1,time1;
           public OneViewHolder(View itemView) {
               super(itemView);
               tv1 = (TextView) itemView.findViewById(R.id.tv);
               name1 = (TextView) itemView.findViewById(R.id.tv_name);
               time1 = (TextView) itemView.findViewById(R.id.tv_time);
           }
       }
    
        class TwoViewHolder extends RecyclerView.ViewHolder{
            private TextView tv2;
            private TextView name2,time2;
            public TwoViewHolder(View itemView) {
                super(itemView);
                tv2 = (TextView) itemView.findViewById(R.id.tv2);
                name2 = (TextView) itemView.findViewById(R.id.tv_name2);
                time2 = (TextView) itemView.findViewById(R.id.tv_time2);
            }
        }
        
    }
     

    9.下面终于进入到了正题~进入到MainActivity中,代码如下所示:

    import android.os.Handler;
    import android.os.Message;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    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 java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.Socket;
    import java.text.SimpleDateFormat;
    import java.util.ArrayList;
    import java.util.Date;
    
    public class MainActivity extends AppCompatActivity {
    
        private RecyclerView rv;
        private EditText et;
        private Button btn;
        private Socket socket;
        private ArrayList<MyBean> list;
        private MyAdapter adapter;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            rv = (RecyclerView) findViewById(R.id.rv);
            et = (EditText) findViewById(R.id.et);
            btn = (Button) findViewById(R.id.btn);
            list = new ArrayList<>();
            adapter = new MyAdapter(this);
    
            final Handler handler = new MyHandler();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        socket = new Socket("192.168.1.111", 10010);
                        InputStream inputStream = socket.getInputStream();
                        byte[] buffer = new byte[1024];
                        int len;
                        while ((len = inputStream.read(buffer)) != -1) {
                            String data = new String(buffer, 0, len);
                            // 发到主线程中 收到的数据
                            Message message = Message.obtain();
                            message.what = 1;
                            message.obj = data;
                            handler.sendMessage(message);
                        }
    
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
    
            btn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    final String data = et.getText().toString();
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                OutputStream outputStream = socket.getOutputStream();
                                SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss");    //设置日期格式
                                outputStream.write((socket.getLocalPort() + "//" + data + "//" + df.format(new Date())).getBytes("utf-8"));
                                outputStream.flush();
    
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }).start();
                }
            });
    
        }
    
        private class MyHandler extends Handler {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                if (msg.what == 1) {
                    //
                    int localPort = socket.getLocalPort();
                    String[] split = ((String) msg.obj).split("//");
                    if (split[0].equals(localPort + "")) {
                        MyBean bean = new MyBean(split[1],1,split[2],"我:");
                        list.add(bean);
                    } else {
                        MyBean bean = new MyBean(split[1],2,split[2],("来自:" + split[0]));
                        list.add(bean);
                    }
    
                    // 向适配器set数据
                    adapter.setData(list);
                    rv.setAdapter(adapter);
                    LinearLayoutManager manager = new LinearLayoutManager(MainActivity.this, LinearLayoutManager.VERTICAL, false);
                    rv.setLayoutManager(manager);
                }
            }
        }
    }

    代码很简单,因为Socket发送的数据只能是一个基本的数据类型,不能传递类似于HashMap、集合、数组这样的数据,所以只能通过拼接字符串的形式通过加入一些特殊的符号,来起到分割数据的作用,因为传递的数据中带有发送者、接受者、时间、消息等这样的数据,所以通过split来区别这些数据,从而进行具体的分配来实现目的。

    10.最后权限不要忘记加入~

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

    那么运行实现的具体效果又是怎样的呢?请看下面(话说CSDN加图好麻烦啊):

    1)这是开启服务器之后,两台手机打开聊天Demo:

    2)这是客户端发送数据的显示内容:

    3)这是服务端在客户端聊天时显示的Log:

    Demo:https://github.com/shiqiangdva/Socket_Chat

    --------------------转载至https://blog.csdn.net/qq_37842258/article/details/70945192

  • 相关阅读:
    快捷JS PHP
    css userAgent (简易浏览器区分) PHP
    http://fw.qq.com/ipaddress PHP
    JS竖排文字 PHP
    奇怪的body PHP
    使用36进制,无损压缩GUID到26位 PHP
    链接<a>执行JS PHP
    纯JS省市区三级联动 PHP
    Table 样式 PHP
    Exceeded storage allocation. The server response was: 4.3.1 Message size exceeds fixed maximum message size
  • 原文地址:https://www.cnblogs.com/Frank-dev-blog/p/9409369.html
Copyright © 2011-2022 走看看