JAVA用Socket实现双人聊天室

以下仅为个人学习笔记

流程

连接:客户端A→本地服务器←客户端B
发送信息:A/B→输出流→服务器→输出流→A/B
接收消息:服务器→输入流A/B
显示:A/B→GUI界面


服务器

解析

创建两个ServerSocket对象,该对象将不断监听特定端口,直至连接。
这里手动传输端口号至args变量中。

ServerSocket server1 = new ServerSocket(Integer.parseInt(args[0]));
		ServerSocket server2 = new ServerSocket(Integer.parseInt(args[1]));
		Socket client1 = server1.accept();
		Socket client2 = server2.accept();

分别创建来自两个端口的输入/输出流。
通过对输入流的检查选择是否需要写如A/B中。

如何检查

  1. 初始化String变量in_str为null
  2. 调用availabel()方法,若返回值不为0,则读取数据至in_str。
    方法介绍在下面。
  3. 判断in_str,若不为null,向A/B写入数据
  		if(in.available() != 0)
  			myWin.visual_Area.append(in.readUTF() + "\n");
  			
  		//string为编辑区内容,默认为
  		if(myWin.string != null) {
  			out.writeUTF(myWin.string);
  			myWin.visual_Area.append("You:" + myWin.string + "\n");
  			myWin.string = null;
  		}

available方法介绍

available方法将返回能够被读取(read)得字节数,若不为0,则读取。

官方文档介绍:

Returns an estimate of the number of bytes that can be read (or skipped over) from this input stream without blocking, which may be 0, or 0 when end of stream is detected. The read might be on the same thread or another thread. A single read or skip of this many bytes will not block, but may read or skip fewer bytes.
Note that while some implementations of InputStream will return the total number of bytes in the stream, many will not. It is never correct to use the return value of this method to allocate a buffer intended to hold all data in this stream.

A subclass’s implementation of this method may choose to throw an IOException if this input stream has been closed by invoking the close() method.

The available method of InputStream always returns 0.

This method should be overridden by subclasses.

Returns:
an estimate of the number of bytes that can be read (or skipped over) from this input stream without blocking or 0 when it reaches the end of the input stream.

需要注意的是InputStream必定返回0。

服务器完整代码

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class receiveServer {
	public static void main(String[] args) throws Exception {
		ServerSocket server1 = new ServerSocket(Integer.parseInt(args[0]));
		ServerSocket server2 = new ServerSocket(Integer.parseInt(args[1]));
		Socket client1 = server1.accept();
		Socket client2 = server2.accept();
		while(true) {
			DataInputStream in1 = new DataInputStream(client1.getInputStream());
			DataOutputStream out1 = new DataOutputStream(client1.getOutputStream());
			
			DataInputStream in2 = new DataInputStream(client2.getInputStream());
			DataOutputStream out2 = new DataOutputStream(client2.getOutputStream());
			System.out.println("Connect Already");//输出至控制台已确认程序正常进行
			String in_str1 = null;
			if(in1.available() != 0)
				in_str1 = in1.readUTF();
			String in_str2 = null;
			if(in2.available() != 0)
				in_str2 = in2.readUTF();
			if(in_str1 != null)
				out2.writeUTF(in_str1);
			if(in_str2 != null)
				out1.writeUTF(in_str2);
		}
	}
}

客户端

整体为一个类

属性:

  • 窗口。我设为(815,600),空布局。
  • 消息区和编辑区(其实就是JTextArea), 消息区为不可编辑,滚动条常在。
  • 标签,用于显示编辑区字数。
  • 一个用于计数的int变量sumCount和保存发送消息的String变量string中

方法:

  • 构造方法,用于初始化界面
  • 更改标签内容
class UI{
  JFrame win = new JFrame("ChatRoom");
  JTextArea edit_Area;
  JTextArea visual_Area;
  JLabel wordCount;
  int sumCount;
  String string = null;
}

