专栏首页cmazxiaoma的架构师之路Android实战 粗略实现一个简单的C/S结构聊天室的功能

Android实战 粗略实现一个简单的C/S结构聊天室的功能

1.设计思路: 在实际应用中,客户端需要和服务器保持长时间的通信,而服务器需要不断地读取客户端数据,并向客户端写入数据;客户端也需要不断地读取服务器数据,并向服务器写入数据。当使用传统BufferedReader的readLine()方法,在该方法返回成功之前,线程会被阻塞,程序无法执行。重点:考虑到这个原因,服务器应该为每个Socket单独启动一条线程,每条线程负责与一个客户端进行通信。同样,客户端读取服务器数据的线程同样会被堵塞,所以系统应该单独启动一条线程,该线程专门负责读取服务器数据。 2.服务器相关代码

**//MyServer.java**
importjava.io.IOException;
importjava.net.ServerSocket;
importjava.net.Socket;
importjava.text.SimpleDateFormat;
importjava.util.ArrayList;
importjava.util.Date;
public classMyServer {
static intport=40011;
staticServerSocketserverSocket=null;
**//定义保存所有Socket的ArrayList**
public staticArrayListsocketList=newArrayList();
public static voidmain(String[] args)throwsIOException{
serverSocket=newServerSocket(port);
System.out.println("开启服务器...");
while(true){
**//此行代码会阻塞,将一直等待别人的连接**
Socket s=serverSocket.accept();
SimpleDateFormat sdf=newSimpleDateFormat("yy-MM-dd HH:mm:ss");
System.out.println(sdf.format(newDate())+"客户端"+s+"已经连接!");
socketList.add(s);
System.out.println(sdf.format(newDate())+"当前在线客户端列表="+socketList);
**//每当客户端连接后启动一条ServerThread线程为该客户端服务**
ServerThread severThread=newServerThread(s);
newThread(severThread).start();}}}
****
**//ServerThread.java**
importjava.io.BufferedReader;
importjava.io.IOException;
importjava.io.InputStreamReader;
importjava.io.OutputStream;
importjava.net.Socket;
importjava.text.SimpleDateFormat;
importjava.util.Date;
importjava.util.Iterator;
**//负责处理每条线程通信的线程类**
public  classServerThreadimplementsRunnable{
//定义当前线程所处理的Socket
Sockets=null;
**//该线程所处理的Socket所对象的输入流**
BufferedReaderbf=null;
publicServerThread(Socket s)throwsIOException{
this.s=s;
**//初始化该Socket对应的输入流**
bf=newBufferedReader(newInputStreamReader(s.getInputStream(),"utf-8"));
}
public voidrun(){
String content=null;
**//采取循环不断的从Socket中读取客户端发送过来的数据**
System.out.println("服务器判断客户端时候发送消息");
while((content=readFromClient())!=null){
**//将读到的内容向每一个Socket发送一次**
for(Iterator it=MyServer.socketList.iterator();it.hasNext();){
Socket s=it.next();
try{
System.out.println("读取了客户端过来的数据");
SimpleDateFormat sdf=newSimpleDateFormat("yy-MM-dd HH:mm:ss");
System.out.println(sdf.format(newDate())+"客户端:"+s+"发来的消息="+content);
OutputStream os=s.getOutputStream();
os.write((content+"\n").getBytes("utf-8"));
}catch(Exception e){;
**//删除该Socket**
it.remove();
System.out.println("当前抛出socket有异常");
System.out.println("当前socketList队列="+MyServer.socketList);
}
}
}
}
//定义读取客户端数据的方法
privateStringreadFromClient(){
try{
String data=bf.readLine();
System.out.println("读出来的消息="+data);
returndata;
}catch(IOException e){
e.printStackTrace();
**//删除该Socket**
MyServer.socketList.remove(s);
}
return null;
}
}

3.客户端相关代码

