使用socket打造一个聊天室,本人是个小白,过程中遇到的所有问题,我都会进行说明,一切面向和我一样的小白,毕竟大神也不会看这些,先看一看效果
资料说明:基于socket的聊天室,包括一个服务端,一个客户端代码,一个将客户端代码封装好后的app代码和apk文件,socket,https相关资料,网络基础知识资料,观察者模式资料
第一种:一个socket服务端,一个客户端,客户端上线,服务端有提示,会打印上线设备的ip,表示建立连接
client客户端,发送一个消息,服务端会转发到所有设备,前面的数字是发送该信息的设备端口号,可以当id用
第二种将上面的客户端简单封装后,做成app,服务端不变,不要问我为什么一个大一个小,懒得改了…
一:好啦,正式开始,我会一步一步的完成这个小东西,以下内容仅适合小小白阅读,大神请直接看代码
1.本人刚学Android一个月左右,看完郭大神的第一行代码,觉得很不错,给五星好吧,不吹不黑,后来又买了Android 精彩编程200例,这个是有点坑,例子多,质量就不敢保证了,一般一个例子或许只有十几行代码,一笔带过,看完依然云里雾里,在这提醒大家,土豪略过。
我的初衷是做一个真正的翻版qq,这算是万里长征第一步吧,嘿嘿嘿嘿,整个代码异常简单,尤其是客户端
2.通过这个例子,你将会学到以下:
socket连接----异常------线程---------handler异步消息转发---------加深构造方法的理解 -------- getInputStream等流的操作 ----一个loding等待界面的动画------------观察者模式-----泛型等
二:上代码,代码为第一种方式,即服务端-客户端,app只是封装了以一下
服务端:服务端代码可以在Android studio,eclipse,java web端运行,其本质只是一段j基于java的独立程序 ,不拘泥与平台
思路:在TcpServer中开一个socket,通过阻塞循环获取收到的字节流信息传给clientTask处理,clientTask将收到的消息转成字符串并调用Msgpool进行分发,分发消息使用观察者模式,把消息传给所有连接上的设备,在Msqpool中每接到一个设备的消息就为其开一个线程,
结构:
TcpServer ------> socketl连接
clientTask -------> socket读取的消息进行处理,连接TcpServer和Msgpool
Msgpool ---------> 消息转发给各个客户端
TcpServer 由于代码较多,就不贴了,所有代码都有详细注释(见开头),已经上传
package com.example.tcpserver.tcpserver;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class TcpServer {
//方法带括号,属性不带括号
public void start(){
//服务端是serversocket 客户端是socket
ServerSocket serverSocket = null;
try {
//服务端只需要开一个端口即可,网络服务属于耗时操作,需在线程里进行,以免影响其他任务,
// 但是这里服务器只进行网络的监控这一个操作,所以可以省略
serverSocket = new ServerSocket(9100);
//Msgpool主要负责消息分发给各个客户端,这里是启动Msgpool
Msgpool.getInstance().start();
//循环监听端口,一有消息立即丢给clientTask进行处理,然后继续监听,避免连接上一个设备后,
// 其余设备无法连接,端口被占用的情况
while (true){
//端口堵塞,会一直卡在这,直到有消息,丢给clientTask后继续堵在这
Socket socket = serverSocket.accept();
//系统打印连接者的ip和端口号
System.out.println("ip:"+socket.getInetAddress().getHostAddress()+"port:"
+socket.getPort()+"is online..");
//clientTask负责处理接受到的信息
ClientTask clientTask = new ClientTask(socket);
//将处理好的消息丢给Msgpool进行转发
Msgpool.getInstance().addMsgConingListener(clientTask);
clientTask.start();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
public static void main(String[] args) {
new TcpServer().start();
}
}
客户端:借鉴了mvp模式的一点精髓,逻辑,界面,活动分开
TCPActivity:
package com.example.myapplication;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.Button; //发送按钮
import android.widget.EditText; //文本框
import android.widget.ProgressBar; //圆形进度条,用来当loding加载过程
import android.widget.TextView; //消息显示区
import android.text.TextUtils;
import com.example.myapplication.tcpclient.TcpClientApp;
public class TCPActivity extends AppCompatActivity {
private EditText et;
private TextView tv;
private Button bt;
private ProgressBar loding;
private TextView tv_loding;
private TcpClientApp tcpClientApp = new TcpClientApp();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initview(); //控件初始化
tcpClientApp.setOnMsgComingListener(new TcpClientApp.OnMsgComingListener() {
@Override
public void onMsgComing(String msg) {
appendMsgToContent(msg);
}
@Override
public void onError(Exception e) {
e.printStackTrace();
}
@Override//是否连接的handle
public void uiupDate(String msg) {
upDate(msg);
}
});
bt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String msg = et.getText().toString();
if(TextUtils.isEmpty(msg)){ //一个工具类,进行字符串处理,判断是否为空
return;
}
tv.setText("");//清空界面
tcpClientApp.sendMsg(msg);
}
});
}
private void upDate(String msg) {
if(msg == "wait"){
loding.setVisibility(View.VISIBLE); //设置控件的显示和隐藏
tv_loding.setVisibility(View.VISIBLE);
}
if(msg == "kkk"){
tv.append("kkk"); //测试,不用管
}
if(msg == "content"){
loding.setVisibility(View.GONE);
tv_loding.setVisibility(View.GONE);
tv.append("连接成功!");
}
}
private void appendMsgToContent(String msg){
tv.append(msg+"\n"); //向ui更新消息
}
private void initview(){
et = findViewById(R.id.et_msg);
tv = findViewById(R.id.tv_msg);
bt = findViewById(R.id.bt_send);
loding = findViewById(R.id.loging);
tv_loding = findViewById(R.id.tv_loding);
}
protected void onDestroy(){
super.onDestroy();
tcpClientApp.onDestroy();
}
}
TcpClientApp:
package com.example.myapplication.tcpclient;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.widget.ProgressBar;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
public class TcpClientApp {
private Socket msocket;
private InputStream ls;
private OutputStream os;
private Handler mUiHandler = new Handler(Looper.getMainLooper());
private Handler waithandler = new Handler(Looper.getMainLooper());
private static final String TAG = "TcpClientApp";
//接口,在活动中实例化
public interface OnMsgComingListener{
void onMsgComing(String msg);
void onError(Exception e);
void uiupDate(String msg);
}
private OnMsgComingListener mlistener;
public void setOnMsgComingListener(OnMsgComingListener listener){
mlistener = listener;
}
public TcpClientApp() { //构造方法,和类名一致
//一定要在线程中进行,网络连接属于耗时操作
//可以看见,socket连接前后设置了两个检测的handler,因为这里socket是异步的
//就是说,在我没有连接成功的时候,用下面的sendmsg发送消息会报空指针异常
//所以通过handle将是否已经连接这一信息传给主线程,并在ui界面更新显示
new Thread(){
@Override
public void run() {
try {
waithandler.post(new Runnable() {
@Override
public void run() {
//handler消息机制 向主程序发送wait标识正在连接
mlistener.uiupDate("wait");
// 在这里执行你要想的操作 比如直接在这里更新ui或者调用回调在 在回调中更新ui
}
});
msocket = new Socket("10.4.2.107", 9100);
ls = msocket.getInputStream();
os = msocket.getOutputStream();
waithandler.post(new Runnable() {
@Override
public void run() {
//连接成功发送content,
mlistener.uiupDate("content");
}
});
readServerMsg();
}catch (final IOException e){
mUiHandler.post(new Runnable() {
@Override
public void run() {
if (mlistener != null) {
mlistener.onError(e);
}
}
});
//handle消息
}
}
}.start();
}
private void readServerMsg() throws IOException {
final BufferedReader br = new BufferedReader(new InputStreamReader(ls));
String line = null;
while ((line = br.readLine()) != null) {
final String finalLine = line;
//handle消息
mUiHandler.post(new Runnable() {
@Override
public void run() {
if (mlistener != null) {
Log.d(TAG, "接受到消息并转发到ui线程...");
mlistener.onMsgComing(finalLine);
}
}
});
}
}
public void sendMsg(final String msg){
new Thread(){
@Override
public void run() {
try {
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
bw.write(msg);
bw.newLine();
bw.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
public void onDestroy(){
//所有异常分开保证一个出现问题,其余的可以正常关掉
if(os != null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}if(ls != null){
try {
ls.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(msocket != null){
try {
msocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}