今天任务较少也较为简单
本节任务
- 账号登录(半成品)
- 可视化界面
一、账号登陆
我们知道,账号输入操作是需要在客户端进行的,所以这里我们需要在客户端封装登陆方法login,在这里读取输入并传递给服务端账号信息
java
public void login(OutputStream os){
Scanner sc=new Scanner(System.in);
System.out.println("请输入账号:");
String account=sc.nextLine();
byte[] ac=account.getBytes();
try {
os.write(ac.length);//不可以发送字符串的长度,必须发送字节的长度
os.write(ac);
os.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
接着我们需要在服务端读取传递过来的信息,用封装函数proLogein实现,并返回读取到的用户名
java
public String proLogein(Socket socket){
try {
InputStream is=socket.getInputStream();
int len=is.read();
byte[] msgs=new byte[len];
is.read(msgs);
String userName=new String(msgs);
System.out.println("用户"+userName+"登录了");
return userName;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
tips.这里proLogein方法应该在消息处理方法proClintMsg处创建线程时调用(如下)
java
public void proClintMsg(Socket socket){
//注意:循环速度太快,导致cpu被占满,io流根本没机会运行,所以这里加一个println,让cpu空闲下来
new Thread(()->{
//登录
//读取用户名
String UserName=proLogein(socket);
ClientUser cu=new ClientUser(UserName,socket);
//保存ClientUser对象
SocketList.add(cu);
while(true) {
try {
InputStream is=cu.socket.getInputStream();
System.out.println("监听"+cu.socket.getPort()+"消息中:");//顺序不对?
String msg=receive(is);
replyMsg(cu,msg);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}).start();
}
用户信息类Client内部定义了账号account(用户名)和套接字socket(这里就不展示代码了)
至此,用户登录的功能雏形便完成了
二、实现界面可视化
这里我们需要创建一个UI类:ClientUI
首先,先创建窗口对象,并调用内部函数设置相关参数(尺寸、关闭方法、显示格式、布局等)
java
JFrame jf=new JFrame("聊天室");
jf.setSize(500,600);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//关闭
jf.setLocationRelativeTo(null);//居中显示
//默认边框布局
随后添加相关组件(最后记得设置可见)
- 文本显示JTextArea
java
JTextArea msgArea=new JTextArea();//支持多行显示所以不用jtext(显示文字)
JScrollPane scrollPane=new JScrollPane(msgArea);//滚动显示面板
jf.add(scrollPane, BorderLayout.NORTH);
- 输入框JTestField
java
JTextField msgField=new JTextField();//输入框
msgField.setPreferredSize(new Dimension(400,0));
msgField.setBackground(Color.LIGHT_GRAY);
jf.add(msgField, BorderLayout.WEST);
- 发送键JButton
java
JButton sendButton=new JButton("发送");
sendButton.setPreferredSize(new Dimension(100,0));
jf.add(sendButton, BorderLayout.EAST);
tips.这里不要忘记为按钮添加监听方法(下面是新版本的简便写法)
java
sendButton.addActionListener(e -> sendMsg(msgArea,msgField));
//e相当于是内部类,,创建一个临时内部类可以直接调用本类中的函数,不需要单独写一个监听类
到这里界面基本就创建完成了,接下来需要做的便是功能迁移
三、功能迁移
我们的代码大体上是不需要做什么改动的,现在我们需要做的是读取可视化界面内输入框里的内容,并让其显示在文本框上
消息发送方法如下
java
public void sendMsg(JTextArea msgArea,JTextField msgField){
String msg=msgField.getText();
//send(msg);
System.out.println("发送中...");
//设置到自己的界面上
msgArea.append("我:"+msg+"\n");
//发送消息
client.send(msg);
}
显示如下(在showui中添加新线程)
java
new Thread(()->{
while(true) {
String msg=client.readMsg();
// System.out.print("接收消息:");
// System.out.println(msg);
// System.out.println("请输入:");
msgArea.append(msg+"\n");
}
}).start();
四、完整代码
·Client
java
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
public class Client {
//由于外部需要访问socket,所以定义为全局变量
Socket socket=null;
public void init(){
try {
socket=new Socket("127.0.0.1",54321);
login(socket.getOutputStream());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void send(String message){
try {
OutputStream os=socket.getOutputStream();
byte[] msgs=message.getBytes();
int len=msgs.length;
os.write(len);
os.write(msgs);
os.flush();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public String readMsg(){
try {
InputStream is=socket.getInputStream();
int len=is.read();
byte[] msgs=new byte[len];
is.read(msgs);
return new String(msgs);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void login(OutputStream os){
Scanner sc=new Scanner(System.in);
System.out.println("请输入账号:");
String account=sc.nextLine();
byte[] ac=account.getBytes();
try {
os.write(ac.length);//不可以发送字符串的长度,必须发送字节的长度
os.write(ac);
os.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
·ClientUI
java
import javax.swing.*;
import java.awt.*;
import java.util.Scanner;
public class ClientUI extends JFrame {
Client client=new Client();
public void showUI(){
//先加载客户端 并进行登录
client.init();
JFrame jf=new JFrame("聊天室");
jf.setSize(500,600);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//关闭
jf.setLocationRelativeTo(null);//居中显示
//默认边框布局
JTextArea msgArea=new JTextArea();//支持多行显示所以不用jtext(显示文字)
// JTextPane msgPane=new JTextPane(msgArea);
JScrollPane scrollPane=new JScrollPane(msgArea);//滚动显示
scrollPane.setPreferredSize(new Dimension(0,500));
JTextField msgField=new JTextField();//输入框
msgField.setPreferredSize(new Dimension(400,0));
msgField.setBackground(Color.LIGHT_GRAY);
JButton sendButton=new JButton("发送");
sendButton.setPreferredSize(new Dimension(100,0));
//JLabel label=new JLabel("当前状态");
jf.add(scrollPane, BorderLayout.NORTH);
jf.add(msgField, BorderLayout.WEST);
jf.add(sendButton, BorderLayout.EAST);
jf.setVisible(true);
sendButton.addActionListener(e -> sendMsg(msgArea,msgField));//e相当于是内部类,,创建一个临时内部类可以直接调用本类中的函数,不需要单独写一个监听类
new Thread(()->{
while(true) {
String msg=client.readMsg();
// System.out.print("接收消息:");
// System.out.println(msg);
// System.out.println("请输入:");
msgArea.append(msg+"\n");
}
}).start();
}
public void sendMsg(JTextArea msgArea,JTextField msgField){
String msg=msgField.getText();
//send(msg);
System.out.println("发送中...");
//设置到自己的界面上
msgArea.append("我:"+msg+"\n");
//发送消息
client.send(msg);
}
public static void main(String[] args) {
ClientUI clientUI=new ClientUI();
clientUI.showUI();
}
}
·ClientUser
java
import java.net.Socket;
public class ClientUser {
String account;
Socket socket;
public ClientUser(String account,Socket socket){
this.account=account;
this.socket=socket;
}
public ClientUser(){
}
}
·Server
java
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
public class Server {
ServerSocket ss=null;
ArrayList<ClientUser> SocketList=new ArrayList<>();
public void init(){
try {
ss=new ServerSocket(54321);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public Socket listen(){
try {
return ss.accept();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void send(String message, OutputStream os){
try {
//mission:通过os把这个消息发送出去
byte[] msgs=message.getBytes();
int len=msgs.length;
os.write(len);//发送长度
os.write(msgs);
os.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public String receive(InputStream is){
try {
int len=is.read();
byte[] msgs=new byte[len];
is.read(msgs);
// for(int i=0;i<len;i++){
// msgs[i]= (byte) is.read();
// }
String msg=new String(msgs);
System.out.println("收到:"+msg);
return msg;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void replyMsg(ClientUser client,String msg){
//转发消息
for(int i=0;i<SocketList.size();i++){
ClientUser cu=SocketList.get(i);
Socket s=cu.socket;
if (s!=client.socket){
try {
String m= client.account+"说:"+msg;//注意拼接逻辑
send(m,s.getOutputStream());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
public void proClintMsg(Socket socket){
//注意:循环速度太快,导致cpu被占满,io流根本没机会运行,所以这里加一个println,让cpu空闲下来
new Thread(()->{
//登录
//读取用户名
String UserName=proLogein(socket);
ClientUser cu=new ClientUser(UserName,socket);
//保存ClientUser对象
SocketList.add(cu);
while(true) {
try {
InputStream is=cu.socket.getInputStream();
System.out.println("监听"+cu.socket.getPort()+"消息中:");//顺序不对?
String msg=receive(is);
replyMsg(cu,msg);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}).start();
}
public String proLogein(Socket socket){
try {
InputStream is=socket.getInputStream();
int len=is.read();
byte[] msgs=new byte[len];
is.read(msgs);
String userName=new String(msgs);
System.out.println("用户"+userName+"登录了");
return userName;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
Server server=new Server();
//初始化
server.init();
//匿名内部类放到线程中(监听连接部分)
new Thread(()->{
while(true) {
System.out.println("等待连接...");
Socket s = server.listen();
System.out.println("有新的连接:"+s.getInetAddress()+"("+s.getPort()+")");
server.proClintMsg(s);//处理客户端消息 ,每个客户端一个线程
}
}).start();
}
}