**//MainActivity.java**
packagecom.example.administrator.multithreadclient;
importandroid.app.Activity;
importandroid.os.Handler;
importandroid.os.Message;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.view.View;
importandroid.widget.EditText;
importandroid.widget.ImageButton;
importandroid.widget.TextView;
importjava.io.IOException;
importjava.io.OutputStream;
importjava.net.Socket;
importjava.text.SimpleDateFormat;
public classMainActivityextendsActivity {
EditTextinput;
TextViewshow;
ImageButtonsend;
**//定义与服务器通信的子线程**
ClientThreadclientThread;
@Override
protected voidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
input= (EditText) findViewById(R.id.input);
send= (ImageButton) findViewById(R.id.send);
show= (TextView) findViewById(R.id.show);
finalStringBuilder sb=newStringBuilder();
Handler handler=newHandler(){
@Override
public voidhandleMessage(Message msg) {
//如果消息来自于子线程
if(msg.what==0x123){
show.setTextSize(15);
sb.append(msg.obj);
sb.append("\n");
show.setText(sb.toString());
send.setVisibility(View.VISIBLE);
}
}
};
clientThread=newClientThread(handler);
**//客户端启动ClientThread线程创建网络连接,读取来自服务器的数据**
newThread(clientThread).start();
send.setOnClickListener(newView.OnClickListener() {
@Override
public voidonClick(View v) {
try{
Message msg=newMessage();
msg.what=0x345;
msg.obj=input.getText().toString();
System.out.println("您发送的消息="+msg.obj);
clientThread.revHandler.sendMessage(msg);
System.out.println("您发送的消息已经交给多线程处理");
//清空文本框
input.setText("");
send.setVisibility(View.GONE);
}catch(Exception e){
e.printStackTrace();
}
}
});
}
}
**//ClientThread.java**
packagecom.example.administrator.multithreadclient;
importandroid.os.Handler;
importandroid.os.Looper;
importandroid.os.Message;
importandroid.util.Log;
importjava.io.BufferedReader;
importjava.io.IOException;
importjava.io.InputStreamReader;
importjava.io.OutputStream;
importjava.net.InetSocketAddress;
importjava.net.Socket;
importjava.net.SocketTimeoutException;
/**
* Created by Administrator on 2017/2/9.
*/
public classClientThreadimplementsRunnable {
privateSockets;
//定义向UI线程发送消息的Handler对象
privateHandlerhandler;
//定义接受Ui线程消息的Handler对象
publicHandlerrevHandler;
//该线程所处理的Socket所对应的输入流
BufferedReaderbf=null;
OutputStreamos=null;
private final intport=40011;
private finalStringremoteAddress="192.168.0.106";
publicClientThread(Handler handler){
this.handler=handler;
}
public voidrun(){
try{
//创建一个无连接的Socket
s=newSocket(remoteAddress,port);
bf=newBufferedReader(newInputStreamReader(s.getInputStream(),"utf-8"));
os=s.getOutputStream();
**//启动一条子线程来读取服务器响应的数据**
newThread(){
public voidrun(){
Stringcontent=null;
while((content=readToServer())!=null){
**//每当读到来自服务器的消息之后,响应给U界面**
System.out.println("读取到服务器发送给客户端的消息="+content);
Message msg=newMessage();
msg.what=0x123;
msg.obj=s+":"+content;
handler.sendMessage(msg);
}
}
}.start();
**//为当前线程初始化Looper**
Looper.prepare();
revHandler=newHandler(){
@Override
public voidhandleMessage(Message msg) {
//接受到UI线程中用户输入的数据
if(msg.what==0x345){
try{
System.out.println("将您的消息发给服务器进行处理");
System.out.println("os="+os);
System.out.println("socket="+s);
**//将用户在文本框内输入的内容写入网络**
os.write((msg.obj.toString()+"\n").getBytes("utf-8"));
}catch(Exception e){
e.printStackTrace();
}
}
}
};
**//启动Looper**
Looper.loop();
}catch(SocketTimeoutException e){
System.out.println("网络连接超时");
}
catch(IOException e){
e.printStackTrace();
}
}
publicStringreadToServer(){
try{
returnbf.readLine();
}catch(IOException e){
e.printStackTrace();
System.out.println("客户端不能从服务器上读取数据!");
}
return null;
}
}

