此文章写于2021年6月29日
一.背景概述
TCP(传输控制协议)是一种面向连接的,可靠的传输层协议。TCP协议在网络层IP协议的基础上,向应用层用户进程提供可靠的,全双工的数据流传输。
二.设计内容
本课程设计的目的就是设计一个发送TCP数据包的程序,并根据本设计说明TCP数据包的结构以及TCP协议与IP协议的关系,使学生对TCP协议的工作原理有更深入的认识。
三.设计要求
本课程的功能是填充一个TCP数据包,并发送给目的主机。
(1)推荐采用图形界面实现。
(2)程序代码书写规范,有充足的注释。
(3)程序运行:填写目的IP,目的端口。
(4)开发平台、开发语言不限。
(5)明确题目的意图,设计上应有足够的工作量。
(6)成功发送后在屏幕上输出“Send OK”。
四.总体设计
程序的实现由三部分组成:表示层,工具层和应用层
(1)在表示层上,主要是程序与用户交互界面的实现
(2)在工具层上,主要是对应用层中所用到的技术进行封装,以便于调用,并使代码易读。
(3)在应用层上,主要是对TCP数据包进行发送和接收。
设计思路
首先创建一个接收端用于监听用户指定的端口,如果在指定的端口获取到信息,就将其输出到文本框当中;在发送端,程序根据用户输入的目的IP,目的端口和需要发送的消息,创建一个TCP数据包,发送出去。
设计环境
IntelliJ IDEA 2020.2,Windows 10
基本功能
我设计的TCP数据包发送端有以下几个功能
TCP数据包接收端有以下几个功能
详细设计
发送端:
某些变量的定义:
String destIp;//用户输入的目的IP
String destPort;//用户输入的目的端口
String msg;//用户输入的需要发送的信息
Socket socket;//套接字对象
Sender main;//发送端主窗口
socketUtil工具类:
负责创建Socket对象和关闭Socket对象的工具类,
此类中的getSocket方法通过用户输入的ip和端口号获取socket对象;
close方法将传入的socket对象进行关闭。
public class socketUtil {
private socketUtil(){}
/**
* 关闭socket和serverSocket
* @param sk
*/
public static void close(Socket sk){
try {
sk.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
/**
* 获取Socket对象
* @param ip
* @param port
* @return
*/
public static Socket getSocket(String ip,int port) throws IOException {
Socket socketObj = new Socket(ip,port);
return socketObj;
}
}
stringUtil工具类:
主要用于判断字符串是否为空
public class stringUtil {
/**
* 判断字符串是否为空
* @param str
* @return
*/
public static boolean isEmpty(String str){
if(str==null || "".equals((str.trim()))){
return true;
}
else{
return false;
}
}
}
在Sender类中初始化发送端主界面的函数:
主要组件有ip输入框,端口输入框,消息输入框和一个发送按钮。
private void initComponents() {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
ResourceBundle bundle = ResourceBundle.getBundle("logInFrm");
label1 = new JLabel();
label2 = new JLabel();
socketIpTxt = new JTextField();
socketPortTxt = new JTextField();
scrollPane1 = new JScrollPane();
messageTxt = new JTextArea();
label3 = new JLabel();
button1 = new JButton();
//======== this ========
setTitle(bundle.getString("Sender.this.title"));
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
Container contentPane = getContentPane();
//---- label1 ----
label1.setText(bundle.getString("Sender.label1.text"));
//---- label2 ----
label2.setText(bundle.getString("Sender.label2.text"));
//======== scrollPane1 ========
{
scrollPane1.setViewportView(messageTxt);
}
//---- label3 ----
label3.setText(bundle.getString("Sender.label3.text"));
//---- button1 ----
button1.setText(bundle.getString("Sender.button1.text"));
button1.addActionListener(e -> button1ActionPerformed(e));
GroupLayout contentPaneLayout = new GroupLayout(contentPane);
contentPane.setLayout(contentPaneLayout);
contentPaneLayout.setHorizontalGroup(
contentPaneLayout.createParallelGroup()
.addGroup(contentPaneLayout.createSequentialGroup()
.addContainerGap(83, Short.MAX_VALUE)
.addGroup(contentPaneLayout.createParallelGroup()
.addComponent(label2, GroupLayout.Alignment.TRAILING)
.addComponent(label1, GroupLayout.Alignment.TRAILING)
.addComponent(label3, GroupLayout.Alignment.TRAILING))
.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
.addGroup(contentPaneLayout.createParallelGroup()
.addComponent(button1)
.addGroup(contentPaneLayout.createParallelGroup(GroupLayout.Alignment.LEADING, false)
.addComponent(socketIpTxt, GroupLayout.DEFAULT_SIZE, 168, Short.MAX_VALUE)
.addComponent(socketPortTxt, GroupLayout.PREFERRED_SIZE, 58, GroupLayout.PREFERRED_SIZE)
.addComponent(scrollPane1, GroupLayout.DEFAULT_SIZE, 168, Short.MAX_VALUE)))
.addContainerGap(95, Short.MAX_VALUE))
);
contentPaneLayout.setVerticalGroup(
contentPaneLayout.createParallelGroup()
.addGroup(contentPaneLayout.createSequentialGroup()
.addGap(31, 31, 31)
.addGroup(contentPaneLayout.createParallelGroup(GroupLayout.Alignment.TRAILING)
.addComponent(label1, GroupLayout.PREFERRED_SIZE, 26, GroupLayout.PREFERRED_SIZE)
.addComponent(socketIpTxt, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE))
.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
.addGroup(contentPaneLayout.createParallelGroup(GroupLayout.Alignment.BASELINE)
.addComponent(label2, GroupLayout.PREFERRED_SIZE, 26, GroupLayout.PREFERRED_SIZE)
.addComponent(socketPortTxt, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE))
.addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED)
.addGroup(contentPaneLayout.createParallelGroup()
.addComponent(scrollPane1, GroupLayout.PREFERRED_SIZE, 112, GroupLayout.PREFERRED_SIZE)
.addComponent(label3))
.addGap(18, 18, 18)
.addComponent(button1)
.addContainerGap(16, Short.MAX_VALUE))
);
pack();
setLocationRelativeTo(getOwner());
// JFormDesigner - End of component initialization //GEN-END:initComponents
}
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
private JLabel label1;
private JLabel label2;
private JTextField socketIpTxt;
private JTextField socketPortTxt;
private JScrollPane scrollPane1;
private JTextArea messageTxt;
private JLabel label3;
private JButton button1;
// JFormDesigner - End of variables declaration //GEN-END:variables
}
在Sender类中用于执行发送TCP数据包的按钮事件监听方法:
当用户按下此按钮时,程序通过各个输入框的getText()方法获取相应的字符串并赋值给字符串对象,程序会首先判断用户输入的表单是否全面;
如果全面的话,就会通过输入的ip和端口号创建一个socket对象;
随后会通过socket对象的getOutPueStream方法创建一个outPutStream对象,并在此基础上创建一个outPutStreamWriter对象;
之后利用outPutStreamWriter对象的write方法将msg消息字符串发送到输出流,并进行刷新;
发送完成后,通过调用socket对象的close方法关闭socket,并提示消息框:“Send OK!”。
private void button1ActionPerformed(ActionEvent e) {
String destIp = this.socketIpTxt.getText();
String destPort = this.socketPortTxt.getText();
String msg = this.messageTxt.getText();
Socket socketObj = null;
/**
* 参数数量不正确
*/
if(stringUtil.isEmpty(destIp)) {
JOptionPane.showMessageDialog(null,"请输入IP!");
return;
}
if(stringUtil.isEmpty(destPort)) {
JOptionPane.showMessageDialog(null,"请输入端口号!");
return;
}
if(stringUtil.isEmpty(msg)) {
JOptionPane.showMessageDialog(null,"请输入要发送的消息!");
return;
}
try {
/**
* 创建socket对象
*/
String str = msg;
socketObj = new Socket(destIp,Integer.parseInt(destPort));
/**
* 创建OutStreamWriter对象
*/
OutputStream os = socketObj.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(os);
osw.write(str);//输出字符流
osw.flush();
socketUtil.close(socketObj);
JOptionPane.showMessageDialog(null,"Send OK!");
} catch(Exception ex) {
JOptionPane.showMessageDialog(null,"Sorry,can’t connect to "+destIp+":"+destPort);
ex.printStackTrace();
}
}
当用户没有输入目的IP或者目的端口或者消息时,图形界面会分别弹出消息框提示用户:
mainSender类中用于实例化窗口的主方法:
public static void main(String[] args) {
Sender main = new Sender();
main.setBounds(300,300,500,330);
main.setVisible(true);
}
SeverSocket severSocket;//负责接收发送端连接请求的SeverSocket
Socket socket;//severSocket的accept方法获取的Socket对象
String port;//用户输入的端口号
Reveiver main;//接收端主窗口
socketUtil工具类:
主要包含用于获取serverSocket对象和关闭socket对象的方法。
getServerSocket方法利用用户输入的端口号,创建一个severSocket对象。
close方法将传入的socket对象和severSocket对象进行关闭。
public class socketUtil {
private socketUtil(){}
/**
* 获取serverSocket对象
* @param port
* @return serverSocket
* @throws Exception
*/
public static ServerSocket getServerSocket(int port) throws Exception{
ServerSocket serverSocket= new ServerSocket( Integer.valueOf(port) );
return serverSocket;
}
/**
* 关闭socket和serverSocket
* @param sk
* @param ss
*/
public static void close(Socket sk, ServerSocket ss){
try {
ss.close();
sk.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
public static void close(Socket sk){
try {
sk.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
stringUtil工具类:
主要用于判断字符串是否为空。
public class stringUtil {
/**
* 判断字符串是否为空
* @param str
* @return
*/
public static boolean isEmpty(String str){
if(str==null || "".equals((str.trim()))){
return true;
}
else{
return false;
}
}
}
Reciver类中的初始化接收端窗口的方法:
主要组件包括:一个端口输入框,一个消息展示框和一个监听按钮。
private void initComponents() {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
ResourceBundle bundle = ResourceBundle.getBundle("logInFrm");
button1 = new JButton();
receiverMsgTxt = new JTextArea();
label2 = new JLabel();
portTxt = new JTextField();
label1 = new JLabel();
//======== this ========
setTitle("Receiver");
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
Container contentPane = getContentPane();
//---- button1 ----
button1.setText(bundle.getString("demo.button1.text"));
button1.addActionListener(e -> button1ActionPerformed(e));
//---- label2 ----
label2.setText(bundle.getString("demo.label2.text"));
//---- label1 ----
label1.setText(bundle.getString("demo.label1.text"));
GroupLayout contentPaneLayout = new GroupLayout(contentPane);
contentPane.setLayout(contentPaneLayout);
contentPaneLayout.setHorizontalGroup(
contentPaneLayout.createParallelGroup()
.addGroup(contentPaneLayout.createSequentialGroup()
.addGap(42, 42, 42)
.addGroup(contentPaneLayout.createParallelGroup()
.addGroup(contentPaneLayout.createSequentialGroup()
.addGap(35, 35, 35)
.addComponent(label1)
.addGap(2, 2, 2)
.addComponent(portTxt, GroupLayout.PREFERRED_SIZE, 87, GroupLayout.PREFERRED_SIZE))
.addGroup(contentPaneLayout.createSequentialGroup()
.addGap(57, 57, 57)
.addComponent(label2))
.addComponent(receiverMsgTxt, GroupLayout.PREFERRED_SIZE, 207, GroupLayout.PREFERRED_SIZE)
.addGroup(contentPaneLayout.createSequentialGroup()
.addGap(62, 62, 62)
.addComponent(button1)))
.addContainerGap(89, Short.MAX_VALUE))
);
contentPaneLayout.setVerticalGroup(
contentPaneLayout.createParallelGroup()
.addGroup(contentPaneLayout.createSequentialGroup()
.addGap(26, 26, 26)
.addGroup(contentPaneLayout.createParallelGroup()
.addGroup(contentPaneLayout.createSequentialGroup()
.addGap(3, 3, 3)
.addComponent(label1))
.addComponent(portTxt, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE))
.addGap(18, 18, 18)
.addComponent(label2)
.addGap(7, 7, 7)
.addComponent(receiverMsgTxt, GroupLayout.PREFERRED_SIZE, 118, GroupLayout.PREFERRED_SIZE)
.addGap(7, 7, 7)
.addComponent(button1)
.addContainerGap(33, Short.MAX_VALUE))
);
pack();
setLocationRelativeTo(getOwner());
// JFormDesigner - End of component initialization //GEN-END:initComponents
}
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
private JButton button1;
private JTextArea receiverMsgTxt;
private JLabel label2;
private JTextField portTxt;
private JLabel label1;
// JFormDesigner - End of variables declaration //GEN-END:variables
}
Reciver类中用于处理监听按钮事件的方法:
主要用于监听用户输入的端口号。
程序开始的时候,会将图形界面中端口输入框的getText方法将字符串读取出来保存到新创建的字符串对象中,如果端口输入框为空,会弹出消息框提醒用户输入端口号;
如果输入了端口号,程序或通过socketUtil工具类的getSeverSocket方法并结合用户输入的端口号获取一个severSocket对象,并发送一个消息框:“监听中…”;
之后,程序会利用severSocket的accept方法获取一个socket对象,并利用socket对象的getInputStream方法获取一个inPutStream对象;
随后程序会调用inPutStream对象的read方法将输入流的字符串读入并进行刷新,并将其设置为消息展示框的内容;
最后程序会调用socketUtil工具类的close方法将socket对象和severSocket对象进行关闭。
private void button1ActionPerformed(ActionEvent e) {
ServerSocket serverSocket=null;
Socket socket = null;
String port = this.portTxt.getText();
if(stringUtil.isEmpty(port)){
JOptionPane.showMessageDialog(null,"请输入端口号!");
return;
}
try {
//构造ServerSocket实例,指定端口监听客户端的连接请求
serverSocket = socketUtil.getServerSocket(Integer.valueOf(port));
JOptionPane.showMessageDialog(null,"监听中...");
//建立跟客户端的连接
socket = serverSocket.accept();
//返回此套接字的输入流
InputStream is = socket.getInputStream();
byte [] b = new byte [ 1024 ];
is.read(b);
if(b != null){
JOptionPane.showMessageDialog(null,"成功监听到消息!");
}
String receiverMsgStr = new String (b);
this.receiverMsgTxt.setText(receiverMsgStr);
} catch (Exception ex) {
ex.printStackTrace();
} finally {
//操作结束,关闭socket
socketUtil.close(socket,serverSocket);
}
}
在此方法中,如果用户没有输入端口号,图形界面会创建一个消息框提示用户:
mainSender类中用于实例化接收窗口的方法:
1. public static void main(String[] args) {
2. Receiver main = new Receiver();
3. main.setBounds(300,300,300,300);
4. main.setVisible(true);
5. }
用户通过点击“监听消息”按钮,开始对指定端口进行监听:
当接收端接收到消息以后,接收端界面中的文本框中出现接收到的消息:
五.总结与体会
在这次计算机网络课程设计的过程中,我遇到了不少的问题,例如Swing图形界面的建立,按钮事件的处理,Socket类的使用等等。在此之前我没有接触过这些,但在这次计算机网络设计过程中,我通过查阅视频,文献,资料或者与同学交流,学到了很多东西!
通过这次计算机网络课程设计,我感觉到我自己知识的欠缺,以后要学习的东西还是很多,比如网络编程,JavaWeb等等。这学期我也曾感觉到迷茫,但是在这个期末,我通过写很多代码,用到的都是刚刚学到的比较实用的知识,我感觉到我自己还是有潜力的,我也因此找到了我真正热爱的发展方向。
在这个课程设计的过程中,我也和很多同学进行了交流,感觉到了自己与他们之间的差距,也认识到了自己的优点和缺点。特别是在与同学交流的过程中我对学习过的知识进行了又一遍的复习,巩固了基础,也学习到了一些以前没有接触过的内容,除此之外,还增进了同学们之间的的友谊。
虽然这次课程设计结束了,但是我的学习之路还远远不会止步于此,我将在今后的学习生活中,严格要求自己,努力学习知识,努力完成各项试验,多进行实践,争取成为一名优秀的计算机专业学生!
六.参考文献
[1]Tamara Dean 著. 陶华敏,韩存兵,宋德伟译.计算机网络实用教程.机械工业
出版社.2000
[2] 谢希仁.《计算机网络(第 4 版)》电子工业出版社
[3] Andrew S.Tanenbaum、David J.Wetherall. 计算机网络(英文版•第 5 版). 机械工业出版社
[4] 王勇. 计算机网络课程设计(计算机课程设计与综合实践规划教材). 清华
大学出版社