安卓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 发送的时候,使用的是全双工的形式,不是半双工的形式.全双工就是类似于电话,可以一直通信.并且,在发送后,如果又接受数据,那么在这个接受到数据之前,整个过程是不会停止的.会进行堵塞,堵塞就 ...
随机推荐
- 了解fortran语言
最近看了一些文献,发现用了Fortran语言编程,并且还是近几年的,了解了之后才知道,其实Fortran已经慢慢没有人再用了,之所有还有一批人在用,极大可能是历史遗留问题吧.而这,也得从Fortran ...
- 源码分析篇 - Android绘制流程(二)measure、layout、draw流程
performTraversals方法会经过measure.layout和draw三个流程才能将一帧View需要显示的内容绘制到屏幕上,用最简化的方式看ViewRootImpl.performTrav ...
- 执行bin/hdfs haadmin -transitionToActive nn1时出现,Automatic failover is enabled for NameNode at bigdata-pro02.kfk.com/192.168.80.152:8020 Refusing to manually manage HA state的解决办法(图文详解)
不多说,直接上干货! 首先, 那么,你也许,第一感觉,是想到的是 全网最详细的Hadoop HA集群启动后,两个namenode都是standby的解决办法(图文详解) 这里,nn1,不多赘述了.很简 ...
- JavaScript基础(一)概述
JavaScript 概述 JS作用 验证表单(以前的网速慢) 页面特效(PC端的网页效果) 移动端(移动web和app) 异步和服务器交互(AJAX) 服务端开发(nodejs) 语言类型 js是一 ...
- 解读Secondary NameNode的功能
1.概述 最近有朋友问我Secondary NameNode的作用,是不是NameNode的备份?是不是为了防止NameNode的单点问题?确实,刚接触Hadoop,从字面上看,很容易会把Second ...
- maven install 打包 报错 Cannot run program "gpg.exe": CreateProcess error
打包报错, mvn install后加上参数-Dgpg.skip,例如:mvn install -Dgpg.skip 即可解决. 我们也可以去掉 这个 插件 <plugin> ...
- SSM整合(2): spring 与 mybatis 整合
在进行完spring与springmvc整合之后, 继续 spring与mybatis的整合. 既然是操作数据库, 那必然不能缺少了连接属性 一. db.properties jdbc.driver= ...
- Oracle Comment 获取并修改表或字段注释
select * from dba_tables where owner = 'DINGYINGSI'; select * from user_col_comments where table_nam ...
- tcp/ip通信中ip头部结构iph->check校验计算
通过raw socket修改通信数据后,可通过该函数重新校验计算iph->check值 在http://www.cnblogs.com/dpf-10/p/7899237.html查看实际调用 s ...
- JS 定时器 setTimeout 与 setInterval 的区别和用法
定时器: window.setTimeout(function(){},间隔时间毫秒); -- 定时炸弹,延迟执行,只执行一次 window.setInterval(function(){},间隔的时 ...