4.布局代码

//activity_main.xml
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.administrator.multithreadclient.MainActivity">
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/input"/>
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_chat_white_48dp"
android:id="@+id/send"
android:background="#00000000"/>
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:textSize="8pt"
android:textColor="#BFF4FD"
android:text="服务器发来的消息"/>
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/show"/>

5.技术点总结 1.建立网络连接,网络通信是不稳定的,它所需要的时间也不确定,因此直接在UI线程中建立网络连接,通过网络读取数据可能阻塞UI线程,导致Android应用失去响应。 2.<uses-permissionandroid:name="android.permission.INTERNET"/> 加入访问网络的权限 3.迭代器用法 (1)Iterator<Socket> it=MyServer.SockList.iterator();使用Iterator()要求返回一个Iterator (2)使用next()获取序列中的下一个元素 (3)使用hasNext()检查序列中时候还有元素 (4)使用remove()将迭代器返回的元素删除 4.多线程使用 5.Handler,MessageQueue,Looper原理 1)Looper:每一个线程只有一个Looper,它负责管理MessageQueue,会不断的从Message中取出消息,并且将消息分给对应的Handler处理 2)MessageQueue:由Looper负责管理,它采用先进先出的方式来管理Message 3)Handler:它能将消息发送给Looper管理的Message,也可以处理Looper分发给它的消息 6.extends Thread 和 implements Runnable的区别 1)采用extends Thread优点:编程简单,如果要访问当前线程,无需使用Thread.currentThread()方法,可以直接用this,即可获取当前线程。 缺点:由于继承了Thread,类无法再继承其他的父类 2)采用implements Runnable** 优点**:没有继承Thread类,所以可以继承其他的父类,在这种形式下,多个线程可以共享同一个对象,所以非常合适多个相同的线程来处理同一份资源的情况下,把cpu代码和数据分开,形成清晰的模型,较好的体现了面向对象的思想 缺点:编程稍微复杂,如果要访问当前线程,必须使用Thread.currentThread()方法 7.测试截图 客户端1的截图


客户端2的截图


服务器端的截图


心之所向,素履以往。生如逆旅,一苇以航。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Android仿淘宝购物车,玩转电商购物车

    用户2032165
  • 你应该知道的VirtualBox虚拟机网络配置

    用户2032165
  • 通过分析LinkedHashMap了解LRU

    我们都知道LRU是最近最少使用,根据数据的历史访问记录来进行淘汰数据的。其核心思想是如果数据最近被访问过,那么将来访问的几率也更高。在这里提一下,Redis缓存...

    用户2032165
  • 面向对象(二十三)-网络 Socket 理论知识

    计算机在网络上都有一个 IP地址,每个计算机都有端口,端口范围在0-65535之间。 端口,是计算机上 应用程序通讯所用的地址。

    雷潮
  • [javaSE] 网络编程(TCP,UDP,Socket特点)

    陶士涵
  • 【leetcode刷题】T72-反转字符串

    编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。

    木又AI帮
  • 2019 第五届「四维图新杯」,地信创新大赛,实打实奖金 10w+!

    与往届「四维图新杯」创新大赛不同的是,本届大赛将选题进一步扩展,要求选手以数字地图、位置大数据领域为创新方向,进行行业跨界、技术融合的创新设计即可。这一制度不仅...

    AI研习社
  • 好省app新手必看 5分钟带你玩转好省团长模式

     好省是一款导购APP,依托于淘宝天猫平台,为广大用户提供海量优惠券,致力于帮助用户买到最具性价比的网购商品。与此同时,好省还一个创业平台,在这里,任何人...

    速成应用小程序开发平台
  • Mac安装配置Tomcat

    地址:http://tomcat.apache.org/download-90.cgi 

    week
  • 最新SCI影响因子发布!Nature屠榜,AI领域Top 1000期刊盘点

    Clarivate Analytics发布了最新年度期刊引用报告(JCR),2018年度共有12538种期刊被收录(不过有25本期刊的影响因子为 0),其中影响...

    新智元

扫码关注云+社区

领取腾讯云代金券