在这一块纠结了好久,不知道是不是因为前面漏掉了及时总结的环节,才导致现在的混乱和零散。像是在努力建造一栋大楼,前面一直在捡拾建材,终于着手要开始建造的时候,却发现每一块材料都需要反复雕琢加工,框架和样式都在脑子里,每一步要做什么甚至也一目了然,因为每一步都似曾相识,但每一步又着实走得很艰难。。。虽然举步维艰,但只要坚持,我们就始终都在进步,每一天,哪怕只是填一粒沙,也会有意想不到的收获。接下来我就来讲讲自己一步步蹭过来的通信之路。。。
首先,亮出核心思想——“大局观”!
这是本次挑战过程中我体会最深的一点。依然用建造楼房来说,假如没有设计师的设计图纸,仅凭建筑师天马星空地堆砖头,那么也许做到一半会突然发现哪一部分受到影响无法继续下去,然后就要拆掉产生影响的部分,拆到一半又发现后面还会再用到这里,于是半路停手,继续建造其他部分,做到一半又突然发现少了什么东西,然后这边砌块砖、那边堆块瓦,堆着堆着就不知道自己究竟干了些什么,只得放弃,最终变成烂尾。。。但如果事先经过精心研究和设计,按照整理好的思路一步步进行,那么过程就会变得格外清晰顺畅。可见,整体框架和思路至关重要!
接下来就要着手做我的小小聊天室啦!
分析:若想实现群聊功能,就要有能够处理客户机信息的服务器,然后通过方法调用,将某一客户机发来的消息转发给其他的每一位在线用户。那么首先,我们就要想办法实现一台客户机和服务器之间的通信。这样可以把任务粗略地分成三块:1.服务器;2.客户机;3.建立链接。具体步骤如下:
一.构造服务器和客户机界面,添加主要组件,如:服务器连接按钮、客户机登陆按钮、消息输入框、客户机消息显示框、发送按钮等等。如:服务器界面构造——
package lyd_p130730群聊服务器;
import java.awt.FlowLayout;
import java.awt.Label;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.swing.JTextField;
public class ChatServer {
public void initGUI(){
JFrame jf = new JFrame();
jf.setSize(700,500);
jf.setTitle("服务器");
jf.setResizable(false);
jf.setDefaultCloseOperation(3);
jf.setLayout(new FlowLayout());
//端口连接
Label l = new Label("服务器端口:");
jf.add(l);
JTextField jtf_conn =new JTextField(7);
jf.add(jtf_conn);
JButton jb_conn = new JButton("连接");
jf.add(jb_conn);
//显示消息
JTextArea jta_msg = new JTextArea(20,50);
jta_msg.setEditable(false);
jf.add(jta_msg);
//发送消息
JTextField jtf_msg =new JTextField(40);
jf.add(jtf_msg);
JButton jb_msg = new JButton("发送");
jf.add(jb_msg);
//界面可见
jf.setVisible(true);
//加监听器
ServerLis sl1 = new ServerLis(jtf_conn,jta_msg);
jb_conn.addActionListener(sl1);
ServerLis sl2 = new ServerLis(jtf_conn,jta_msg);
jb_conn.addActionListener(sl2);
}
public static void main(String[] args) {
ChatServer cs = new ChatServer();
cs.initGUI();
}
}
二.关键代码:
//在服务器界面输入端口号后,点击创建,等待客户机连入
ServerSocket server = new ServerSocket(9000);
client = server.accept();
//客户端创建和连接
Socket client = new Socket(localhost,9000);
//从客户机获取输入输出流
DataOutputStream dos = new DataOutputStream(client.getOutputStream());
DataInputStream dis = new DataOInputStream(client.getInputStream());
dis = new DataInputStream(client.getInputStream());
dos = new DataOutputStream(client.getOutputStream());
三.由于实现群聊需要多个客户机同时连入,因此需要启动一个线程,将客户机连接放进线程当中,并在其中定义一个服务器处理客户机的方法。
package lyd_p130730群聊服务器;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import javax.swing.JTextArea;
public class ServerThread extends Thread{
private ServerSocket ss;
private JTextArea jta_msg;
public ServerThread(ServerSocket ss,JTextArea jta_msg){
this.ss = ss;
this.jta_msg=jta_msg;
}
private Thread iothread;
private OutputStream ous;
private InputStream ins;
public void run(){
process();
}
//定义一个处理客户机的方法
public void process(){
while(true){
try {
//等待客户机连入
final Socket client = ss.accept();
System.out.println("等待结束啦!!!");
//创建处理客户机的线程
Thread iothread = new Thread(){
public void run(){
handleClients();
readMsg();
}
//定义一个处理客户机的具体方法
public void handleClients(){
try {
ous = client.getOutputStream();
ins = client.getInputStream();
//读写数据
String s ="Welcome~\r\n";
//获取字节
byte[] data = s.getBytes();
//用输出对象发送数据
ous.write(data);
//强制输出
ous.flush();
//关闭连接
client.close();
} catch (Exception e) {
e.printStackTrace();}
}
//定义一个服务器读取消息的方法
public void readMsg(){
BufferedReader brd = new BufferedReader(new InputStreamReader(ins));
try {
String msg = brd.readLine();
jta_msg.append(msg);
} catch (Exception e) {
e.printStackTrace();}
}
};
} catch (Exception e) {
e.printStackTrace();}
}
}
//定义一个服务器给一个客户机发送消息的方法
public void sendMsg(String msg){
try {
ous.write(msg.getBytes());
ous.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
四.读取和发送消息。
//定义一个服务器给一个客户机发送消息的方法
public void sendMsg(String msg){
try {
ous.write(msg.getBytes());
ous.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
五.实现群聊时,还需要创建一个队列,用以保存每一个创建服务器并连入客户机的线程。
package lyd_p130730群聊服务器;
import java.io.IOException;
public class ThreadArray {
private static String msg;
//定义用于存储线程的队列
public static java.util.ArrayList<ServerThread> array = new java.util.ArrayList<>();
//定义添加线程的方法
public void addThread(ServerThread st){
array.add(st);
System.out.println(st+"上线啦!!!");
}
//定义删除线程的方法
public void removeThread(ServerThread st){
array.remove(st);
System.out.println(st+"要下线啦,886 !!!");
}
}
六.群聊,即服务器读取某一客户机发来的信息,再转发给所有在线用户。因此需要在上面队列类中定义一个群发的方法,即 遍历数组,对每一个Client对象调用sendMsg()方法。
//定义一个将消息发送给所有用户的方法
public static void sendAll(String str){
//遍历数组
for(int i = 0;i<array.size();i++){
ServerThread st = array.get(i);
st.sendMsg(msg);
}
}
至此,聊天室的基本框架就大体完成啦,由于类比较多,涉及到许多传参的问题,因此出现了很多空指针的报错,另外,由于一开始思路不清晰导致代码混乱,使得该项目两次无法继续进行而重新开始,慢慢理清思路的过程中也对通信有了更深入地理解,至于传输图片等功能还有待探索和实践,希望自己能够把握基本框架,带着清晰的思路在通信之路上越走越顺~O(∩_∩)O~