前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android实战 粗略实现一个简单的C/S结构聊天室的功能

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

作者头像
用户2032165
发布2018-06-05 16:39:35
9140
发布2018-06-05 16:39:35
举报

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

代码语言:javascript
复制
**//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.客户端相关代码

代码语言:javascript
复制
**//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();
}
}
});
}
}
代码语言:javascript
复制
**//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.布局代码

代码语言:javascript
复制
//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的截图


服务器端的截图


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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017.02.09 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档