昨天晚上上完了客户端程序的编写,左哥给大家布置了一个作业————编写一个有界面的客户端。
我用今天晚上的时间来把这个作业做完了,整体来说,这个客户端界面不难写,只是其中的逻辑需要去好好理清。
首先,我创建了一个项目,然后我即学即用,把昨天学到的只是运用起来,将代码分成四个包去管理,common包:存放工具类,用于存放一些公用的函数。control包:存放逻辑控制类的包,用于存放程序的核心代码。dao包:用于存放数据或者模型。view包:用于存放可视化界面类的包。
如下图所示:
我把聊天界面客户端的代码框架大致构建了出来,要分为四个类:一个存放在view包里面的MyClientFrame类:用于构建一个聊天界面。
窗口需要有标题,两个标签,三个文本框,两个按钮,一个文本域。
这些参数变量除了两个标签之外,其他的都要传入动作监听器里面去监听的。
创建好的窗口界面:
如下图:
聊天窗口大致需要的功能就这样出来。
窗口出来后就要对按钮,文本框和文本域进行监听。
下一个要编写的类毫无疑问就是监听器类了,监听器类的要执行的任务是监听两个按钮。
第一个链接按钮就要创建一个客户端对象,获取文本框的内容然后输入给Socket对象的构造函数中,这就是链接按钮的功能。
第二个发送按钮要执行的功能是获取文本框内的字符串发送给服务器。
上述就是监听器要执行的监听功能。
编写完了监听器类,第三个类就是客户端类,客户端要做的是创建一个Socket对象,接收服务器的IP和端口号,创建输入输出流,然后用一个线程来执行死循环接收服务器发送过来的信息。
最后一个是工具类,放在common包中去,要实现的是send()和get()函数
send()要实现的功能是从客户端发送信息去服务器中。
get()的功能恰好相反,要从服务器中获取信息。
这就是客户端的框架,框架搭建好了之后,我就开始编写代码了。
以下的图是测试结果:
以上两幅图就是测试结果,测试结果没有让我失望(经过了多番测试和修改程序后),我写的程序可以与我第一款管理软件服务器连接成功(充满了成功的喜悦)。
大家可以看到两个客户端可以通信,服务器发送的系统信息也可以进入两个客户端的聊天框中。
再来一个user5试一试。
加入了一个user5之后也一样可以通信。
以上的多次测试可以证明我这个聊天窗口客户端没有太大的问题了,可以正常使用。
下面是我的代码:
public void initMyClientFrame(){
setTitle("客户端聊天界面");
setSize(500,500);
setLocationRelativeTo(null);
setDefaultCloseOperation(3);
BorderLayout bl = new BorderLayout();//创建一个网格布局对象,设置界面的布局,分为上中下部
ipLabel = new JLabel("IP地址");//创建一个上部面板,存放IP地址和端口的标签和输入框,还有一个按钮
inferLabel = new JLabel("端口号");
ipText = new JTextField(15);
inferText = new JTextField(10);
connetButton = new JButton("连接");
connetButton.setActionCommand("连接");
JPanel upPanel = new JPanel();//将组件逐一添加到上部的面板上,并且把上部的面板添加到界面北面
upPanel.setLayout(new FlowLayout());
upPanel.add(ipLabel);
upPanel.add(ipText);
upPanel.add(inferLabel);
upPanel.add(inferText);
upPanel.add(connetButton);
this.add(upPanel, bl.NORTH);
MsgArea = new JTextArea(15,15);//创建中部聊天区域,并且添加到界面的中部,顺便添加滚动面板
JScrollPane jp = new JScrollPane(MsgArea);
this.add(jp, bl.CENTER);
inputText = new JTextField(37);//创建底部发送信息面板,并且添加到界面的底部
sendButton = new JButton("发送");
sendButton.setActionCommand("发送");
JPanel downPanel = new JPanel();
downPanel.setLayout(new FlowLayout());
downPanel.add(inputText);
downPanel.add(sendButton);
this.add(downPanel, bl.SOUTH);
setVisible(true);//界面可视化
MyListener mt = new MyListener(connetButton,sendButton,
ipText,inferText,inputText,MsgArea);
sendButton.addActionListener(mt);
connetButton.addActionListener(mt);
inputText.addKeyListener(mt);
ipText.addKeyListener(mt);
}
我觉得有计划,把思路理清后,做事的效率可以更加的快,所以我先把图片和框架先摆出来,然后慢慢整理。
下面是我的界面设计图:
public MyListener(JButton connetButton,JButton sendButton,
JTextField ipText,JTextField inferText,
JTextField inputText,JTextArea MsgArea){//构造方法获取需要监听的参数
this.ipText = ipText;
this.inferText = inferText;
this.inputText = inputText;
this.connetButton = connetButton;
this.sendButton = sendButton;
this.MsgArea = MsgArea;
}
public void actionPerformed(ActionEvent e) {
if(e.getActionCommand().equals("连接")){
MyClient mc = new MyClient(MsgArea,ipText,inferText,inputText,this);//创建我的客户端对象
}
if(e.getActionCommand().equals("发送")){
String message = inputText.getText();
Tool.send(ous, message+"\r\n");//发送消息给服务器
MsgArea.append(message+"\r\n");//信息传输到用户端界面
}
}
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == '\n'&&e.getSource()==inputText){
String message = inputText.getText();
Tool.send(ous, message+"\r\n");//发送消息给服务器
MsgArea.append(message+"\r\n");//信息传输到用户端界面
}
if(e.getKeyCode() == '\n'&&e.getSource()==ipText){
MyClient mc = new MyClient(MsgArea,ipText,inferText,inputText,this);//快捷键
}
}
public void keyReleased(KeyEvent e) {
}
public void keyTyped(KeyEvent e) {
}
下一个是客户端类,举一个不太恰当的例子就像航天器和太空站一样对接,客户端最主要的功能就是跟服务器实现对接,首先是建一个Socket对象,接收IP地址和端口号,然后建立输入输出流,进行输入输出操作,C/S之间的接口协议是这个类编写最重要的内容。代码如下:
public void initClient(){
try {
System.out.println("进入了initClient()");//0.0.0.0/0.0.0.0:9090
// int port = Integer.parseInt(inferMsg);
Socket client = new Socket(ipMsg.toString(),9090);//创建一个socket来让服务器接收
ins = client.getInputStream();
ous = client.getOutputStream();
ml.ous = ous;
System.out.println("客户端已经连接上服务器......");
MsgArea.append("客户端已经连接上服务器......\r\n");
String Msg = Tool.get(ins);//接收输入提示:请输入用户名
MsgArea.append(Msg);//在用户界面输出后,用户在聊天信息框上面输入信息
new Thread(){
public void run() {
try{
while(true){
String msg = Tool.get(ins);//死循环来接收服务器的信息
MsgArea.append(msg);
}
}catch(Exception e){
e.printStackTrace();
}
};
}.start();
} catch (Exception e) {
e.printStackTrace();
}
}
这里有一个我没办法解决的问题:
Socket client = new Socket(ipMsg.toString(),9090);
这一句话,前面可以接收一个String类型的IP地址,后面是端口号,但是我没办法通过输入的形式去让socket的构造函数去接收端口号,我在前面试过用:
int port = Integer.parseInt(inferMsg);
inferMsg是端口输入框getText()的值,转化为int port后就传入port给端口,代替9090
但是编译出现错误,说是类型不符合。
MyClient的构造方法是
public MyClient (JTextArea MsgArea,JTextField ipText
,JTextField inferText,JTextField inputText,MyListener ml){
this.MsgArea = MsgArea;
this.ml = ml;
ipMsg = ipText.getText();
inferMsg = ipText.getText();
System.out.println("进入了MyClient()");
initClient();
}
出现的错误是:
这就是30行,提示这行错了。
希望各位大牛可以帮我解答,给我一个解决方案。
还有就是我只是一个大二的学生,希望可以给出一个通俗易懂的答案。
最后一个是工具类,以前发过,不做太多解释了:
public class Tool { /** * 服务器向客户端发送数据 * @param out * @param msg * @throws Exception */ public static void send(OutputStream out,String msg) { byte[] b = msg.getBytes(); try { out.write(b); out.flush(); } catch (IOException e) { e.printStackTrace(); } } /** * 服务器获取客户端输入的数据 * @param ins * @return */ public static String get(InputStream ins)throws Exception{ String msg = ""; int v = ins.read(); while(v!=13){ msg = msg + (char)v; v = ins.read(); if(v == -1){ throw new Exception(); } } msg = msg.trim(); return msg; }
跟上次做的服务器比起来,这次做的时候,我的思路明显清晰多了,上次是遇到一个问题就创建一个类去解决相应的问题,导致我的界面代码居然放在服务器主类中,后来还花了一个多小时再分类成功,真是累死。。。
不过这样翻了错误之后,印象就深刻多了。
这次我先做好框架,然后再往里面去填塞内容,思路清晰,代码简单,效率也高多了。
哈哈,这样不断的总结反省,我相信我效率将会越来越高!
也许我的程序还有不足的地方,明天我再仔细看看吧,看看哪里有bug,再修改一下。