安卓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 发送的时候,使用的是全双工的形式,不是半双工的形式.全双工就是类似于电话,可以一直通信.并且,在发送后,如果又接受数据,那么在这个接受到数据之前,整个过程是不会停止的.会进行堵塞,堵塞就 ...
随机推荐
- 一、Linq简介
语言集成查询Language Integrated Query(LINQ)是一系列将查询功能集成到C#语言的技术统称. 传统数据查询的缺点: 简单的字符串查询,没有编译时类型检查或Intellisen ...
- JVM 监控工具 jstack 和 jvisualvm 的使用
Java线程状态 线程的五种状态 * 新建:new(时间很短) * 运行:runnable * 等待:waitting(无限期等待),timed waitting(限期等待) * 阻塞:blocked ...
- RocketMQ多Master多Slave模式部署
每个 Master 配置一个 Slave,有多对Master-Slave,HA采用同步双写方式,主备都写成功,向应用返回成功. 优点:数据与服务都无单点,Master宕机情况下,消息无延迟,服务可用性 ...
- 线性查找算法(BFPRT)
BFPRT算法的作者是5位真正的大牛(Blum . Floyd . Pratt . Rivest . Tarjan). BFPRT解决的问题十分经典,即从某n个元素的序列中选出第k大(第k小)的元素, ...
- Linq 多表连接查询join
在查询语言中,通常需要使用联接操作.在 LINQ 中,可以通过 join 子句实现联接操作.join 子句可以将来自不同源序列,并且在对象模型中没有直接关系(数据库表之间没有关系)的元素相关联,唯一的 ...
- Python代码注释应该怎么写?
https://zhuanlan.zhihu.com/p/22663276?refer=passer http://zh-google-styleguide.readthedocs.io/en/lat ...
- Jni如何传递并且修改两个基础参数
最近在开发jni时,需要返回多个参数给java.这个过程中,碰到了一些问题,值得探讨一下. 具体是这样,jni方法jni_do_something作了底层处理后,得出两个int数据,需要将他们的值 ...
- keepalived之单播----k8sHA准备
一.概述 keepalived主要有三个模块,分别是core.check和vrrp.core模块为keepalived的核心,负责主进程的启动.维护以及全局配置文件的加载和解析.check负责健康检查 ...
- postgreSql 常用查询总结
1. 日期格式转化(参考) select beg_time, end_time, extract(epoch from to_timestamp(end_time,'yyyy-mm-dd-HH24-M ...
- 安装win8/win10提示无法在驱动器0分区上安装windows解决方法
在通过U盘或光盘安装win8/win8.1/win10系统时,不少用户遇到无法安装的问题,提示“无法在驱动器0的分区1上安装windows”,格式化分区1也不能解决,进而提示Windows无法安装到这 ...