安卓基于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聊天的更多相关文章

  1. Socket聊天程序——Common

    写在前面: 上一篇记录了Socket聊天程序的客户端设计,为了记录的完整性,这里还是将Socket聊天的最后一个模块--Common模块记录一下.Common的设计如下: 功能说明: Common模块 ...

  2. Socket聊天程序——客户端

    写在前面: 上周末抽点时间把自己写的一个简单Socket聊天程序的初始设计和服务端细化设计记录了一下,周二终于等来毕业前考的软考证书,然后接下来就是在加班的日子度过了,今天正好周五,打算把客户端的详细 ...

  3. Socket聊天程序——服务端

    写在前面: 昨天在博客记录自己抽空写的一个Socket聊天程序的初始设计,那是这个程序的整体设计,为了完整性,今天把服务端的设计细化记录一下,首页贴出Socket聊天程序的服务端大体设计图,如下图: ...

  4. 安卓Socket连接实现连接实现发送接收数据,openwrt wifi转串口连接单片机实现控制

    安卓Socket连接实现连接实现发送接收数据,openwrt wifi转串口连接单片机实现控制 socket 连接采用流的方式进行发送接收数据,采用thread线程的方式. 什么是线程?  详细代码介 ...

  5. Java Socket聊天室编程(二)之利用socket实现单聊聊天室

    这篇文章主要介绍了Java Socket聊天室编程(二)之利用socket实现单聊聊天室的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下 在上篇文章Java Socket聊天室编程(一)之 ...

  6. Java Socket聊天室编程(一)之利用socket实现聊天之消息推送

    这篇文章主要介绍了Java Socket聊天室编程(一)之利用socket实现聊天之消息推送的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下 网上已经有很多利用socket实现聊天的例子了 ...

  7. [Socket]Socket聊天小程序

    一个简单是Socket聊天小程序,读写操作在不同的线程中.服务器端采用线程池. 1.Server import java.io.IOException; import java.net.ServerS ...

  8. workerman-chat(PHP开发的基于Websocket协议的聊天室框架)(thinkphp也是支持socket聊天的)

    workerman-chat(PHP开发的基于Websocket协议的聊天室框架)(thinkphp也是支持socket聊天的) 一.总结 1.下面链接里面还有一个来聊的php聊天室源码可以学习 2. ...

  9. python socket 聊天室

    socket 发送的时候,使用的是全双工的形式,不是半双工的形式.全双工就是类似于电话,可以一直通信.并且,在发送后,如果又接受数据,那么在这个接受到数据之前,整个过程是不会停止的.会进行堵塞,堵塞就 ...

随机推荐

  1. 了解fortran语言

    最近看了一些文献,发现用了Fortran语言编程,并且还是近几年的,了解了之后才知道,其实Fortran已经慢慢没有人再用了,之所有还有一批人在用,极大可能是历史遗留问题吧.而这,也得从Fortran ...

  2. 源码分析篇 - Android绘制流程(二)measure、layout、draw流程

    performTraversals方法会经过measure.layout和draw三个流程才能将一帧View需要显示的内容绘制到屏幕上,用最简化的方式看ViewRootImpl.performTrav ...

  3. 执行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,不多赘述了.很简 ...

  4. JavaScript基础(一)概述

    JavaScript 概述 JS作用 验证表单(以前的网速慢) 页面特效(PC端的网页效果) 移动端(移动web和app) 异步和服务器交互(AJAX) 服务端开发(nodejs) 语言类型 js是一 ...

  5. 解读Secondary NameNode的功能

    1.概述 最近有朋友问我Secondary NameNode的作用,是不是NameNode的备份?是不是为了防止NameNode的单点问题?确实,刚接触Hadoop,从字面上看,很容易会把Second ...

  6. maven install 打包 报错 Cannot run program "gpg.exe": CreateProcess error

    打包报错, mvn install后加上参数-Dgpg.skip,例如:mvn install -Dgpg.skip   即可解决. 我们也可以去掉 这个 插件   <plugin>    ...

  7. SSM整合(2): spring 与 mybatis 整合

    在进行完spring与springmvc整合之后, 继续 spring与mybatis的整合. 既然是操作数据库, 那必然不能缺少了连接属性 一. db.properties jdbc.driver= ...

  8. Oracle Comment 获取并修改表或字段注释

    select * from dba_tables where owner = 'DINGYINGSI'; select * from user_col_comments where table_nam ...

  9. tcp/ip通信中ip头部结构iph->check校验计算

    通过raw socket修改通信数据后,可通过该函数重新校验计算iph->check值 在http://www.cnblogs.com/dpf-10/p/7899237.html查看实际调用 s ...

  10. JS 定时器 setTimeout 与 setInterval 的区别和用法

    定时器: window.setTimeout(function(){},间隔时间毫秒); -- 定时炸弹,延迟执行,只执行一次 window.setInterval(function(){},间隔的时 ...