作者:邹峰立,微博:zrunker,邮箱:zrunker@yahoo.com,微信公众号:书客创作,个人平台:www.ibooker.cc。
书客创作
简易聊天室,什么是聊天室呢,简单一点说就是一些人可以共同聊天,别人能够看见你发布的消息,你也可以看到别人的消息,大家的消息是公开的。
功能分析:
1、聊天功能,聊天是一个长时间的相互交互的过程,要实现长时间连接Socket是一个比较不错的选择。
2、一些人相互聊天功能,要想实现相互聊天,就要将消息转发给所有建立连接的人,这里就要进行消息转发。
实现思路:
利用Socket实现客户端和服务端长连接,每次将连接进来的Socket保存到一个集合当中,当其中一个Socket有接收到数据的时候,将接收到的数据转发给其他的Socket,这样既能实现聊天功能又能实现转发。
对Socket的操作应放在子线程当中,每一次有新的Socket连接进来之后就新开一个子线程。
如果对Socket不是很了解,推荐【JavaEE】Socket简单体验, TCP和UDP这篇文章进行了解。
服务端搭建
首先要实现监听客户端连接,需要利用ServerSocket的accept方法进行监听,但是该方法会将程序阻塞,所以该监听过程要放在子线程中完成。这里定义的线程为ServerListener。
其次每监听到一个Socket,Socket无论是获取还是传输过程都是对字节流进行操作,这是一个比较耗时的操作,所以也要放在子线程中操作。这里定义的线程为ChatSocket。
最后还要定义一个Socket的子线程管理类,方便对所有连接进来的Socket的子线程进行管理,例如对数据的转发。这里定义的管理类为ChatManager。
在程序最开始运行的时候,要开启对Socket连接的监听,所以在main方法中开始监听线程。
public static void main(String[] args) {
// 开启服务端监听
new ServerListener().start();
}
开启监听线程之后,当监听到有Socket进行连接的时候,不仅要新建一个子线程实现该Socket的操作,而且要把该Socket添加到Socket线程管理类ChatManager当中,方便后期管理。
public class ServerListener extends Thread {
@Override
public void run() {
try {
// 1、创建ServerScoket,设置端口
ServerSocket serverSocket = new ServerSocket(12345);
while (true) {
// 2、accept方法将导致程序阻塞
Socket socket = serverSocket.accept();
JOptionPane.showMessageDialog(null, "有客户端连接到本机的12345端口");
// 3、将socket传递给新线程
ChatSocket cs = new ChatSocket(socket);
cs.start();
// 4、使用Chatmanager进行管理
ChatManager.getInstance().add(cs);
}
} catch (IOException e) {e.printStackTrace();}
}
}
在ChatSocket新线程中,要实现对接收到的Socket进行处理,例如获取Socket客户端发送过来的数据进行转发等。而转发过程,是转发给所有连接进来的Socket,所以转发过程的实现将由ChatManager完成。
ChatSocket在一开启线程的时候,就要进行客户端数据获取,并转发。
ChatSocket要能够完成服务端想客户端发送数据的过程。
public class ChatSocket extends Thread {
private Socket socket;
public ChatSocket(Socket socket) {
this.socket = socket;
}
// 服务端传值给客户端
public void out(String out) {
try {
if (socket.isConnected() && !socket.isClosed()) {
// 获取当前Socket输出流,输出数据
socket.getOutputStream().write(out.getBytes("gbk"));
System.out.println("转发数据**********" + out);
} else {
// 链接已关闭
ChatManager.getInstance().remove(this);
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
try {
if (socket.isConnected() && !socket.isClosed()) {
// 接受客户端数据
BufferedReader br = new BufferedReader(new InputStreamReader(
socket.getInputStream(), "gbk"));
// 读取数据
String line = null;
while ((line = br.readLine()) != null) {
// 转发数据
ChatManager.getInstance().publish(this, line);
System.out.println("接受数据******" + line);
}
br.close();
} else {
// 链接已关闭
ChatManager.getInstance().remove(this);
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
最后来说说Socket线程管理类ChatManager,一个聊天室只能有一个Socket线程管理类,所以该类要使用单例模式。
private static ChatManager cm;
// 单例
public static ChatManager getInstance() {
if (cm == null) {
cm = new ChatManager();
}
return cm;
}
提供Socket子线程存放集合,能够实现Socket子线程添加和移除功能。
// ChatSocket集合
Vector vector = new Vector<>();
// 添加ChatSocket
public void add(ChatSocket chatSocket) {
vector.add(chatSocket);
}
// 移除ChatSocket
public void remove(ChatSocket chatSocket) {
vector.remove(chatSocket);
}
最后是借助Socket子线程存放集合,实现数据转发功能。
// 发送消息
public void publish(ChatSocket cs, String out) {
for (int i = 0; i < vector.size(); i++) {
ChatSocket chatSocket = vector.get(i);
if (!cs.equals(chatSocket)) {
chatSocket.out(out);
}
}
}
Android端搭建
对于Android客户端而言就显得稍微简单一些,只需要实现数据的接受和传递即可。
首先是界面的构建:
界面的构建客户端界面
当点击连接的时候,客户端会向服务端发送连接请求,请求成功之后便可发送消息,聊天内容将会在界面中间部分显示。
1、初始化信息,在Activity中完成对控件的初始化和点击事件监听。
// 定义控件全局变量
private TextView ipTv, contentTv;
private Button linkBtn, sendBtn;
private EditText sendEd;
// 初始化控件
private void initView() {
ipTv = (TextView) findViewById(R.id.tv_ip);
contentTv = (TextView) findViewById(R.id.tv_content);
linkBtn = (Button) findViewById(R.id.btn_link);
linkBtn.setOnClickListener(this);
sendBtn = (Button) findViewById(R.id.btn_send);
sendBtn.setOnClickListener(this);
sendEd = (EditText) findViewById(R.id.ed_send);
}
// 点击事件监听
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_link:// 链接
connect();
break;
case R.id.btn_send:// 发送
send();
break;
}
}
2、连接服务端,并获取服务端传递数据。
// 定义三个全局变量
private Socket socket;
private BufferedReader reader;
private BufferedWriter writer;
// 连接Socket服务端
private void connect() {
final String ip = ipTv.getText().toString().trim();
final int port = 12345;
// 异步执行
AsyncTask asyncTask = new AsyncTask() {
@Override
protected Void doInBackground(Void... params) {
try {
// 实例化Socket
socket = new Socket(ip, port);
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
publishProgress("success");
// 读取传递过来的数据
String line;
while ((line = reader.readLine()) != null) {
publishProgress(line);
}
} catch (IOException e) {
publishProgress("failed");
e.printStackTrace();
}
return null;
}
@Override
protected void onProgressUpdate(String... values) {
if ("success".equals(values[0]))
Toast.makeText(MainActivity.this, "连接成功", Toast.LENGTH_LONG).show();
else if ("failed".equals(values[0]))
Toast.makeText(MainActivity.this, "无法建立连接", Toast.LENGTH_LONG).show();
else
contentTv.append("他说:" + values[0] + "\n");
super.onProgressUpdate(values);
}
};
asyncTask.execute();
}
注意连接服务端和都去服务端数据是一个耗时的操作,所以应放在子线程中完成,这里是采用异步AsyncTask的方式来进行处理。
3、发送数据
// 向Socket服务端发送
private void send() {
String out = sendEd.getText().toString().trim();
// 发送数据
try {
writer.write(out + "\n");
writer.flush();
sendEd.setText("");
contentTv.append("我说:" + out + "\n");
} catch (IOException e) {
e.printStackTrace();
}
}
到这里就已经全部完成了,包括服务端和Android客户端的实现,在实际开发当中往往是通过构建一些比较成熟的框架来实现这一过程,实现原理大致相同,实现过程有所不同。
Github后台代码地址
Github安卓客户端代码地址
阅读原文
微信公众号:书客创作
网友评论