聊天室的原理是这样的,一个客户端与服务器建立通讯成功,即客户端socket连接到服务器的ServerSocket之后,服务器端程序将对应的socket加入到容器中,为每一个socket创建一条线程,服务器端读到客户端发来的信息之后,遍历该容器,给容器中的每个socket发送一次,就完成了所有客户端广播。当客户端离开时,向服务器发送一条断开连接信息,从服务器端断开socket,将容器中的对应socket移除掉。
图:
服务器端Server,java:
accept通信后,为该socket建立一条线程,开启循环读写操作
/**
* Created by konghao on 2017/11/8.
*/
public class Server {
static List<Socket> cons = new LinkedList<Socket>();
private static Socket socket = null;
public static class ServerThread extends Thread{
private Socket s;
public ServerThread(Socket socket){
this.s = socket;
cons.add(s);
}
@Override
public void run(){
System.out.print("新用户加入\n");
System.out.print("当前在线数量:"+cons.size()+"\n");
try{
while(true){
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
String mess;// = br.readLine();
//保存信息
if((mess=br.readLine())!=null) {
if(mess.equals("-用户退出-")){
s.close();
}
System.out.print("客户端:" + mess + "\n");
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
//为了证明是服务器返回的数据,我对mess修改在发送到客户端
//这里修改广播到所有客户端
for (Socket so : cons){
BufferedWriter buffw = new BufferedWriter(new OutputStreamWriter(so.getOutputStream()));
String str = "服务器>>"+mess+"\n";
buffw.write(str);
buffw.flush();
}
//
//单客户端通信
/*
String str = "服务器>>"+mess+"\n";
bw.write(str);
bw.flush();
*/
}
}
}catch (IOException e){
System.out.print("用户退出!\n");
cons.remove(s);
e.printStackTrace();
this.interrupt();
//e.printStackTrace();
}catch (NullPointerException e) {
System.out.print("NullPointerException");
}finally {
try {
s.close();
}catch (IOException e){
System.out.print("IOException-2");
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(30001);
while(true){
Socket s = ss.accept();
new Thread(new ServerThread(s)).start();
}
}
}
安卓客户端:
进入活动时候创建一条线程,完成socket连接,和循环监听接受服务器发来的消息。当发送按钮按下后,向服务器发送消息。退出活动时候,向服务器发送结束通信信息,服务器收到后关闭socket。
ChatRoomActivity.java
public class ChatRoomActivity extends Activity implements View.OnClickListener{
private Socket socket = null;
private EditText edit;
private Button send,disconnect;
private RecyclerView msg_recyclerView;
private MsgAdapter adapter;
private List<Msg> msgs = new ArrayList<Msg>();//存储消息容器
private String name = "";
private static final int UPDATE = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chat_room);
Intent intent = getIntent();
name = intent.getStringExtra("name");
Log.d("孔昊",name);
//从服务器读数进程
new Thread(new Runnable() {
private String msg_get="";
@Override
public void run() {
try {
socket = new Socket();
socket.connect(new InetSocketAddress("公网IP", 30001), 5000);
//input
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
while((msg_get=br.readLine())!=null){
Message message = new Message();
Bundle bundle = new Bundle();
Log.d("孔昊",msg_get);
bundle.putString("msg",msg_get);
message.setData(bundle);
message.what = UPDATE;
handler.sendMessage(message);
}
}catch (IOException e){
e.printStackTrace();
}
}
}).start();
edit = (EditText) findViewById(R.id.edit);
send = (Button) findViewById(R.id.send);
send.setOnClickListener(this);
disconnect = (Button) findViewById(R.id.disconnect);
disconnect.setOnClickListener(this);
msg_recyclerView = (RecyclerView) findViewById(R.id.msg_view);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
msg_recyclerView.setLayoutManager(linearLayoutManager);
adapter = new MsgAdapter(msgs);
msg_recyclerView.setAdapter(adapter);
}
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg){
super.handleMessage(msg);
switch (msg.what){
//更新消息列表
case UPDATE:{
Bundle bundle = msg.getData();
String msg_get = bundle.getString("msg");
Msg msg1 = new Msg(msg_get);
msgs.add(msg1);
adapter.notifyDataSetChanged();
}
break;
}
}
};
//向服务器发数据进程
class SocketThread extends Thread{
private String msg;
//Socket socket;
public SocketThread(String m){
msg = m;
}
@Override
public void run(){
try {
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write(msg);
bw.newLine();
bw.flush();
}catch (IOException e){
e.printStackTrace();
}
}
}
@Override
public void onClick(View v) {
switch(v.getId()){
case R.id.send:{
String s = name +":"+ edit.getText().toString();
edit.setText("");
new Thread(new SocketThread(s)).start();
}
break;
case R.id.disconnect:{
try {
//socket.shutdownOutput();
//socket.shutdownInput();
//socket.close();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("-用户退出-");
bw.newLine();
bw.flush();
//发送message,更新UI
Message message = new Message();
Bundle bundle = new Bundle();
bundle.putString("msg","你已经退出聊天!");
message.setData(bundle);
message.what = UPDATE;
handler.sendMessage(message);
}catch (IOException e){
//Log.d("孔昊","断开连接");
e.printStackTrace();
}
}
break;
}
}
//back事件
@Override
public void onBackPressed(){
try {
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("-用户退出-");
bw.newLine();
bw.flush();
}catch (IOException e){
e.printStackTrace();
}
super.onBackPressed();
}
}
服务器端收到的信息:
另附java控制台作为客户端代码:
/**
* Created by konghao on 2017/11/8.
*/
public class Client {
private static String name;
private static Socket socket;
public static void main(String[] args) {
try {
name = "孔昊";
Socket s = new Socket("112.74.92.125",30001);
socket = s;
System.out.println("客户端IP:"+s.getLocalAddress()+"端口"+s.getPort());
//构建IO流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
//建立键盘输入:
Scanner scanner = new Scanner(System.in);
while(true){
System.out.println("请输入发送消息内容:");
bw.write(name+":"+scanner.nextLine()+"\n");
bw.newLine();
bw.flush();
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
//读取服务器返回的消息数据
System.out.println(s.getInetAddress().getLocalHost()+":"+s.getPort()+">>"+br.readLine());
}
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try{
socket.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
}