# TCP-based network programming **Repository Path**: ju-wenhui/tcp-based-network-programming ## Basic Information - **Project Name**: TCP-based network programming - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-06-28 - **Last Updated**: 2024-06-28 ## Categories & Tags **Categories**: Uncategorized **Tags**: TCP, Java, 多线程, Sqlite ## README
- 服务器
#### 子模块设计
- 客户端
客户端设计两个界面,分别是Login界面和ChatView客户端个人聊天界面。
Login界面:有“登录”按钮监听,“登录”按钮触发后会隐藏Login界面,显示聊天界面。
ChatView界面:有“发送”、“发送文件”和关闭聊天界面按钮监听。“发送”按钮触发后,先判断是否获取到文本,若获取成功,编码后发送数据,若没有成功,系统提醒重新输入要发送的文本信息;“发送文件”按钮触发后,会进行文件的选择,获取选择文件的路径,发送文件的名称、大小、内容;关闭聊天界面按钮触发后,该Client程序终止,并向服务端发送客户端离开的信息。
- 服务端
服务端开启后,直接进入服务端界面并连接数据库,界面会显示客户端聊天的信息以及文件发送和接收的情况。
聊天信息的接收:服务端接收到客户端们的聊天信息之后,会将信息进行解码,展示在面板上并保存在数据库中,之后将信息再次编码转发给所有在线的客户端。
文件信息的接收:服务端接收到某一客户端上传的文件之后,判断其他在线客户端是否接收,若接收则将文件进行转发。
### 五、程序清单
```java
public class ChatView {
String userName; //由客户端登录时设置
JTextField text;
JTextArea textArea;
ClientReadAndWrite.ChatViewListen listener;
// 构造函数
public ChatView(String userName) {
this.userName = userName ;
init();
}
// 初始化函数
void init() {
//窗口
JFrame JFrame = new JFrame("客户端");
JFrame.setBounds(500,200,400,330); //设置坐标和大小
JFrame.setResizable(false); // 缩放为不能缩放
//面板
JPanel JPanel = new JPanel();
JLabel Lable = new JLabel("用户:" + userName); //用户界面标题
//文本
textArea = new JTextArea("与服务器连接成功\n",12, 35);
textArea.setEditable(false); //不可修改文本内容
//滚动面板
JScrollPane Scroll = new JScrollPane(textArea);
Scroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); // 显示垂直条
//面板上添加标题和滚动面板
JPanel.add(Lable);
JPanel.add(Scroll);
//编辑文本
text = new JTextField(20);
JButton button = new JButton("发送");
JButton openFileBtn = new JButton("发送文件");
JPanel.add(text);
JPanel.add(button);
JPanel.add(openFileBtn);
//“打开文件”按钮------ ----监听
openFileBtn.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {showFileOpenDialog(JFrame);}});
// “发送”按钮--------------监听
listener = new ClientReadAndWrite().new ChatViewListen();
listener.setJTextField(text);
listener.setJTextArea(textArea);
listener.setChatViewJf(JFrame);
text.addActionListener(listener); // 文本框添加监听
button.addActionListener(listener); // 按钮添加监听
JFrame.add(JPanel);
JFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JFrame.setVisible(true);
}
// “打开文件”监听
void showFileOpenDialog(JFrame parent) {
// 文件选择器,选择要发送的文件
JFileChooser fileChooser = new JFileChooser();
// 选择器默认打开D盘界面
fileChooser.setCurrentDirectory(new File("D:\\"));
// 选择文件
int choose = fileChooser.showOpenDialog(parent);
// 点击确定
if(choose == JFileChooser.APPROVE_OPTION) {
// 获取路径
File file = fileChooser.getSelectedFile();
String path = file.getAbsolutePath();
ClientFileThread.outFileToServer(path);
}
}
}
```
```java
public class Client {
// 主函数,新建登录窗口
public static void main(String[] args) {
new Login();
}
}
```
```java
public class ClientFileThread extends Thread{
private Socket socket = null;
private JFrame chatViewJFrame = null;
static String userName = null;
static PrintWriter out = null;
static DataInputStream fileIn = null;
static DataOutputStream fileOut = null;
static DataInputStream fileReader = null;
static DataOutputStream fileWriter = null;
public ClientFileThread(String userName, JFrame chatViewJFrame, PrintWriter out) {
ClientFileThread.userName = userName;
this.chatViewJFrame = chatViewJFrame;
ClientFileThread.out = out;
}
//客户端-------------------------------------------------接收文件
public void run() {
try {
InetAddress addr = InetAddress.getByName(null); // 获取主机地址
socket = new Socket(addr, 8090);
fileIn = new DataInputStream(socket.getInputStream());
fileOut = new DataOutputStream(socket.getOutputStream());
//接收文件
while(true) {
String textName = fileIn.readUTF();
long totalLength = fileIn.readLong();
int result = JOptionPane.showConfirmDialog(chatViewJFrame, "是否接受?", "提示",
JOptionPane.YES_NO_OPTION);
int length = -1;
byte[] buff = new byte[1024];
long curLength = 0;
//提示框选择结果,0确定,1取消
if(result == 0){
// 新建当前用户的文件夹
File userFile = new File("D:\\software_cache\\ideaProject\\EngineeringTraining\\src\\文件\\" + userName);
if(!userFile.exists()) {
userFile.mkdir();
}
//文件
File file = new File("D:\\software_cache\\ideaProject\\EngineeringTraining\\src\\文件\\" + userName + "\\"+ textName);
fileWriter = new DataOutputStream(new FileOutputStream(file));
// 把文件写进本地
while((length = fileIn.read(buff)) > 0) {
fileWriter.write(buff, 0, length);
fileWriter.flush();
curLength += length;
if(curLength == totalLength) { // 强制结束
break;
}
}
String msg = userName + "成功接收文件!";
String msgTrans = codeBase64(msg);
out.println(msgTrans);
out.flush();
// 提示文件存放地址
JOptionPane.showMessageDialog(chatViewJFrame, "文件存放于:\n" +
"D:\\software_cache\\ideaProject\\EngineeringTraining\\src\\文件\\" +
userName + "\\" + textName, "提示", JOptionPane.INFORMATION_MESSAGE);
}
// 不接受文件
else {
while((length = fileIn.read(buff)) > 0) {
curLength += length;
if(curLength == totalLength) { // 强制结束
break;
}
}
}
fileWriter.close();
}
} catch (Exception e) {}
}
// 客户端-----------------------------------------------------------发送文件
static void outFileToServer(String path) {
try {
File file = new File(path);
fileReader = new DataInputStream(new FileInputStream(file));
fileOut.writeUTF(file.getName()); // 发送文件名字
fileOut.flush();
fileOut.writeLong(file.length()); // 发送文件长度
fileOut.flush();
int length = -1;
byte[] buff = new byte[1024];
while ((length = fileReader.read(buff)) > 0) { // 发送内容
fileOut.write(buff, 0, length);
fileOut.flush();
}
String msg = userName + "已成功发送文件!";
String msgTrans = codeBase64(msg);
out.println(msgTrans);
out.flush();
} catch (Exception e) {}
}
//Base64编码
private static String codeBase64(String msg){
//1、字符串转为字节数组
byte[] bytes = msg.getBytes();
//2、编码
byte[] encodeBytes = Base64.getEncoder().encode(bytes);
//3、将编码后的字节数组转为字符串
String msgTrans = new String(encodeBytes);
return msgTrans;
}
}
```
```java
public class ClientReadAndWrite extends Thread{
static Socket mySocket = null; // 一定要加上static,否则新建线程时会清空
static JTextField textInput;
static JTextArea textShow;
static JFrame chatViewJFrame;
static BufferedReader in = null;
static PrintWriter out = null;
static String userName;
// 接收服务端-----------------------------------------------------发送来的消息
public void run() {
try {
in = new BufferedReader(new InputStreamReader(mySocket.getInputStream()));
//不断获取服务端发来的信息
while (true) {
String msgTrans = in.readLine();
String msg = decodeBase64(msgTrans);
textShow.append(msg + '\n'); //显示在用户自己的界面里
textShow.setCaretPosition(textShow.getDocument().getLength()); //光标移动到文本末尾
}
} catch (Exception e) {}
}
//登陆按钮--------------------------------------------------------监听
class LoginListen implements ActionListener {
JTextField textField; //名字
JPasswordField pwdField; //密码
JFrame loginJFrame; //登录窗口
ChatView chatView = null; //用户聊天界面
public void setJTextField(JTextField textField) {
this.textField = textField;
}
public void setJPasswordField(JPasswordField pwdField) {
this.pwdField = pwdField;
}
public void setJFrame(JFrame jFrame) {
this.loginJFrame = jFrame;
}
//登录按钮------------------监听
public void actionPerformed(ActionEvent event) {
userName = textField.getText();
String userPwd = String.valueOf(pwdField.getPassword()); //char数组转为string
//(!userName.isEmpty() && userPwd.equals("6666"))
if(!userName.isEmpty()) {
chatView = new ChatView(userName); //新建聊天窗口,设置聊天窗口的用户名
//连接
try {
InetAddress addr = InetAddress.getByName(null); //获取主机地址
mySocket = new Socket(addr,8080);
loginJFrame.setVisible(false); //登陆成功后,隐藏登录窗口
out = new PrintWriter(mySocket.getOutputStream()); // 输出流
String msg = "用户:" + userName + "成功连接服务器!";
String msgTrans= codeBase64(msg);
out.println(msgTrans); // 发送用户名给服务器
out.flush();
} catch (IOException e) {
e.printStackTrace();
}
// 数据读写线程-----------------启动
ClientReadAndWrite readAndPrint = new ClientReadAndWrite();
readAndPrint.start();
// 文件读写线程-------------- -启动
ClientFileThread fileThread = new ClientFileThread(userName, chatViewJFrame, out);
fileThread.start();
}
else {
JOptionPane.showMessageDialog(loginJFrame, "请输入用户姓名!", "提示", JOptionPane.WARNING_MESSAGE);
}
}
}
//聊天界面---------------------------------------------------设置监听
class ChatViewListen implements ActionListener{
public void setJTextField(JTextField text) {
textInput = text; // 放在外部类,因为其它地方也要用到
}
public void setJTextArea(JTextArea textArea) {
textShow = textArea; // 放在外部类,因为其它地方也要用到
}
//关闭聊天界面的--------------------------------------------监听
public void setChatViewJf(JFrame jFrame) {
chatViewJFrame = jFrame; // 放在外部类,因为其它地方也要用到
chatViewJFrame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
String msg = "用户:" + userName + "已离开!";
String msgTrans = codeBase64(msg);
out.println(msgTrans);
out.flush();
System.exit(0);
}
});
}
// 信息发送------------------------------------------------------监听
public void actionPerformed(ActionEvent event) {
try {
String str = textInput.getText();
// 文本框内容为空
if("".equals(str)) {
textInput.grabFocus(); // 设置焦点(可行)
// 弹出消息对话框(警告消息)
JOptionPane.showMessageDialog(chatViewJFrame, "输入为空,请重新输入!", "提示", JOptionPane.WARNING_MESSAGE);
return;
}
out.println(codeBase64(userName + ":" + str)); // 输出给服务端
out.flush();
textInput.setText(""); // 清空文本框
textInput.grabFocus(); // 设置焦点(可行)
} catch (Exception e) {}
}
}
//Base64编码
private static String codeBase64(String msg){
//1、字符串转为字节数组
byte[] bytes = msg.getBytes();
//2、编码
byte[] encodeBytes = Base64.getEncoder().encode(bytes);
//3、将编码后的字节数组转为字符串
String msgTrans = new String(encodeBytes);
return msgTrans;
}
//解码base64方法
private static String decodeBase64(String msgTrans){
//1、使用base64类的getDecoder()方法获取一个Decoder实例
Base64.Decoder decoder = Base64.getDecoder();
//2、解码字符串
byte[] decodedBytes = decoder.decode(msgTrans);
//3、将解码后的字符转换成字符串
String msg = new String(decodedBytes);
return msg;
}
}
```
```java
public class ForwardFile extends Thread {
private Socket nowSocket = null;
private DataInputStream input = null;
private DataOutputStream output = null;
public ForwardFile(Socket socket) {
this.nowSocket = socket;
}
public void run() {
try {
input = new DataInputStream(nowSocket.getInputStream()); // 输入流
while (true) {
// 获取文件名字和文件长度
String textName = input.readUTF();
long textLength = input.readLong();
// 发送文件名字和文件长度给其他在线用户
for(Socket socket: ServerFileThread.list) {
output = new DataOutputStream(socket.getOutputStream()); // 输出流
if(socket != nowSocket) { // 发送给其它客户端
output.writeUTF(textName);
output.flush();
output.writeLong(textLength);
output.flush();
}
}
// 发送文件内容
int length = -1;
long curLength = 0;
byte[] buff = new byte[1024];
while ((length = input.read(buff)) > 0) {
curLength += length;
for(Socket socket: ServerFileThread.list) {
output = new DataOutputStream(socket.getOutputStream()); // 输出流
if(socket != nowSocket) { // 发送给其它客户端
output.write(buff, 0, length);
output.flush();
}
}
if(curLength == textLength) { // 强制退出
break;
}
}
}
} catch (Exception e) {
ServerFileThread.list.remove(nowSocket); // 线程关闭,移除相应套接字
}
}
}
```
```java
public class Login {
JTextField textField = null;
JPasswordField pwdField = null;
ClientReadAndWrite.LoginListen listener=null;
// 构造函数
public Login() {
init();
}
void init() {
//登录窗口,不缩放
JFrame JFrame = new JFrame("登录");
JFrame.setBounds(500, 250, 310, 210);
JFrame.setResizable(false);
//面板1
JPanel JPanel1 = new JPanel();
JLabel HeadJLabel = new JLabel("登录界面");
HeadJLabel.setFont(new Font(null, 0, 35)); // 设置文本的字体类型、样式 和 大小
JPanel1.add(HeadJLabel);
//面板2
JPanel JPanel2 = new JPanel();
JLabel nameJLabel = new JLabel("用户名:");
textField = new JTextField(20);
JLabel pwdJLabel = new JLabel("密码: ");
pwdField = new JPasswordField(20);
JButton loginButton = new JButton("登录");
JPanel2.add(nameJLabel);
JPanel2.add(textField);
JPanel2.add(pwdJLabel);
JPanel2.add(pwdField);
JPanel2.add(loginButton);
//大面板,用于装面板1和面板2
JPanel JPanel = new JPanel(new BorderLayout()); //BorderLayout布局
JPanel.add(JPanel1, BorderLayout.NORTH);
JPanel.add(JPanel2, BorderLayout.CENTER);
//监听按钮
listener = new ClientReadAndWrite().new LoginListen(); //新建监听类
listener.setJTextField(textField);
listener.setJPasswordField(pwdField);
listener.setJFrame(JFrame);
pwdField.addActionListener(listener); //密码回车可登录
loginButton.addActionListener(listener); //按钮添加监听
JFrame.add(JPanel);
JFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //关闭界面程序结束
JFrame.setVisible(true);
}
}
```
```java
public class MultiChat {
JTextArea textArea;
// 用于向文本区域添加信息
void setTextArea(String str) {
textArea.append(str+'\n');
textArea.setCaretPosition(textArea.getDocument().getLength()); //光标放最后
}
// 构造函数
public MultiChat() {
init();
}
void init() {
//窗口
JFrame JFrame = new JFrame("服务器端");
JFrame.setBounds(500,100,450,500); // 设置窗口坐标和大小
JFrame.setResizable(false); // 不可缩放
//容器
JPanel JPanel = new JPanel();
JLabel Lable = new JLabel("服务器端存储的信息");
//文本
textArea = new JTextArea(23, 38);
textArea.setEditable(false); // 文本内容不可修改
//滚动面板
JScrollPane Scroll = new JScrollPane(textArea);
Scroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); // 显示垂直条
JPanel.add(Lable);
JPanel.add(Scroll);
JFrame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
JFrame.add(JPanel); //容器添加到窗口
JFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 窗口关闭程序结束
JFrame.setVisible(true);
}
}
```
```java
public class Server{
static ServerSocket server = null;
static Socket socket = null;
static List
- 登录界面
- 信息传输界面
- 文件传输界面
- 上传代码
### 七、项目训练的收获和体会
实训的任务是利用TCP协议进行网络程序设计。网络通信在当今数字化的时代已经成为生活和工作的重要组成部分。本次实训,我从TCP网络程序的编写,到Base64的编码、解码,再到字段数据和文件的传输,以及群聊和数据库的实现中,收获颇多。
我了解了网络通信的复杂性和挑战性,包括TCP的三次握手建立连接、数据传输、断开连接等过程,这些增强了我的网络通信理论基础,为我后续的编程实践提供了重要的指导。
Base64编码的学习让我对二进制数据和文本数据之间的转换有了更清晰的认识,这种编码方式不仅保证了数据的完整性,还以高了数据传输的效率和安全性。
总之,通过本次实训,我不仅掌握了相关的理论知识和技术方法,还提高了自己的编程能力和解决问题的能力,这些收获将为我今后的学习和工作提供重要的支持和帮助。
### 八、参考文献(含网络资源)
[1]李检辉.基于TCP的Java Socket网络连接过程要点分析[J].电脑知识与技术,2023,19(20):103-105.DOI:10.14004/j.cnki.ckt.2023.1017.
[2]缪志敏,刘莹,岳淑贞. TCP协议“三次握手”的误解和正确认识[C]//全国高等学校计算机教育研究会,中国计算机学会,教育部高等学校计算机类专业教学指导委员会.2023中国高校计算机教育大会论文集.[出版者不详],2023:5.DOI:10.26914/c.cnkihy.2023.109489.
[3]薛宾.嵌入式数据库SQLite在Java中的应用[J].天津职业院校联合学报,2008(04):123-126.
[4]姚峰.Java平台中Base64编码/解码算法的改进[J].计算机应用与软件,2008,25(12):164-165+176.