20130726 通信初步——群聊聊天室的创建
最近一直在写通信部分,虽然还有好多功能没有实现,但自我安慰了一下,起码有一部分功能还是实现了的。
也许是太久没有用过脑子了,感觉脑子有点不灵光,反应有点慢,不过,我会尽量跟上大家的脚步的。
下面是我做出来的一个简单的群聊聊天室:
1、首先,我先做了一个服务器的界面,通过在界面上添加按钮的过程,清楚这个聊天室需要有一些什么样的功能。其中,画图功能还没有实现,目前正在写这一部分。
package dyh20130726Frame;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.JTextField;
public class serverFrame extends JFrame {
private static final long serialVersionUID = 1L;
public static void main(String[] args) {
//实例化一个类的对象
serverFrame sFrame = new serverFrame();
sFrame.initGUI();
}
public void initGUI(){
//设置窗体的属性
this.setTitle("服务器");
this.setSize(600, 600);
this.setDefaultCloseOperation(3);
this.setLayout(new FlowLayout());
JLabel jla1 = new JLabel("端口号");
JTextField jtf1 = new JTextField(5);
JButton jbu1 = new JButton("确定");
JLabel jla2 = new JLabel("消息栏");
JTextField jtf2 = new JTextField(10);
JButton jbu2 = new JButton("发送");
JPanel jpa2 = new JPanel();
jpa2.setPreferredSize(new Dimension(550,290));
jpa2.setBackground(Color.GRAY);
//设置面板上的按钮
JButton jbu3 = new JButton("画线");
JButton jbu4 = new JButton("画圆");
//将标签添加到面板上
jpa2.add(jbu3);
jpa2.add(jbu4);
JTextArea sjte = new JTextArea();
sjte.setPreferredSize(new Dimension(550,200));
// System.out.println("显示框的大小变化了");
this.add(jla1);
this.add(jtf1);
this.add(jbu1);
this.add(jla2);
this.add(jtf2);
this.add(jbu2);
this.add(sjte);
this.add(jpa2);
this.setVisible(true);
//创建监听器的对象
serverListener ac = new serverListener(jtf1, jtf2, sjte);
//将监听器添加到事件源上
jbu1.addActionListener(ac);
jbu2.addActionListener(ac);
}
}
添加按钮时要注意的是,要写明每一个按钮的作用,否则,到后来进行调用传参时,有可能会弄混淆。
2、界面完成后,接下来就是要实现每个按钮的功能了。先是“确定”按钮:确定按钮的功能是,一旦点击“确定”按钮,即在指定端口上创建一个服务器。
String str = e.getActionCommand();
if (str.equals("确定")){
int port = Integer.parseInt(jtf1.getText());
try {
//创建一个指定端口上的服务器
ServerSocket server = new ServerSocket(port);
System.out.println("服务器创建成功!");
//创建一个线程对象
serverThread sThread = new serverThread(server, sjte);
sThread.start();
System.out.println("服务器线程启动了!");
} catch (IOException e1) {
e1.printStackTrace();
}
}
3、服务器创建成功后,要让服务器处于等待状态,以等待客户机来连接。但这一等待过程会发生阻塞,假如一直没有客户机来连接,程序就会卡在发生阻塞的位置,无法就行下去,因此,要将这一部分写在一个线程中,再对线程进行调用,这样就可以避免这种情况的发生。一下是服务器端的第一个线程:
/**
* 服务器进入阻塞状态,等待客户机来连
* @param sjte
*/
public void waiting(JTextArea sjte){
while(true){
Socket socket;
try {
//让服务器进入阻塞状态
socket = server.accept();
System.out.println("进入了一个客户机连接:"+socket.getRemoteSocketAddress().toString());
//创建一个线程对象
serverThread2 ct = new serverThread2(socket, sjte);
ct.start();
serverList.add(ct);
System.out.println("启动线程处理连接对象成功");
} catch (IOException e) {
e.printStackTrace();
}
}
}
4、客户机与服务器连接成功后,服务器就要从客户机端读取消息,这一过程中,服务器依然充当了一个被动的角色。假如客户机与服务器连接成功,但客户机一直没有给服务器发送消息,服务器依然会处在阻塞状态,为解决这一问题,我将服务器读取客户机发来的消息的方法,写在了另一线程中。以下就是服务器端的第二个线程:
/**
* 客户机与服务器连接成功后,服务器循环读取客户机发来的内容,
* 并将其接收到的内容发送给客户机
*/
public void work(){
try {
//得到一个输入输出流对象
ous = client.getOutputStream();
ins = client.getInputStream();
//表明客户端与服务器连接成功
String s = "欢迎进入聊天室!"+"\r\n";
//用输出对象发送数据
ous.write(s.getBytes());
//强制输出
ous.flush();
//调用读取字符串的方法,读取客户机发来的信息
String inputs = getString(ins);
while(!inputs.equals("exit")){
System.out.println("服务器接受的内容是:\r\n"+inputs);
sjte.append("客户机发来消息:"+inputs+"\r\n");
String str = "客户端输入的内容是:\r\n"+inputs+"\r\n";
serverList.sendMas(str);
//客户机下一次输入
inputs = getString(ins);
}
//客户机输入内容为“exit”时进行该操作
String str2 = "再见!\r\n";
serverList.sendMas(str2);
client.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 服务器读取从客户机发来的消息的方法
* @param ins 输入流
* @return 客户机发送给服务器的字符串
*/
public String getString(InputStream ins){
//创建一个字符串缓冲区
StringBuffer strMas = new StringBuffer();
int nRead = 0;
while (nRead!= 13){
try {
nRead = ins.read();
//将字节添加到字符串中
strMas.append((char)nRead);
} catch (IOException e) {
e.printStackTrace();
}
}
//将读到的字节组转为字符串,并调用trim去掉尾部的空格
String inputs = strMas.toString().trim();
System.out.println("得到的字符串是:"+inputs);
return inputs;
}
5、群聊,顾名思义,就是有很多客户端进行聊天,这样服务器就要将它读到的每一个客户端发来的消息,再发送给每一个客户端。这样,我用一个队列来存储每一个客户端的消息,而客户端一旦与服务器相连,就会启动一个线程,线程中有读取消息的方法,因此,队列中实质存储的内容是线程。一旦启动一个线程,就将其添加到队列中,然后通过循环遍历队列,将队列中的内容发送给每一个客户端。以下是服务器端的队列:
//创建一个队列,用于存储线程
private static List<serverThread2> ctList = new ArrayList<serverThread2>();
//不能在其他类中再创建该对象
// private serverList(){}
//向队列中添加线程对象的方法
public static void add(serverThread2 ctThread){
ctList.add(ctThread);
}
//遍历队列,将队列中存储的线程对象还给线程,并将信息发送出去
public static void sendMas(String mas2){
for(int i=0; i<ctList.size();i++){
serverThread2 ct = ctList.get(i);
ct.writeMas(mas2+"\r\n");
System.out.println("服务器发送消息:"+mas2);
}
}
6、服务器暂时告一段落,下面就是客户端的内容。
首先仍然是要有一个界面,这一部分与服务器端相类似,在此处就不再赘述。
7、客户机端要实现的功能有连接服务器、发送消息、接收(读取)消息。
以下是客户端连接服务器及接收服务器发来的消息的部分:
String str = e.getActionCommand();
if (str.equals("连接")){
int port = Integer.parseInt(jtf1.getText());//端口号
String string = jtf2.getText();//IP
try {
//创建一个连接到服务器端的Socket对象
Socket socket = new Socket(string,port);
System.out.println("客户端与服务器连接成功!");
//得到输入输出流对象
ins = socket.getInputStream();
ous = socket.getOutputStream();
//读取一行字符串
brd = new BufferedReader((new InputStreamReader(ins)));
//实例化一个客户端线程对象
clientThread cThread = new clientThread( ous, ins, brd, jte);
cThread.start();
} catch (Exception e1) {
e1.printStackTrace();
}
}
if(str.equals("发送")){
String string2 = jtf3.getText();//获取消息栏中的内容
sendMsg(string2);
}
}
/**
* 客户端发送信息的方法
*/
public void sendMsg(String msg){
try {
msg+="\r\n";
ous.write(msg.getBytes());
ous.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
由于客户机在等待服务器发来消息时会发生阻塞,因为客户机并不知道服务器什么时候才会发来消息,在等待的过程中就会发生阻塞,因此,客户机读取服务器发送来的消息的方法要写在一个线程中,然后对线程进行调用。一下是客户机端读取消息的线程:
/**
* 从服务器中读取消息,这个方法会阻塞,必须在独立线程中
*/
public void readFromServer(){
String inputs;
try {
BufferedReader brd = new BufferedReader(new InputStreamReader(ins));
//逐行读取服务器中发来的消息
inputs = brd.readLine();
System.out.println("服务器中发来的inputs为:"+inputs);
while (!inputs.equals("exit")){
System.out.println("服务器发来消息:"+inputs);
jte.append("服务器发来消息:"+inputs+"\r\n");
inputs = brd.readLine();
}
} catch (IOException e) {
e.printStackTrace();
}
}
在写代码时,一定要注意细节问题,可能一个小小的错误,就会影响到整个程序的运行。总之,就是要细心,细心,再细心!