首先制作一个客户端,界面如下:

android 聊天界面 android 聊天室_android 聊天界面

使用方法:启动后,首先在登录编辑框输入一个昵称,然后点击登录,上面灰色区域是聊天窗,其中会显示你的登录提示,显示其他人发的消息。在的登录成功后,可以在下面的发送编辑框内编辑你要发的信息,点击发送就可以推送给当前所有登录中的用户,下线的方法就是发送“bye”,之后便不会再接收到其他人的信息。

代码如下:

布局的代码:

MainActivity
packagecom.example.chatroom;importandroidx.appcompat.app.AppCompatActivity;importandroid.os.Bundle;importandroid.os.StrictMode;importandroid.text.method.ScrollingMovementMethod;importandroid.view.View;importandroid.widget.Button;importandroid.widget.EditText;importandroid.widget.TextView;importjava.io.BufferedReader;importjava.io.InputStreamReader;importjava.io.PrintStream;importjava.net.Socket;public class MainActivity extendsAppCompatActivity {private Button send=null;private Button log=null;private String s="";private EditText name=null;private EditText chat=null;private TextView msg=null;private PrintStream out=null;private BufferedReader msgget=null;private Socket client=null;
@Overrideprotected voidonCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);//下面这两句是为了使程序能够在主线程中创建Socket
StrictMode.setThreadPolicy(newStrictMode.ThreadPolicy.Builder()
.detectDiskReads().detectDiskWrites().detectNetwork()
.penaltyLog().build());
StrictMode.setVmPolicy(newStrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects().penaltyLog().penaltyDeath()
.build());
name=(EditText)findViewById(R.id.name);
send=(Button)findViewById(R.id.send);
log=(Button)findViewById(R.id.log);
chat=(EditText)findViewById(R.id.chat) ;
msg=(TextView)findViewById(R.id.msg);
msg.setMovementMethod(newScrollingMovementMethod());
log.setOnClickListener(newView.OnClickListener() {
@Overridepublic voidonClick(View v) {
star();
}
});
send.setOnClickListener(newView.OnClickListener() {
@Overridepublic voidonClick(View v) {//发送信息到服务器
out.println(chat.getText().toString());
chat.setText("");
}
});
}//此函数的作用是连接至服务器并开启一个线程来接收服务器发来的消息
public voidstar(){try{
client=new Socket("10.0.2.2",9090);
msg.append("已连接至服务器\n");
msg.append("已加入聊天 "+"当前身份: "+name.getText().toString()+" \n");
out=newPrintStream(client.getOutputStream());
msgget=new BufferedReader(new InputStreamReader(client.getInputStream(),"UTF-8"));
out.println(name.getText().toString());//这里启动一个新线程来不断监听服务器发来的消息,直到收到“bye”为止
newThread()
{public voidrun(){try{while(true){
s=msgget.readLine();if(s!=""&&s!=null){if(s.equals("bye")){
msg.append("已退出聊天");break;}
msg.append(s+"\n");
}
}
}catch(Exception e)
{
e.printStackTrace();
}
}
}.start();
}catch(Exception e)
{
e.printStackTrace();
System.out.println("连接失败");
}
}
}

客户端逻辑还是很简单的:连接服务器并接收服务器发来的消息,最后把服务器的消息显示在Textview中。

接下来是服务器

服务器是这个项目的难点所在,先梳理一下思路:首先是要利用线程,为每一个用户的连接创建一个新线程,这些线程一对一的去接收来自用户的信息,然后将受到的信息发送到所有客户端,在接收到某用户的“bye”则断开与该用户的连接结束他的线程。

如何管理这些Socket连接就是难点,我使用的方法是创建一个管理线程的工具类,在其中定义好静态方法以及静态变量,我定义了一个List serverlist来存放各个线程的连接,然后在需要把某用户的消息广播时,就遍历 serverlist来把消息发送到所有的用户。在用户下线时,在从中匹配(Socket支持使用"=="进行比较)找到该用户的Socket连接把它关闭并从serverlist中移除。

