重要的类

1.ServerSocket类
ServerSocket类主要功能是在指定端口处建立监听服务。
ServerSocket serverSocket = new ServerSocket(Configuration.PORT);
一台计算机可以开启多个客户端,不同客户端用随机分配的端口区别,ServerSocket在指定端口阻塞监听客户端的请求。
Socket socket = serverSocket.accept();
若有客户端的socket请求连接,在服务端建立一个对应的Socket与之进行通信,再执行之后的语句。
2.Socket类
客户端可创建一个Socket对象连接服务端进行通信:
Socket socket = new Socket(InetAddress.getLocalHost(), Configuration.PORT);
Socket类的构造函数有两个参数,第一个参数是欲连接到的Server计算机的主机地址,第二个参数是该ServerSocket监听的端口号。
Socket对象建立成功之后,就可以在Client和Server之间建立一个连接,利用Socket类的方法getOutputStream()和getInputStream()获取和传输数据。
服务端
Socket实现多线程通信
为实现多个客户端连接服务器,解决ServerSocket.accept()阻塞的问题,把服务端对客户端的处理全部写到独立的线程里,并循环监听下一个客户端的连接请求。
while (true) {
// 接收客户端Socket,监听,阻塞等待客户端连接,创建socket实例
Socket socket = serverSocket.accept();
// 客户端IP
String ip = socket.getInetAddress().getHostAddress();
// 客户端端口
int port = socket.getPort();
System.out.println("客户端UID:"+ip+":"+port);
// 建立新线程
Runnable runnable=new HandleMessageRunnable(socket, ip, port);
Thread handleClientThread=new Thread(runnable);
handleClientThread.start();
}
服务端线程
通过构造函数获取客户端的socket和clientUid
public HandleMessageRunnable(Socket socket, String ip, int port) {
this.socket = socket;
this.ip = ip;
this.port = port;
this.clientUid = ip + ":" + port;
}
1.添加客户端
将clientUid添加到ArrayList<String>,并通过HashMap实现clientUid与线程HandleMessageRunnable的映射关系。
//添加客户端
public void addClient() {
clientUidArrayList.add(clientUid);
hashMap.put(clientUid, this);
}
2.发送连接成功消息
通过输出流将当前客户连接成功的消息发送给对应客户端
public void sendConnectedMessage() throws IOException {
// 获取输入流
socketInputStream = socket.getInputStream();
// 获取输出流
socketOutputStream = socket.getOutputStream();
// 向当前客户端传输连接成功信息
String successMessage ="服务端提示信息:"+Configuration.NEWLINE+
Util.getCurrentTime() + Configuration.NEWLINE + "成功连接服务器" +
Configuration.NEWLINE+ "服务器IP: " + Util.getLocalHostAddress() +
", 端口: " + Configuration.PORT + Configuration.NEWLINE+ "客户端IP: "
+ ip + ", 端口: " + port + Configuration.NEWLINE;
socketOutputStream.write(successMessage.getBytes());
}
3.更新所有在线客户端名单
a.为StringBuilder的开头添加消息类型(Configuration.TYPE_UPDATEONLINELIST)与分隔符(Configuration.SEPARATOR)。
StringBuilder(Configuration.TYPE_UPDATEONLINELIST + Configuration.SEPARATOR);
b.遍历客户列表(clientUidArrayList),用StringBuilder将客户列表里所有的clientUid拼接在一起,并用逗号隔开。
for (String clientUid : clientUidArrayList) {
stringBuilder.append(clientUid);
int index = clientUidArrayList.indexOf(clientUid);
int size = clientUidArrayList.size();
if (index != size - 1) {
stringBuilder.append(",");
}
}
c.再次遍历客户列表,通过HashMap的映射关系将在线客户端名单一一传给每个客户端。
String onlineClients=stringBuilder.toString();
for (String clientUid : clientUidArrayList) {
OutputStream out = hashMap.get(clientUid).socket.getOutputStream();
out.write(onlineClients.getBytes());
}
客户端
初始化客户端界面
a.创建对象,取消窗口关闭键。
// 创建客户端窗口对象
clientFrame = new ClientFrame();
// 窗口关闭键无效,必须通过退出键退出客户端
clientFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
b.通过获取本机的屏幕分辨率设置客户端窗口的位置
int screenWidth = Util.getScreenWidth();
int screenHeight = Util.getScreenHeight();
int x = (screenWidth - Configuration.CLIENT_FRAME_WIDTH) / 2;
int y = (screenHeight - Configuration.CLIENT_FRAME_HEIGHT) / 2;
// 设置位置
clientFrame.setLocation(x, y);
// 设置窗口可见
clientFrame.setVisible(true);
连接服务端
a.创建socket对象连接服务器
Socket socket = new Socket(InetAddress.getLocalHost(), Configuration.PORT);
b.将创建的socket对象传递给客户端界面类(ClientFrame)
clientFrame.setSocket(socket);
c.在聊天消息框显示连接成功的欢迎信息
// 获取输入流
socketInputStream = socket.getInputStream();
// 获取输出流
socketOutputStream = socket.getOutputStream();
// 获取服务端发送的欢迎信息
byte[] buf = new byte[1024 * 1];
int len = socketInputStream.read(buf);
// 将欢迎信息显示在聊天消息框
String welcomeMessage=new String(buf, 0, len);
clientFrame.messageJTextArea.append(welcomeMessage);
clientFrame.messageJTextArea.append(Configuration.NEWLINE);
数据的发送、处理与接收

