安卓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
安卓socket聊天的更多相关文章
- Socket聊天程序——Common
写在前面: 上一篇记录了Socket聊天程序的客户端设计,为了记录的完整性,这里还是将Socket聊天的最后一个模块--Common模块记录一下.Common的设计如下: 功能说明: Common模块 ...
- Socket聊天程序——客户端
写在前面: 上周末抽点时间把自己写的一个简单Socket聊天程序的初始设计和服务端细化设计记录了一下,周二终于等来毕业前考的软考证书,然后接下来就是在加班的日子度过了,今天正好周五,打算把客户端的详细 ...
- Socket聊天程序——服务端
写在前面: 昨天在博客记录自己抽空写的一个Socket聊天程序的初始设计,那是这个程序的整体设计,为了完整性,今天把服务端的设计细化记录一下,首页贴出Socket聊天程序的服务端大体设计图,如下图: ...
- 安卓Socket连接实现连接实现发送接收数据,openwrt wifi转串口连接单片机实现控制
安卓Socket连接实现连接实现发送接收数据,openwrt wifi转串口连接单片机实现控制 socket 连接采用流的方式进行发送接收数据,采用thread线程的方式. 什么是线程? 详细代码介 ...
- Java Socket聊天室编程(二)之利用socket实现单聊聊天室
这篇文章主要介绍了Java Socket聊天室编程(二)之利用socket实现单聊聊天室的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下 在上篇文章Java Socket聊天室编程(一)之 ...
- Java Socket聊天室编程(一)之利用socket实现聊天之消息推送
这篇文章主要介绍了Java Socket聊天室编程(一)之利用socket实现聊天之消息推送的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下 网上已经有很多利用socket实现聊天的例子了 ...
- [Socket]Socket聊天小程序
一个简单是Socket聊天小程序,读写操作在不同的线程中.服务器端采用线程池. 1.Server import java.io.IOException; import java.net.ServerS ...
- workerman-chat(PHP开发的基于Websocket协议的聊天室框架)(thinkphp也是支持socket聊天的)
workerman-chat(PHP开发的基于Websocket协议的聊天室框架)(thinkphp也是支持socket聊天的) 一.总结 1.下面链接里面还有一个来聊的php聊天室源码可以学习 2. ...
- python socket 聊天室
socket 发送的时候,使用的是全双工的形式,不是半双工的形式.全双工就是类似于电话,可以一直通信.并且,在发送后,如果又接受数据,那么在这个接受到数据之前,整个过程是不会停止的.会进行堵塞,堵塞就 ...
随机推荐
- ElasticSearch入门2: 基本用法
基本用法: 一.索引创建 (启动集群和索引请看上一篇文章:http://www.cnblogs.com/liuxiaoming123/p/8081883.html) 1.打开浏览器,输入请求:htt ...
- python处理json格式的数据
这里我就不介绍json了,不知道json的同学可以去百度一下json,首先我们的json的格式如下,这个json有点长,这个json来自我以前的一个小任务,具体看这里:http://www.cnblo ...
- StreamSets学习系列之StreamSets的集群安装(图文详解)
不多说,直接上干货! 若是集群安装 需要在对应节点执行相同的操作. 见 StreamSets学习系列之StreamSets支持多种安装方式[Core Tarball.Cloudera Parcel . ...
- spec 文件详解
转自http://blog.sina.com.cn/s/blog_43b39e250100nnu4.html rpm软件包系统的标准分组:/usr/share/doc/rpm-4.3.3/GROUPS ...
- Ceph 块设备 - 块设备快速入门
目录 一.准备工作 二.安装 Ceph 三.使用块存储 一.准备工作 本文描述如何安装 ceph 客户端,使用 Ceph 块设备 创建文件系统并挂载使用. 必须先完成 ceph 存储集群的搭建,并 ...
- Netty 核心组件 Pipeline 源码分析(二)一个请求的 pipeline 之旅
目录大纲: 前言 针对 Netty 例子源码做了哪些修改? 看 pipeline 是如何将数据送到自定义 handler 的 看 pipeline 是如何将数据从自定义 handler 送出的 总结 ...
- 并发编程之 CyclicBarrier 源码分析
前言 在之前的介绍 CountDownLatch 的文章中,CountDown 可以实现多个线程协调,在所有指定线程完成后,主线程才执行任务. 但是,CountDownLatch 有个缺陷,这点 JD ...
- [转]Reporting Services 中的身份验证类型
本文转自:https://docs.microsoft.com/zh-cn/previous-versions/sql/sql-server-2008/cc281310%28v%3dsql.100%2 ...
- WPF命令(Command)介绍、命令和数据绑定集成应用
要开始使用命令,必须做三件事: 一:定义一个命令 二:定义命令的实现 三:为命令创建一个触发器 WPF中命令系统的基础是一个相对简单的ICommand的接口,代码如下: public interfac ...
- C# 全文搜索Lucene
全文出自:https://blog.csdn.net/huangwenhua5000/article/details/9341751 1 lucene简介1.1 什么是luceneLucene是一个全 ...