首页 / 技术 / 正文

Java编写的TCP协议和UDP协议的通信实例及swing编写的局域网聊天软件

2017年03月05日 7 条评论 ... 技术

这是日常的练习,这种课题一般会在大学的实验课中出现。本文会贴出 TCP 实例和 UDP 实例的代码,并且在最后贴出使用 swing 编写的UI界面的局域网聊天软件。

TCP和UDP的区别

详细的区别不详细说,最大的特点就是,TCP是可靠的通信协议,UDP是不可靠的通信协议,但是TCP通信的成本比较高,而UDP的通信成本比较低。这就是有得有失,在网上不同的场景使用不同的协议,比如发送 Email 的时候要求一定是可靠,所以使用的是 TCP 协议,视频语音聊天的时候,要求传输速度要快,但是偶尔的卡顿也不影响正常使用,所以一般选择使用 UDP 协议。

使用java编写两种通信协议都不需要额外的导入其他jar包。

使用JAVA编写UDP协议的通信实例

发信部分

发信部分需要创建两个实例,DatagramSocket 和 DatagramPacket,另外还需要将信息转换为 byte 的形式,方便传送,代码如下:

  1. public class SocketSender {  
  2.     public static String sender(String msg) throws Exception {  
  3.         // 创建 datagramSocket对象,可以指定端口号和IP地址  
  4.         DatagramSocket sendSocket = new DatagramSocket();  
  5.         // 转换报文为byte类型  
  6.         byte[] buf = msg.getBytes();  
  7.         // 获取本机IP地址  
  8.         InetAddress ip = InetAddress.getLocalHost();  
  9.         int port = 1111;  
  10.         DatagramPacket sendPacket = new DatagramPacket(buf, buf.length, ip, port);  
  11.         sendSocket.send(sendPacket);  
  12.         sendSocket.close();  
  13.         return "ok";  
  14.     }  
  15.  

收信部分

收信部分与发信部分基本上一致,使用的也是之前的两个实例,只是发信使用的是 send() 方法,而收信使用的是 receive() 方法。

  1. public class SocketReceiver {  
  2.     public static String receiver(int port) throws Exception {  
  3.         // 获取本机IP地址  
  4.         InetAddress ip = InetAddress.getLocalHost();  
  5.         DatagramSocket receiver = new DatagramSocket(port, ip);  
  6.         byte[] recMsg = new byte[1024];  
  7.         DatagramPacket getPacket = new DatagramPacket(recMsg, recMsg.length);  
  8.         receiver.receive(getPacket);  
  9.         // 对接收到了 byte数组进行转换  
  10.         String res = new String(recMsg, 0, getPacket.getLength());  
  11.         receiver.close();  
  12.         return res;  
  13.     }  

接收端和发送端

收信和送信的类编写好了,就可以编写对应的接收端和发送端了,另外创建两个类,并分别编写可执行的main方法,此外在程序主体中使用了死循环的方式,可以让程序连续的监听消息或者发送消息。

发送端

  1. public class Client {  
  2.     public static void main(String[] args) {  
  3.         System.out.println("我是客户端,请输入要发送的信息");  
  4.         while (true) {  
  5.             Scanner sc = new Scanner(System.in);  
  6.             String msg = sc.next();  
  7.             String res = null;  
  8.             try {  
  9.                 res = SocketSender.sender(msg);  
  10.             } catch (Exception e) {  
  11.                 e.printStackTrace();  
  12.             }  
  13.             System.out.println(res);  
  14.         }  
  15.     }  

收信端

  1. public class Server {  
  2.     public static void main(String[] args) {  
  3.         System.out.println("我是接收端,正在监听消息");  
  4.         while (true) {  
  5.             String res = null;  
  6.             try {  
  7.                 res = SocketReceiver.receiver(1111);  
  8.             } catch (Exception e) {  
  9.                 e.printStackTrace();  
  10.             }  
  11.             System.out.println("接收到消息,消息内容:" + res);  
  12.         }  
  13.     }  

使用JAVA编写TCP的通信实例

TCP的通信在 java 中使用,则是实例化了 socket ,发送数据和接收数据则是分别实例化 OutputStream 和 InputStream ,并对其进行操作。

发信部分

  1. public class TCPSender {  
  2.     public static String sender(String msg) throws Exception {  
  3.         // 首先获取IP地址和定义端口  
  4.         String host = "localhost";  
  5.         int port = 2222;  
  6.         Socket socket = new Socket(host, port);  
  7.         OutputStream os = socket.getOutputStream();  
  8.         os.write(msg.getBytes());  
  9.         socket.close();  
  10.         return "send ok!";  
  11.     }  

收信部分

收信部分使用了一个 accept() 方法,这个方法会停止程序,直到接收到数据才会继续往下执行。

  1. public class TCPReceiver {  
  2.     public static String receiver() throws Exception{  
  3.         ServerSocket ss = new ServerSocket(2222);  
  4.         Socket socket = ss.accept();  
  5.         InputStream is = socket.getInputStream();  
  6.         byte[] buf = new byte[1024];  
  7.         String res = new String(buf,0,is.read(buf));  
  8.         ss.close();  
  9.         return res;  
  10.     }  

接收端和发送端

与之前的UDP类似。直接在main方法中使用class方法即可

发送端代码

  1. public class Client {  
  2.     public static void main(String[] args) {  
  3.         System.out.println("我是客户端,请输入要发送的信息:");  
  4.         while (true) {  
  5.             Scanner sc = new Scanner(System.in);  
  6.             String msg = sc.next();  
  7.             String res = null;  
  8.             try {  
  9.                 res = TCPSender.sender(msg);  
  10.             } catch (Exception e) {  
  11.                 e.printStackTrace();  
  12.             }  
  13.             System.out.println(res);  
  14.         }  
  15.     }  

接收端代码

  1. public class Server {  
  2.     public static void main(String[] args) {  
  3.         System.out.println("我是服务端,我接收数据,正在监听");  
  4.         while (true) {  
  5.             String res = null;  
  6.             try {  
  7.                 res = TCPReceiver.receiver();  
  8.             } catch (Exception e) {  
  9.                 e.printStackTrace();  
  10.             }  
  11.             System.out.println("接收到信息:" + res);  
  12.         }  
  13.     }  

使用swing编写图形界面

在 eclipse 中想要编写 swing 的图形界面的程序,建议事先安装 eclipse 插件:WindowBuilder

原理分析

在同一个 UI 窗口中进行发信和收信的操作,必然要使用线程,否则无法实现同时接受和发送消息的功能。

java 线程的实现,可以直接让类实现 Runnable 的接口,并且在接口中的 run() 方法编写线程方法。但是另外的问题是,实现了 Runnable 接口无法通过传统的方式传入参数和输出参数。更优的解决方案是实现另外一个多线程的接口 Callable。但是,我这里还是坚持使用了前一种线程接口。传入参数,使用在线程类中设置私有类,并且设置了 GET 和 SET 方法,使用的时候直接通过 SET 方法传入参数,而输出,则是直接在类中操作前台的 UI 元件。

发送消息的类

  1. public class Sender implements Runnable{  
  2.     //使用get set 方法传入三个参数,发送的信息,ip地址和端口  
  3.     private String msg;  
  4.     private String host;  
  5.     private int port;  
  6.       
  7.     public String getMsg() {  
  8.         return msg;  
  9.     }  
  10.  
  11.     public void setMsg(String msg) {  
  12.         this.msg = msg;  
  13.     }  
  14.  
  15.     public String getHost() {  
  16.         return host;  
  17.     }  
  18.  
  19.     public void setHost(String host) {  
  20.         this.host = host;  
  21.     }  
  22.  
  23.     public int getPort() {  
  24.         return port;  
  25.     }  
  26.  
  27.     public void setPort(int port) {  
  28.         this.port = port;  
  29.     }  
  30.  
  31.     public String sender(String msg,String host,int port) throws Exception{  
  32.         //首先获取IP地址和定义端口  
  33.         //创建socket  
  34.         Socket socket = new Socket(host, port);  
  35.         OutputStream os = socket.getOutputStream();  
  36.         //发信  
  37.         os.write(msg.getBytes("UTF-8"));  
  38.         //发送完毕,关闭socket  
  39.         socket.close();  
  40.         return "send ok!";  
  41.     }  
  42.  
  43.     @Override 
  44.     public void run() {  
  45.         try {  
  46.             sender(this.getMsg(),this.getHost(),this.getPort());  
  47.         } catch (Exception e) {  
  48.             //打印错误  
  49.             KK.msgerror(e);  
  50.             e.printStackTrace();  
  51.         }  
  52.           
  53.     }  

接收消息的类

  1. public class Receiver implements Runnable {  
  2.     // Runnable不能实现传入参数和传出参数,因此,使用 get set 方法塞入参数使用  
  3.     // port就是要传入的端口号参数  
  4.     private int port;  
  5.  
  6.     public int getPort() {  
  7.         return port;  
  8.     }  
  9.  
  10.     public void setPort(int port) {  
  11.         this.port = port;  
  12.     }  
  13.  
  14.     public String receiver(int port) throws Exception {  
  15.         ServerSocket ss = new ServerSocket(port);  
  16.         // 进行TCP握手  
  17.         Socket socket = ss.accept();  
  18.         InputStream is = socket.getInputStream();  
  19.         // 创建数据缓冲区  
  20.         byte[] buf = new byte[1024];  
  21.         // 从is中读取信息,并且转换为string类型,编码符号是UTF-8  
  22.         String res = new String(buf, 0, is.read(buf), "UTF-8");  
  23.         ss.close();  
  24.         return res;  
  25.     }  
  26.  
  27.     public void run() {  
  28.         try {  
  29.             // 使用循环,反复接受数据,否则只能接收一条数据  
  30.             while (true) {  
  31.                 // 传入参数。用的get set 方法塞入的  
  32.                 int port = this.getPort();  
  33.                 // 获取到了消息  
  34.                 String msg = receiver(port);  
  35.                 // 调用KK中自定义编写的 static 方法来对外打印消息内容  
  36.                 KK.addmsg(msg, port);  
  37.             }  
  38.         } catch (Exception e) {  
  39.             // 如果出现错误,调用KK中自定义编写的 static 方法,打印到屏幕上  
  40.             KK.msgerror(e);  
  41.             e.printStackTrace();  
  42.         }  
  43.  
  44.     }  

最后则是主程序,图形的代码,由于代码涉及到很多图形组件的信息,都是自动生成的,废话比较多,所以挑选出几端比较重要的代码:

1.设置监听端口后点击开始监听按钮的代码

  1. btnNewButton_1.addActionListener(new ActionListener() {  
  2.     public void actionPerformed(ActionEvent e) {  
  3.         // 点击开始按钮之后,开启监听  
  4.         // 获取端口号  
  5.         String port = textField_3.getText();  
  6.         // 获取的端口号是string,转换为整形  
  7.         int intport = Integer.parseInt(port);  
  8.         // 启动监听  
  9.         try {  
  10.             // 实例化收信  
  11.             Receiver re = new Receiver();  
  12.             // 塞入端口号  
  13.             re.setPort(intport);  
  14.             // 创建线程  
  15.             Thread th = new Thread(re);  
  16.             // 启动线程  
  17.             th.start();  
  18.             // 打印消息  
  19.             textArea.append("开启端口号:" + port + ",正在监听消息;");  
  20.             textArea.append("\r\n");  
  21.         } catch (Exception e1) {  
  22.             e1.printStackTrace();  
  23.         }  
  24.     }  
  25. }); 

2.点击发送消息按钮之后触发的代码

  1. btnNewButton.addActionListener(new ActionListener() {  
  2.     public void actionPerformed(ActionEvent e) {  
  3.         // 发信按钮按下之后  
  4.         // 获取对方ip、端口号,获取自己的端口号和消息内容  
  5.         String reip = txtLocalhost.getText();  
  6.         String report = textField_2.getText();  
  7.         String myport = textField_3.getText();  
  8.         String msg = textArea_1.getText();  
  9.         // 实例化发信  
  10.         Sender se = new Sender();  
  11.         // 塞入值  
  12.         se.setMsg(msg);  
  13.         se.setHost(reip);  
  14.         se.setPort(Integer.parseInt(report));  
  15.         // 创建线程  
  16.         Thread th = new Thread(se);  
  17.         // 启动线程  
  18.         th.start();  
  19.         // 获取当前时间  
  20.         Date date = new Date();  
  21.         DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
  22.         String time = df.format(date);  
  23.         // 打印信息  
  24.         textArea.append(time + " 我说:");  
  25.         textArea.append("\r\n");  
  26.         textArea.append(msg);  
  27.         textArea.append("\r\n");  
  28.         // 清空内容  
  29.         textArea_1.setText("");  
  30.     }  
  31. }); 

3.另外编写了两个 static 方法,方便发信类和收信类使用

  1. // 静态方法,用来调用,往页面上添加消息  
  2. public static void addmsg(String msg, int prot) {  
  3.     // 获取当前时间  
  4.     Date date = new Date();  
  5.     DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
  6.     String time = df.format(date);  
  7.     textArea.append(time + " 来自端口号" + prot + "的消息");  
  8.     textArea.append("\r\n");  
  9.     textArea.append(msg);  
  10.     textArea.append("\r\n");  
  11. }  
  12.  
  13. // 静态方法,用来调用 往页面上添加错误信息  
  14. public static void msgerror(Exception e) {  
  15.     textArea.append("出现错误,错误原因" + e);  
  16.     textArea.append("\r\n");  

软件的演示界面如下:

演示界面

7 条评论

Loading...
  1. Love3

    :razz: 可以像 TeamViewer 12 穿透内网吗?通讯时候支持端到端加密就好了 :twisted:

    说到UDP,怎么测试某台电脑的UDP端口是否正常呢?
    UDP端口被占用后,TCP就不能再次申请这个端口了吗? :wink:

    用TCP端口测试程序可以测试UDP端口?

    telnet 有点难用,不是像Ping即时回馈端口信息的
    开了软防便是如此,不开又感觉不安全

    2017-03-6 [回复]
  2. 姜辰

    高大上啊

    2017-03-7 [回复]
  3. themebetter

    总结得很不错。

    2017-03-13 [回复]
  4. 司马洛

    博主有空能不能把这一个包打包发给我一下 ,我是才接触java的

    2017-03-20 [回复]
  5. 幻音

    望博主有空可以发一下完整源代码

    2020-06-18 [回复]
    • 有野出没

      我文章里面的所有内容就是完整源码。

      2020-06-19 [回复]
  6. 酥炸不上班

    请问最后这段主程序有完整的吗

    2020-12-23 [回复]

发布评论