客户端界面(发送数据)

a.在线名单列表:用int[]获取鼠标选中的在线客户行数,遍历int[],用StringBuilder拼接所有选中的客户的clientUid,并用逗号隔开。
int[] selectedIndex = onlineJTable.getSelectedRows();
// 将所有消息目标的uid拼接成一个字符串,以逗号分隔
receiverStringBuilder = new StringBuilder("");
for (int i = 0; i < selectedIndex.length; i++) {
//获取第i行第0列的ip数据
String ip = (String) tableModel.getValueAt(selectedIndex[i], 0);
//获取第i行第1列的端口数据
String port = (String) tableModel.getValueAt(selectedIndex[i], 1);
receiverStringBuilder.append(ip);
receiverStringBuilder.append(":");
receiverStringBuilder.append(port);
if (i != selectedIndex.length - 1) {
receiverStringBuilder.append(",");
}
}
b.发送消息按钮:向发送的客户名单发送消息,在聊天窗显示发送的消息
// 向服务器发送聊天信息
OutputStream out = socket.getOutputStream();
out.write((Configuration.TYPE_CHAT + Configuration.SEPARATOR + receivers +
Configuration.SEPARATOR+ message).getBytes());
messageJTextArea.append(Util.getCurrentTime() +
Configuration.NEWLINE + "发往 " + receivers+ Configuration.NEWLINE);
// 在聊天窗显示发送消息
messageJTextArea.append(message + Configuration.NEWLINE);
清空文本输入框
//清空文本输入框
sendJTextArea.setText("");
c.退出与清屏
// 向服务器发送退出信息
OutputStream out = socket.getOutputStream();
out.write((Configuration.TYPE_EXIT + Configuration.SEPARATOR).getBytes());
System.exit(0);
messageJTextArea.setText("");
服务端(处理数据)
循环监听并转发客户端的数据
a.读取输入流数据
len = socketInputStream.read(buf);
String message = new String(buf, 0, len);
b.获取分隔符在字符串的位置,将字符串拆分为消息类型与消息主题两部分
int separatorIndex=message.indexOf(Configuration.SEPARATOR);
// 消息类型:退出或者聊天
String messageType = message.substring(0, separatorIndex);
// 消息本体:空或者聊天内容
String messageContent = message.substring(separatorIndex + 1);
c.处理数据:若数据类型为退出客户端,则将客户端信息从clientUid列表和HashMap中移除
// 更新ArrayList和HashMap, 删除退出的uid和线程
removeClient();
// 更新在线名单
updateOnlineList();
// 结束循环,结束该服务线程
break;
更新在线客户名单,结束循环与线程
public void removeClient() {
int index = clientUidArrayList.indexOf(clientUid);
clientUidArrayList.remove(index);
hashMap.remove(clientUid);
}
d.处理数据:若数据类型为聊天,则根据逗号位置拆分获取收信人地址与聊天内容
int messageContentSeparatorIndex=messageContent.indexOf(Configuration.SEPARATOR);
// 提取收信者地址
String[] receiveClientUidArray = messageContent.substring(0,
messageContentSeparatorIndex).split(",");
// 提取聊天内容
String word = messageContent.substring(messageContentSeparatorIndex + 1);
遍历所有要发送的客户端,通过输出流一一发送数据
public void sendChatMessage(String sendClientUid, String[] receiveClientUidArray,
String word) throws IOException {
for (String clientUid : receiveClientUidArray) {
OutputStream out = hashMap.get(clientUid).socket.getOutputStream();
String message=Configuration.TYPE_CHAT+Configuration.SEPARATOR +
sendClientUid + Configuration.SEPARATOR + word;
out.write(message.getBytes());
}
}
客户端(接受数据)
循环监听服务端处理完发来的消息
a.读取输入流数据
byte[] buf = new byte[1024 * 1];
socketInputStream = socket.getInputStream();
int len = socketInputStream.read(buf);
// 处理服务器传来的消息
String message = new String(buf, 0, len);
b.获取分隔符在字符串的位置,将字符串拆分为消息类型与消息主体两部分
int separatorIndex=message.indexOf(Configuration.SEPARATOR);
// 消息类型:更新在线名单或者聊天(发送类型)
String messageType = message.substring(0, separatorIndex);
// 消息本体:最新的在线名单或者聊天内容(发送内容)
String messageContent = message.substring(separatorIndex + 1);
c.处理消息:若消息类型为更新列表,则将消息主体拆分为ip地址与端口号,清空更新在线名单界面

d.处理消息:若消息类型为聊天,则获取分隔符在消息主体的位置,将消息主体拆分为发送者与发送内容两部分
if (messageType.equals(Configuration.TYPE_CHAT)) {
int messageContentSeparatorIndex=messageContent.indexOf(Configuration.SEPARATOR);
String from = messageContent.substring(0, messageContentSeparatorIndex);//发送者
String word = messageContent.substring(messageContentSeparatorIndex + 1);//发送内容
clientFrame.messageJTextArea.append(Util.getCurrentTime() + Configuration.NEWLINE
+ "来自 "+ from+ Configuration.NEWLINE + word + Configuration.NEWLINE);
clientFrame.messageJTextArea.setCaretPosition(clientFrame.messageJTextArea.
getDocument().getLength());
}
局域网内两台主机的通信


网友评论