Java实现网络编程中TCP的C-S通信(带界面)

一、核心代码分析说明

(1)Sever类——connect()方法:在建立与客户端的连接后,首先需要开启多线程,主线程是显示界面,新开的线程是对数据的处理,包括对客户信息的存储以及对客户发送过来信息的处理。

public static void connect() {
    try (ServerSocket server = new ServerSocket(PORT)) {
	while (true) {
	    try {
                Socket connection = server.accept();
		new Thread() {// 多线程
		    @Override
		    public void run() {
			String client_mes = connection.getInetAddress().getHostName() + ":" + connection.getPort();
			clientMap.put(client_mes, connection);// 把客户信息用hashMap存储起来 方便查找
			item[count++] = client_mes;// 连接上的客户端的列表
			String[] items = new String[count];
			for (int k = 0; k < count; k++)
			    items[k] = item[k];
			list.setListData(items);// 把列表放入组件中显示
			lblNewLabel_1.setText(count + " client connect");// 更改界面中连接的客户数量
			mes_receive(connection, client_mes);// 对客户发送过来信息的处理
		    }
		}.start();
	    } catch (IOException ex) {
	    }
	}
    } catch (Exception ex) {
	System.out.println("connect error");
	System.err.println(ex);
    }
}

(2)Sever类——mes_receive()方法:用一个while来不断的判断发送过来的信息是属于哪一类的,是talk、login、还是logout的,根据此进行不同的操作。

while (true) {
    request = buffer_reader.readLine();
    System.out.println("request: " + request + "\n");
    if (request.startsWith("talk")) {
	request = request.substring(4);
	receive_mes_textArea.append(client_mes + " : " + request + "\n");
    } else if (request.startsWith("logout")) {// 客户端退出登录
	System.out.println("clients: " + clients);
	request = request.substring(6);
	clients = request;
	clientMap.remove(clients);
	for (int k = 0; k < count; k++) {
	if (item[k].equals(clients)) {
	    for (int j = k; j < count; j++)
	    item[j] = item[j + 1];
	    item[count] = null;
	    count--;
	    System.out.println("count0: " + count);
	    break;
	}
    }
    // for(String s:item)
    // System.out.println(s);
    String[] items = new String[count];
    for (int k = 0; k < count; k++)
	items[k] = item[k];
    list.setListData(items);
    receive_mes_textArea.append(clients + " is logout...\n");
    System.out.println("count: " + count);
    lblNewLabel_1.setText(count + " client connect");
}


(3)Sever类——按钮触发事件:当按了发送信息(Send Message)的按钮后,就根据选择的socket来进行消息的传送。

JButton btnNewButton = new JButton("Send Message");
btnNewButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
	try {
	    String new_mes = Send_mes_textArea.getText();
	    for (int i = 0; i < client.size(); i++) {
		Socket connection = clientMap.get(client.get(i));
		receive_mes_textArea.append("I say to " + client.get(i) + " : " + new_mes + "\n");
		out = new OutputStreamWriter(connection.getOutputStream());
		out.write("talk" + new_mes + "\r\n");
		out.flush();
	    }
	} catch (Exception e1) {
	    System.out.println("bt error");
	    e1.printStackTrace();
	}
    }
});
btnNewButton.setBounds(130, 297, 122, 23);
contentPane.add(btnNewButton);

(4)Sever类——鼠标点击触发事件:这个算是相当重要的一个东西了,通过鼠标点击,确定选择要发送消息的客户端的地址,并把这些地址存储在ArrayList数组中,在后续的发送按钮触发事件的处理中相当有用。

JScrollPane scrollPane = new JScrollPane();
scrollPane.setBounds(20, 119, 232, 69);
list = new JList<String>();
contentPane.add(scrollPane);
list.addMouseListener(new MouseAdapter() {
    public void mouseClicked(MouseEvent e) {
	client.clear();
	client = list.getSelectedValuesList();
	for (String s : client) {
		System.out.println(s + "\n");
	}
    }
});
scrollPane.setViewportView(list);

(5)Sever类——窗口关闭的监听:当服务器的窗口关闭后,如果有客户端还连接着,就会给这些客户端发送logout的消息,然后客户端就会开始不断的监听,知道有服务器再次连接。

this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
    Socket s = null;
    if (count == 0)
	System.exit(0);
    try {
	for (int i = 0; i < count; i++) {
	    String client_mess = item[i];
	    s = clientMap.get(client_mess);
	    out = new OutputStreamWriter(s.getOutputStream());
	    out.write("logout" + "\r\n");
	    out.flush();
	}
    } catch (Exception ex) {
	System.err.println(ex);
    }
    System.exit(0);
    }
});

(6)Client类——main和connect()方法:main中的多线程,实现边处理界面边处理数据。connect()则负责连接,其中一个比较重要的一点是,在catch中,除了要捕捉异常并抛出外,还要重新调用connect的这个连接方法,为的就是让与服务器断开的客户端能够不断的监听,行程一种递归的机制,然后能在服务器出现时再次与它连上。(这是能让客户端与服务器断开后能重新连接上的重要的一步!!!)