工具类代码如下:

importjava.io.IOException;importjava.io.OutputStreamWriter;importjava.io.PrintWriter;importjava.io.UnsupportedEncodingException;importjava.net.ServerSocket;importjava.net.Socket;importjava.util.ArrayList;importjava.util.HashMap;importjava.util.List;importjava.util.Map;public classsocketlist {static List serverlist=new ArrayList();//添加新连接
static voidadd(Socket client)
{
serverlist.add(client);
}//广播消息
static void sendall(String s) throwsUnsupportedEncodingException, IOException
{ PrintWriter out= null;for(Socket client : serverlist) {
out=new PrintWriter(new OutputStreamWriter(client.getOutputStream(),"UTF-8"),true);
out.println(s);
}
}//移除目标连接
static void dele(Socket client) throwsIOException
{for(int i=0;i
{if(client==serverlist.get(i)) {
serverlist.get(i).close();
serverlist.remove(i);
}
}
}
}

接着是线程类

客户端登录时会首先发送自己的昵称到服务器,因此在服务器中就把收的到的第一条信息作为用户的昵称,之后便不断监听是相应的用户否有消息发来,有的话就通过工具类广播给所有的用户,收到“bye”就使用工具类移除自己的连接并结束线程。

importjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStream;importjava.io.InputStreamReader;importjava.io.OutputStream;importjava.io.OutputStreamWriter;importjava.io.PrintWriter;importjava.io.UnsupportedEncodingException;importjava.net.Socket;public class netthread extendsThread {privateSocket client;privateString name;int i=0;publicnetthread(Socket socket) {this.client=socket;
}public voidrun()
{
BufferedReader msg= null;
PrintWriter out= null;
String s;try{
out=new PrintWriter(new OutputStreamWriter(client.getOutputStream(),"UTF-8"),true);
msg=new BufferedReader(new InputStreamReader(client.getInputStream(),"UTF-8"));//对缓冲区数据读取
StringBuffer info=newStringBuffer();
info.append(msg.readLine());//接收的数据
s=info.toString();while(!s.equals("bye")) {if(!s.equals("")&&!s.equals("null")){//判断是不是第一条消息,是的话就作为昵称
if(i==0) {name=s;i=1;
socketlist.sendall(name+" 加入聊天室");
System.out.println(name+" 加入聊天室");
}else{
System.out.println(name+":"+s);
socketlist.sendall(name+":"+s);//广播到客户端
}
}
info=newStringBuffer();
info.append(msg.readLine());
s=info.toString();
}socketlist.sendall(name+":"+"bye");
//通知该连接对应的客户端下线
out.println("bye");
socketlist.sendall(name+" 退出聊天室");
System.out.println(name+":"+"bye");
System.out.println(name+" 退出聊天室");
socketlist.dele(this.client);
}catch(IOException e) {
e.printStackTrace();
}finally{//关闭相关资源
try{if (out!=null) out.close();if (msg!=null) msg.close();if (client!=null) client.close();
}catch(Exception e)
{
e.printStackTrace();
}
}
}
}

最后就是主函数了,它要做的就是监听是否有用户连接到服务器,有的话就为该用户启动一条新线程,并将该连接存储到工具类的serverlist中

importjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStreamReader;importjava.io.OutputStreamWriter;importjava.io.PrintWriter;importjava.io.UnsupportedEncodingException;importjava.net.InetAddress;importjava.net.ServerSocket;importjava.net.Socket;public classchatroom {public static void main(String[] args) throwsUnsupportedEncodingException, IOException, InterruptedException {//TODO 自动生成的方法存根
ServerSocket server=new ServerSocket(9090);
Socket client;while(true) {//等待用户连接
client=server.accept();//将连接存储到工具类
socketlist.add(client);//开启一个新线程
netthread thread=newnetthread(client);
thread.start();
InetAddress address=client.getInetAddress();
System.out.println("当前客户端的IP:"+address.getHostAddress());
}
}
}