Android网络聊天室实现过程中遇到的问题及解决

Android网络聊天室实现过程中遇到的问题及解决

服务器端

  • 思路
  1. 创建服务器,绑定端口号
  2. while(true)无限循环不断接收客户端的连接请求
  3. 将各个客户端对应的socket添加到集合,方便统一管理
  4. 为各个客户端的socket开启子线程,实现通信(接收,转发)
  • 结构
    在eclipse中创建两个类:Server—对应服务器;ServerThread—对应子线程,重写run()方法,实现接收和转发

源码

  • Server.java
public class Server {
	private static List<Socket> socket_list = new LinkedList<>();

	public static void main(String[] args) {
		try (
				ServerSocket server = new ServerSocket(8888);
				) {
				//使用try with resources确保出现异常时服务器的关闭
			try {
				String hostAddress = InetAddress.getLocalHost().getHostAddress();
				System.out.println("服务器 " + hostAddress + " 已就绪");
				while (true) {
					Socket socket = server.accept();
					//接收客户端的请求
					socket_list.add(socket);
					//添加到集合
					System.out.println("ip: " + socket.getInetAddress().getHostAddress() + " 加入聊天室");
					new Thread(new ServerThread(socket, socket_list)).start();
					//开启线程
				}
			} catch (UnknownHostException e) {
				System.out.println("找不到主机名称");
				e.printStackTrace();
			}
		} catch (IOException e) {
			System.out.println("服务器端口已被占用,无法建立服务器");
			e.printStackTrace();
		}
	}
}
  • ServerThread.java
public class ServerThread implements Runnable {
	private List<Socket> socket_list = new LinkedList<>();
	private Socket mSocket = null;

	public ServerThread(Socket socket, List<Socket> list) {
		this.mSocket = socket;
		socket_list = list;
		//拿到socket的集合,为转发做基础
	}

	@Override
	public void run() {
	//try with resources确保流的关闭
		try (
				BufferedReader br = new BufferedReader(
						new InputStreamReader(mSocket.getInputStream()));
						//读取文本信息,考虑可以用BufferedReader的readLine()方法读取到换行符
				PrintStream ps = new PrintStream(
						new FileOutputStream("chatting records", true));
						//写出到服务器的文件,做记录
						//println()可以直接写出换行符
		) {
			String content = null;
			while(true) {
				
				while ((content = br.readLine()) != null) {
					
					ps.println(content);				
					System.out.println(content);

					Iterator<Socket> it = socket_list.iterator();
					//使用迭代器,获得每一个socket对象
					while (it.hasNext()) {
						Socket socket = it.next();
						if (socket != mSocket) {
						//自己不需要接收自己的消息
							try {
								PrintStream tps = new PrintStream(socket.getOutputStream());
								tps.println(content);
							} catch (IOException e) {
							//创建流对象失败,意味着此socket已关闭,客户端已下线,直接在集合中移除
								it.remove();
							}
						}
					}
				}
			}
		} catch (IOException e) {
			System.out.println("客户端已断开连接");
			socket_list.remove(mSocket);
			System.out.println("ip: " + mSocket.getInetAddress().getHostAddress() + " 退出了聊天室");
		}
	}
}

重点内容

  • 客户端之间的通信
    服务端接收客户端消息,向客户端发送消息我们在Java网络编程已经有了相关基础.那么我们是怎么实现客户端之间的通信的呢?
    我们可以在服务端创建一个LinkedList集合,将所有与客户端相连接的socket保存起来,在接收到一个socket发送过来的信息后,使用迭代器逐个获取所有的socket对象,一一向每一个socket发送信息,这样就实现了客户端之间的通信

客户端

  • 思路
  1. 登录页面,输入用户名,与服务器建立连接
  2. 聊天页面,接收信息,发送信息,UI更新等操作
  3. 考虑到多个页面都会用到socket,BufferedReader等,创建BaseActivity方便变量访问
    BaseActivity内容
public class BaseActivity extends AppCompatActivity {
    public static Socket socket;
    public static BufferedReader br;
    public static BufferedWriter bw;

    private NotNetworkReceiver receiver;

    public void notNetworkBroadcast(){
        Intent intent = new Intent("com.example.chatroom.NOTNETWORK");
        sendBroadcast(intent);
        //发送广播
    }

    @Override
    protected void onResume() {
        super.onResume();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction("com.example.chatroom.NOTNETWORK");
        receiver = new NotNetworkReceiver();
        registerReceiver(receiver, intentFilter);
        //动态注册广播
    }

    @Override
    protected void onPause() {
        super.onPause();
        if(receiver != null){
            unregisterReceiver(receiver);
            receiver = null;
        }
        //注销广播
    }

    private class NotNetworkReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "无连接,请检查您的网络", Toast.LENGTH_SHORT).show();
        }
    }
}
  • 重点内容
  1. 权限申请
    要与服务器建立连接,需要首先在Manifest获取权限,
    在Manifest中添加
<uses-permission android:name="android.permission.INTERNET" />
  1. 与服务器建立连接
new Thread(new Runnable() {
                       @Override
                       public void run() {
                           try {
                               BaseActivity.socket = new Socket("192.168.1.126", 8888);
                               //a.建立与服务器连接
                               BaseActivity.br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                               BaseActivity.bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
                               ChatActivity.actionStart(LoginActivity.this, userName);
                               finish();
                           } catch (IOException e) {
                               notNetworkBroadcast();
                               //b.发送广播,通过广播接收器显示Toast
                               e.printStackTrace();
                           }
                       }

a. 在Android中,Socket socket = new Socket();需要放在子线程中进行,所以为其开启线程.
b. 与socket不同,Toast只能在主线程中显示,因此用广播来实现Toas显示
3. UI更新
与Toast相同,Ui的更新操作也只能在主线程中操作,但是我们客户端的接收和发送信息肯定是会开启子线程的,那么该如何进行编写?
有两种方法:1.使用Handle完成UI更新;
2.使用runOnUiThread()完成更新,这里我使用的第二种

runOnUiThread(new Runnable() {
                               @Override
                               public void run() {
                                   adapter.notifyDataSetChanged();
                                   //消息更新
                                   msgRecycleView.scrollToPosition(msgList.size() - 1);
                                   //显示最后一行,即最新消息
                               }
                           });

对了,在更新UI之前,记得要将消息列表进行更新,就像这样.

msgList.add(new Msg(content, Msg.TYPE_RECIVED));
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值