public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {
	public void run() {
	    try {
		Client frame = new Client();
		frame.setTitle("Client");
		frame.setVisible(true);
		new Thread() {
		    @Override
		    public void run() {
			connect();
			text_content();
		    }
		}.start();
	    } catch (Exception e) {
		e.printStackTrace();
	    }
	}
    });
}

public static void connect() {
    try {
	socket = new Socket("127.0.0.1", 10055);
	out = new OutputStreamWriter(socket.getOutputStream());
	in = socket.getInputStream();
    } catch (IOException ex) {
	System.err.println(ex);
	lblNewLabel_1.setText("try connect...");
	connect();
	text_content();
    }
}

(7)Client类——按钮触发事件:当按了发送信息(Send Message)的按钮后,就消息传送给服务器。

JButton btnNewButton = new JButton("Send Message");
btnNewButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        String new_mes = Send_mes_textArea.getText();
	receive_mes_textArea.append("I say to server : " + new_mes + "\n");
	try {
	    out.write("talk" + new_mes.toString() + "\r\n");
	    out.flush();
	} catch (IOException ex) {
	    System.err.println(ex);
	}
    }
});
btnNewButton.setBounds(130, 297, 122, 23);
contentPane.add(btnNewButton);

(8)Client类——窗口关闭的监听:当服务器的窗口关闭后,就会给连接着的服务器发送logout的消息,让服务器知道该客户端断开了连接,然后就可以重新处理界面中显示的列表信息。

this.addWindowListener(new WindowAdapter() {
    public void windowClosing(WindowEvent e) {
	try {
	    String messs = socket.getInetAddress().getHostName() + ":" + socket.getLocalPort();
	    out.write("logout" + messs +"\r\n");
	    out.flush();
	} catch (IOException ex) {
	    System.err.println(ex);
	}
	System.exit(0);
    }
});

(9)Client类——text_content()方法:对服务器发送过来的消息的接收并分析是talk还是logout消息,并让消息在界面中显示。其中该方法跟connect中一样重要的是,在捕捉并抛出异常后还需要对connect()和text_content()两个方法进行递归,以不断地监听服务器。

扫描二维码关注公众号,回复: 5121657 查看本文章
public static void text_content() {
    try {
	lblNewLabel_1.setText("Connecting...");
	// receive_mes_textArea.append("Connect with Server...\n");
	while (true) {
	    byte[] b = new byte[100];
	    int len = in.read(b);// m为实际读取的字节的数量
	     if (len != 0) {
		String rece = new String(b, 0, len);
		if (rece.startsWith("talk"))
		    receive_mes_textArea.append("server say : " + rece.substring(4) + "\n");
		else if (rece.startsWith("logout")) {
		    receive_mes_textArea.append("out of connecting with server...\n");
		}
	    }
	}
    } catch (Exception ex) {
	System.err.println("与服务器断开连接 " + ex);
	lblNewLabel_1.setText("try connect...");
	connect();
	text_content();
    }
}


二、总结

1、我觉得这次的实验有几个关键点,把根源搞清楚就能很快且清晰的做完这个实验。

(1)客户端的信息存放问题:利用HashMap<String,Socket>(全局变量)来存储。

(2)客户端怎么与不断监听才能与服务器建立连接:利用类似于递归的方法,在连接connect()方法中的catch再次调用自己,知道能够连接上为止。

(3)如何给选择的客户端发送消息:利用鼠标点击事件,把点击后的值全部存储在ArrayList数组(全局变量)中,然后在发送按钮的触发事件中调用。

(4)对关闭窗口的监听:利用windowListener()

(5)多线程:为了可以边处理界面信息,边处理数据!!!

2、在这个实验过程中遇到一些应该注意的小错误如下:

(1)mouseClicked鼠标点击事件时,写成了MouseClicked,但eclipsewarning,不报错,而且错误信息也很模棱两可,就很苦恼了,让我找错找了很久。

(2)catch中最好定义Exception e,而不是IOException e,否则一些非相关性的错误不会显示,会在找错上花费时间。

(3)图形界面的方法调用和普通的方法的调用,要注意顺序,或者看是否需要用到多线程,否则很容易因为其中一个方法阻塞所以另一个方法没办法正常调用。

3、收获吧:

(1)对多线程有了更深了解

(2)界面方面,这次用的是Jframe,其中JScrollPaneJList的结合是新学习到的东西。

(3)其实整个实验过程,动手做一做,学到的东西还是不少的,关键还是要学会去总结,还有做的时候把思路理清,理解清楚在做,不然就会变成在bug中改bug,然后改都改不完了。

4、参考链接

https://blog.csdn.net/qq_37267015/article/details/54311188


猜你喜欢

转载自blog.csdn.net/zhsihui429/article/details/79931913