构造方法中:

		//编辑区
		edit_Area = new JTextArea();
		edit_Area.setBounds(0, 400, 800, 100);
		edit_Area.setLineWrap(true);
		edit_Area.addKeyListener(new KeyListener() {
			public void keyPressed(KeyEvent e) {
			//当按下按键,返回长度,
				sumCount = edit_Area.getText().length();
				//规定最大长度不能超过700
				if(sumCount > 700)
					edit_Area.setEditable(false);
				else
					edit_Area.setEditable(true);
			}

			@Override
			public void keyTyped(KeyEvent e) {
				// TODO Auto-generated method stub
			}

			@Override
			public void keyReleased(KeyEvent e) {
			//同上
				sumCount = edit_Area.getText().length();
			}
		});
  1. sumCount = edit_Area.getText().length()为什么写两次?
    答:只有keyPressed时,按下时获取的长度是在字符出现前,比如按下a,长度应为1,但返回的是没有字符时的0。
    只有keyReleased时,按住键输入时不会返回长度。
  2. 那为什么不写在keyTyped里
    答:尝试结果与只有keyPressed时一致

发送按钮

点击按钮后,将编辑区内容保存到用于保存到string并清空编辑区

		//发送按钮
		JButton send_button = new JButton("发送(C)");
		send_button.setBounds(700, 500, 100, 50);
		//设置快捷键(Alt + C)
		send_button.setMnemonic(KeyEvent.VK_C);
		send_button.setDisplayedMnemonicIndex(3);
		send_button.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				string = edit_Area.getText();
				edit_Area.setText("");
				sumCount = 0;
			}
		});

类完整代码

class UI{
	JFrame win = new JFrame("ChatRoom");
	JTextArea edit_Area;
	JTextArea visual_Area;
	JLabel wordCount;
	int sumCount;
	String string = null;
	public UI() {
		//创建窗口
		win = new JFrame("ChatRoom");
		win.setLayout(null);
		win.setSize(815, 600);
		win.setResizable(false);
		win.setLocationRelativeTo(null);
		win.setDefaultCloseOperation(win.EXIT_ON_CLOSE);
		
		//消息区
		visual_Area = new JTextArea();
		visual_Area.setLineWrap(true);
		visual_Area.setEditable(false);
		
		JScrollPane sp_visual_Area = new JScrollPane(visual_Area);//滚动条(面板)
		sp_visual_Area.setVerticalScrollBarPolicy(sp_visual_Area.VERTICAL_SCROLLBAR_ALWAYS);
		sp_visual_Area.setBounds(0, 0, 800, 400);
		
		//编辑区
		edit_Area = new JTextArea();
		edit_Area.setBounds(0, 400, 800, 100);
		edit_Area.setLineWrap(true);
		edit_Area.addKeyListener(new KeyListener() {
			public void keyPressed(KeyEvent e) {
				sumCount = edit_Area.getText().length();
				if(e.getKeyChar() == KeyEvent.VK_BACK_SPACE) {
					edit_Area.setEditable(false);
				}
				if(sumCount > 700)
					edit_Area.setEditable(false);
				else
					edit_Area.setEditable(true);
			}

			@Override
			public void keyTyped(KeyEvent e) {
				// TODO Auto-generated method stub
				
			}

			@Override
			public void keyReleased(KeyEvent e) {
				sumCount = edit_Area.getText().length();
				
			}
		});
		
		//计数标签
		wordCount = new JLabel("0/700", JLabel.LEFT);
		wordCount.setBounds(0, 500, 50, 50);
		
		//发送按钮
		JButton send_button = new JButton("发送(C)");
		send_button.setBounds(700, 500, 100, 50);
		send_button.setMnemonic(KeyEvent.VK_C);
		send_button.setDisplayedMnemonicIndex(3);
		send_button.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				string = edit_Area.getText();
				edit_Area.setText("");
				sumCount = 0;
			}
		});
		
		win.add(sp_visual_Area);
		win.add(edit_Area);
		win.add(send_button);
		win.add(wordCount);
		
		win.setVisible(true);
	}
	
	//修改标签
	void wordCounter() {
		wordCount.setText(sumCount + "/700");
	}
}

客户端完整代码

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;

import javax.swing.*;

