使用了Socket,swing,线程。
原本是只准备写两人间的通信,是后来才想改成这样的,所以代码有点乱。
有个set集合,当服务器群发消息时就要遍历它。
public class Server_ZZ {
public static void main(String[] args) throws IOException {
ServerSocket ss87 = new ServerSocket(10087);
//用来存储连接的Socket
HashSet<Socket> li=new HashSet<>();
//服务器界面
Swing_Server sw=new Swing_Server();
//不停的接受Socket,将其放入一个集合里面
while(true){
Socket s87 = ss87.accept();
li.add(s87);
sw.addSocket(s87, li);
}
}
}
发送消息的功能是在这个类中完成的,通过触发button的点击事件,来发送消息。用线程来接收消息
发送消息的流程为:
通过Socket得到OutputStream os;
os.write()//写入你要发送的消息,要转换成byte[]型;
接受消息:
因为消息是一直要接收的,如果不用线程则进行不了其他操作。RecepteThread类
public class Swing_Server extends JFrame {
HashSet<Socket> li;
Socket s;
OutputStream os;
String ip;//自己的ip地址
String cip;//客户端的IP地址
JTextArea ja1;
public void addSocket(Socket soc,HashSet<Socket> li) throws IOException{
s=soc;//当前集合通信
this.li=li;//socket通信集合
os= s.getOutputStream();//socket中的输出流
ip=InetAddress.getLocalHost().toString();//拿到自己的ip地址
cip=s.getInetAddress().getHostAddress();//拿到对方的ip地址
System.out.println(ip+"自己的ip地址");
System.out.println(cip+"客户端的ip地址");
setTitle("本机主机名/ip地址:"+ip);// 窗体标题
// 当读到消息就把消息放入文本域,如果不用线程就会在这里一直执行
RecepteThread rt = new ServerRecepteThread(s, this.ja1,this.li);
new Thread(rt).start();
}
public Swing_Server() throws IOException {
setDefaultCloseOperation(EXIT_ON_CLOSE);// 关闭窗口时关闭程序
//setTitle("本机主机名/ip地址:"+ip);// 窗体标题
setResizable(false);// 禁止改变窗体大小
setBounds(500, 100, 0, 0);// 窗体坐标
setSize(700, 550);// 窗体大小
setVisible(true);// 窗体可见
setBackground(Color.white);
Container c = getContentPane();// 拿到窗体的容器
c.setLayout(new FlowLayout(FlowLayout.LEFT));
// 消息文本域
ja1 = new JTextArea();
ja1.setEditable(false);// 不可编辑
ja1.setSize(300, 500);// 文本框大小
ja1.setFont(new Font("楷体", Font.PLAIN, 18));// 字体
ja1.setLineWrap(true);// 自动换行
ja1.setBackground(Color.WHITE);
ja1.setRows(17);
ja1.setColumns(67);
// 发送消息文本域
JTextArea ja2 = new JTextArea();
ja2.setSize(100, 500);// 文本框大小
ja2.setFont(new Font("楷体", Font.PLAIN, 16));
ja2.setRows(7);// 行数
ja2.setColumns(75);// 列数
// 发送按钮
JButton jb = new JButton();
jb.setText("发送");
//监听事件 当按下发送键时 发送消息
jb.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
msg(ja1,ip,ja2.getText());// 在自己的屏幕上显示自己发的消息
//写入流
try {
// 服务器自己发送的消息全部发一遍
for (Socket so : li) {
os = so.getOutputStream();
//将ip地址和消息内容用“&”隔开,这意味发送的消息里不能&
String ipmsg=ip+"&"+ja2.getText();
os.write(ipmsg.getBytes());
}
} catch (IOException e1) {
e1.printStackTrace();
}
ja2.setText("");// 清空文本
}
});
// 将组件加入窗体
c.add(ja1);
c.add(ja2);
c.add(jb);
// 刷新
c.validate();
}
// 在自己的屏幕上显示自己发的消息
public static void msg(JTextArea j,String ip,String msg) {
String str = "主机名/ip地址:" +ip+ "\n" + msg + "\n";
j.append(str);
}
}
这个类是继承的RecepteThread,RecepteThread就是我的客户端接收消息线程类,我重写了run方法。我这种写法是不对的,我开头也讲了,临时改的,就偷了点懒,这里应该用接口可能会更好点。
public class ServerRecepteThread extends RecepteThread implements Runnable {
OutputStream os;
HashSet<Socket> li;
ServerRecepteThread(Socket s, JTextArea ja1, HashSet<Socket> li) throws IOException {
super(s, ja1);
this.li = li;
this.os = s.getOutputStream();
}
@Override
public void run() {
try {
int len;
byte[] bys = new byte[1024];
bi = s.getInputStream();
String read;
while (true) {
// 接受消息
len = bi.read(bys);
read = new String(bys, 0, len);
// 写入消息文本域ja1
ja1.append("ip地址:" + cip + "\n" + read + "\n");
// 服务器接收到客户端消息,就除该客户端都发送一遍
for (Socket so : li) {
if (!so.equals(s)) {
os = so.getOutputStream();
String reads = new String(cip + "&" + read);
os.write(reads.getBytes());
}
}
// 如果接受到“gg” 就结束
if (read.equals("gg")) {
s.close();
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class RecepteThread implements Runnable {
Socket s;
JTextArea ja1;
String cip;
InputStream bi;
socket 显示文本框
RecepteThread(Socket s, JTextArea ja1) {
this.s = s;
this.ja1 = ja1;
// 对方的ip地址
this.cip = s.getInetAddress().getHostAddress();
}
@Override
public void run() {
try {
int len;
byte[] bys = new byte[1024];
bi = s.getInputStream();
String read;
while (true) {
// 接受消息
len = bi.read(bys);
read = new String(bys, 0, len);
// 用&分割
String[] reads = read.split("&");
// 写入消息文本域ja1
ja1.append("ip地址:" + reads[0] + "\n" + reads[1] + "\n");
// 如果接受到“gg” 就结束
if (read.equals("gg")) {
s.close();
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class Client {
public static void main(String[] args) throws UnknownHostException, IOException {
Socket s87 = new Socket(InetAddress.getByName(/*服务器的IP地址 字符串*/), 10087);
System.out.println("Link start");
new Swing_Client(s87);
}
}
这个类其实应该和服务端界面类,抽象后继承同一个父类,因为这两者有很多相同的代码,仅接收消息和发送消息的操作不同,界面设计是一样的。
public class Swing_Client extends JFrame {
public Swing_Client(Socket soc) throws IOException {
Socket s=soc;//当前集合通信
OutputStream os= s.getOutputStream();//socket中的输出流
String ip=InetAddress.getLocalHost().toString();//拿到自己的ip地址
String cip=s.getInetAddress().getHostAddress();//拿到对方的ip地址
System.out.println(ip+"自己的ip地址");
System.out.println(cip+"对方的ip地址");
setDefaultCloseOperation(EXIT_ON_CLOSE);// 关闭窗口时关闭程序
setTitle("本机主机名/ip地址:"+ip);// 窗体标题
setResizable(false);// 禁止改变窗体大小
setBounds(500, 100, 0, 0);// 窗体坐标
setSize(700, 550);// 窗体大小
setVisible(true);// 窗体可见
setBackground(Color.white);
Container c = getContentPane();// 拿到窗体的容器
c.setLayout(new FlowLayout(FlowLayout.LEFT));
// 消息文本域
JTextArea ja1 = new JTextArea();
ja1.setEditable(false);// 不可编辑
ja1.setSize(300, 500);// 文本框大小
ja1.setFont(new Font("楷体", Font.PLAIN, 18));// 字体
ja1.setLineWrap(true);// 自动换行
ja1.setBackground(Color.WHITE);
ja1.setRows(17);
ja1.setColumns(67);
//当读到消息就把消息放入文本域,如果不用线程就会在这里一直执行
RecepteThread rt=new RecepteThread(s,ja1);
new Thread(rt).start();
// 发送消息文本域
JTextArea ja2 = new JTextArea();
ja2.setSize(100, 500);// 文本框大小
ja2.setFont(new Font("楷体", Font.PLAIN, 16));
ja2.setRows(7);// 行数
ja2.setColumns(75);// 列数
// 发送按钮
JButton jb = new JButton();
jb.setText("发送");
//监听事件 当按下发送键时 write入流
jb.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
msg(ja1,ip,ja2.getText());// 发送消息
//写入流
try {
os.write(ja2.getText().getBytes());
} catch (IOException e1) {
e1.printStackTrace();
}
ja2.setText("");// 清空文本
}
});
// 将组件加入窗体
c.add(ja1);
c.add(ja2);
c.add(jb);
// 刷新
c.validate();
}
public static void msg(JTextArea j,String ip,String msg) {
String str = "主机名/ip地址:" +ip+ "\n" + msg + "\n";
j.append(str);
}
}
带有主机名就是自己发的消息。服务器和客户端页面一样。要先开服务器,再开客户端。
代码有很多需要优化的地方,比如我上面提到的代码的复用,不能发送&,以及关闭窗口不能同时关闭线程问题,所以通过关闭窗口来下线再上线会报错。还有可以增加的功能:比如自己发的消息右对齐,别人的左对齐;右边空出来的可以做成 上线人列表,可以发起单独聊天等。下次在改! 下次!下次!咕咕咕