public class sentUI {
	public static void main(String[] args) throws Exception{
		UI myWin = new UI();
		DataOutputStream out = null;
		DataInputStream in = null;
		try {
			Socket client = new Socket(args[0], Integer.parseInt(args[1]));
			out = new DataOutputStream(client.getOutputStream());
			in = new DataInputStream(client.getInputStream());
		} catch (Exception e) {
			e.printStackTrace();
		}
		while(true) {
			myWin.wordCounter();
			System.out.println("程序运行中");
			if(in.available() != 0)
				myWin.visual_Area.append(in.readUTF() + "\n");
				
			if(myWin.string != null) {
				out.writeUTF(myWin.string);
				myWin.visual_Area.append("You:" + myWin.string + "\n");
				myWin.string = null;
			}
		}
	}
}

class UI{
	JFrame win = new JFrame("ChatRoom");
	JTextArea edit_Area;
	JTextArea visual_Area;
	JLabel wordCount;
	int sumCount;
	String string = null;
	public UI() {
		//创建窗口
		win = new JFrame("ChatRoom");
		win.setLayout(null);
		win.setSize(815, 600);
		win.setResizable(false);
		win.setLocationRelativeTo(null);
		win.setDefaultCloseOperation(win.EXIT_ON_CLOSE);
		
		//消息区
		visual_Area = new JTextArea();
		visual_Area.setLineWrap(true);
		visual_Area.setEditable(false);
		
		JScrollPane sp_visual_Area = new JScrollPane(visual_Area);//滚动条(面板)
		sp_visual_Area.setVerticalScrollBarPolicy(sp_visual_Area.VERTICAL_SCROLLBAR_ALWAYS);
		sp_visual_Area.setBounds(0, 0, 800, 400);
		
		//编辑区
		edit_Area = new JTextArea();
		edit_Area.setBounds(0, 400, 800, 100);
		edit_Area.setLineWrap(true);
		edit_Area.addKeyListener(new KeyListener() {
			public void keyPressed(KeyEvent e) {
				sumCount = edit_Area.getText().length();
				if(e.getKeyChar() == KeyEvent.VK_BACK_SPACE) {
					edit_Area.setEditable(false);
				}
				if(sumCount > 700)
					edit_Area.setEditable(false);
				else
					edit_Area.setEditable(true);
			}

			@Override
			public void keyTyped(KeyEvent e) {
				// TODO Auto-generated method stub
				
			}

			@Override
			public void keyReleased(KeyEvent e) {
				sumCount = edit_Area.getText().length();
				
			}
		});
		
		//计数标签
		wordCount = new JLabel("0/700", JLabel.LEFT);
		wordCount.setBounds(0, 500, 50, 50);
		
		//发送按钮
		JButton send_button = new JButton("发送(C)");
		send_button.setBounds(700, 500, 100, 50);
		send_button.setMnemonic(KeyEvent.VK_C);
		send_button.setDisplayedMnemonicIndex(3);
		send_button.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				string = edit_Area.getText();
				edit_Area.setText("");
				sumCount = 0;
			}
		});
		
		win.add(sp_visual_Area);
		win.add(edit_Area);
		win.add(send_button);
		win.add(wordCount);
		
		win.setVisible(true);
	}
	void wordCounter() {
		wordCount.setText(sumCount + "/700");
	}
}

运行程序

服务器

先运行服务器。打开cmd,转到源码所在目录,输入:
javac receiveServer.java
java receiveServer 2021 2022

2021 2022为两个等待连接的端口

客户端


再打开一个cmd,转到源码所在目录,输入:
javac sentUI.java
java sentUI localhost 2021
表示连接到本地服务器2021端口


再打开一个cmd,转到源码所在目录,输入:
java sentUI localhost 2022
表示连接到本地服务器2022端口


结果如下所示:
在这里插入图片描述
在这里插入图片描述

存在问题

  1. 一次性输入大量字符时,可能会超出规定字符长度,标签也会显示,原因暂不知道
  2. 超出规定字符长度后,无法用退格键删除字符,导致只能点击发送按钮清空编辑区
  3. 仅连接一次,没有固定时间检查连接是否断开

可改进地方

  1. 运用多线程
  2. 将JTextArea替换为JTextPane,将发送的信息和接收的信息以左对齐和右对齐区别,增加发送图片等更多功能。
  3. 定期检查网络连接
  4. 可进一步做为多人聊天室
  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值