导图社区 Java网络通信
Internet上提供了大量有用的信息,很少有人能在接触过Internet后拒绝它的诱惑。计算机网络实现了多台计算机间的互联,使得它们彼此之间能够进行数据交流。网络应用程序就是在已连接的不同计算机上运行的程序,这些程序借助于网络协议,相互之间可以交换数据。编写网络应用程序前,首先必须明确所要使用的网络协议。TCP/IP协议是网络应用程序的首选。
编辑于2023-03-22 17:09:17 贵州微信小程序开发知识整理,系统地介绍了微信小程序开发的基础知识,帮助开发者更好地理解和掌握小程序开发的要点和流程。
SpringFramework技术的介绍,SpringFramework,通常简称为Spring,是一个开源的企业级Java应用程序框架,由Rod Johnson创建,并于2004年首次发布。Spring Framework的主要目标是简化企业级Java开发,提高开发效率和应用程序的可维护性。
Maven的特性以及使用部署方法,Maven介绍:Maven是一款为Java项目构建管理、依赖管理的工具(软件),使用Maven可以自动化构建、测试、打包和发布项目,大大提高了开发效率和质量。
社区模板帮助中心,点此进入>>
微信小程序开发知识整理,系统地介绍了微信小程序开发的基础知识,帮助开发者更好地理解和掌握小程序开发的要点和流程。
SpringFramework技术的介绍,SpringFramework,通常简称为Spring,是一个开源的企业级Java应用程序框架,由Rod Johnson创建,并于2004年首次发布。Spring Framework的主要目标是简化企业级Java开发,提高开发效率和应用程序的可维护性。
Maven的特性以及使用部署方法,Maven介绍:Maven是一款为Java项目构建管理、依赖管理的工具(软件),使用Maven可以自动化构建、测试、打包和发布项目,大大提高了开发效率和质量。
Java网络通信
Internet上提供了大量有用的信息,很少有人能在接触过Internet后拒绝它的诱惑。计算机网络实现了多台计算机间的互联,使得它们彼此之间能够进行数据交流。网络应用程序就是在已连接的不同计算机上运行的程序,这些程序借助于网络协议,相互之间可以交换数据。 编写网络应用程序前,首先必须明确所要使用的网络协议。TCP/IP协议是网络应用程序的首选。
一、 网络程序设计基础
网络程序设计编写的是与其他计算机进行通信的程序。Java已经将网络程序所需要的东西封装成不同的类,用户只要创建这些类的对象,使用相应的方法,即使不具备有关的网络知识,也可以编写出高质量的网络通信程序。
1. 局域网与因特网:为了实现两台计算机的通信,必须用一个网络线路连接两台计算机,如右图所示
服务器是指提供信息的计算机或程序,客户机是指请求信息的计算机或程序。网络用于连接服务器与客户机,实现两者间的相互通信。但有时在某个网络中很难将服务器与客户机区分开。我们通常所说的局域网(Local Area Network,LAN),就是一群通过一定形式连接起来的计算机。它可以由两台计算机组成,也可以由同一区域内的上千台计算机组成。将LAN延伸到更大的范围,这样的网络称为广域网(Wide Area Network,WAN)。我们熟悉的因特网(Internet),就是由无数的LAN和WAN组成的。
2. 网络协议:网络协议规定了计算机之间连接的物理、机械(网线与网卡的连接规定)、电气(有效的电平范围)等特征,计算机之间的相互寻址规则,数据发送冲突的解决方式,长数据如何分段传送与接收等内容。就像不同的国家有不同的法律一样,目前网络协议也有多种。下面简单地介绍几个常用的网络协议。
1||| ip协议:IP是Internet Protocol的简称,是一种网络协议。Internet网络采用的协议是TCP/IP协议,其全称是Transmission Control Protocol/Internet Protocol。Internet依靠TCP/IP协议,在全球范围内实现了不同硬件结构、不同操作系统、不同网络系统间的互联。在Internet网络上存在着数以亿计的主机,每台主机都用网络为其分配的Internet地址代表自己,这个地址就是IP地址。到目前为止,IP地址用4个字节,也就是32位的二进制数来表示,称为IPv4。为了便于使用,通常取用每个字节的十进制数,并且每个字节之间用圆点隔开来表示IP地址,如192.168.1.1。现在人们正在试验使用16个字节来表示IP地址,这就是IPv6,但IPv6还没有完全投入使用。 TCP/IP模式是一种层次结构,共分为4层,分别为应用层、传输层、互联网层和网络层。各层实现特定的功能,提供特定的服务和访问接口,并具有相对的独立性
2||| TCP与UPD协议:在TCP/IP协议栈中,有两个高级协议是网络应用程序编写者应该了解的,即传输控制协议(Transmission Control Protocol,TCP)与用户数据报协议(User Datagram Protocol,UDP)。 TCP协议是一种以固接连线为基础的协议,它提供两台计算机间可靠的数据传送。TCP可以保证从一端数据送至连接的另一端时,数据能够确实送达,而且抵达的数据的排列顺序和送出时的顺序相同。因此,TCP协议适合可靠性要求比较高的场合。就像拨打电话,必须先拨号给对方,等两端确定连接后,相互才能听到对方说话,也知道对方回应的是什么。 HTTP、FTP和Telnet等都需要使用可靠的通信频道。例如,HTTP从某个URL读取数据时,如果收到的数据顺序与发送时不相同,可能就会出现一个混乱的HTML文件或是一些无效的信息。 UDP是无连接通信协议,不保证数据的可靠传输,但能够向若干个目标发送数据,或接收来自若干个源的数据。UDP以独立发送数据包的方式进行。这种方式就像邮递员送信给收信人,可以寄出很多信给同一个人,且每一封信都是相对独立的,各封信送达的顺序并不重要,收信人接收信件的顺序也不能保证与寄出信件的顺序相同。 UDP协议适合于一些对数据准确性要求不高但,对传输速度和时效性要求非常高的网站,如网络聊天室、在线影片等。这是由于TCP协议在认证上存在额外耗费,可能使传输速度减慢,而UDP协议即使有一小部分数据包遗失或传送顺序有所不同,也不会严重危害该项通信。
一些防火墙和路由器会设置成不允许UDP数据包传输,因此若遇到UDP连接方面的问题,应先确定所在网络是否允许UDP协议。
3. 端口和套接字:一般而言,一台计算机只有单一的连到网络的物理连接(Physical Connection),所有的数据都通过此连接对内、对外送达特定的计算机,这就是端口。网络程序设计中的端口(port)并非真实的物理存在,而是一个假想的连接装置。端口被规定为一个在0~65535之间的整数。HTTP服务一般使用80端口,FTP服务使用21端口。假如一台计算机提供了HTTP、FTP等多种服务,那么客户机会通过不同的端口来确定连接到服务器的哪项服务上。 通常,0~1023之间的端口数用于一些知名的网络服务和应用,用户的普通网络应用程序应该使用1024以上的端口数,以避免端口号与另一个应用或系统服务所用端口冲突。 网络程序中的套接字(Socket)用于将应用程序与端口连接起来。套接字是一个假想的连接装置,就像插座一样可连接电器与电线,Java将套接字抽象化为类,程序设计者只需创建Socket类对象,即可使用套接字。
二、 TCP程序设计基础
1. TCP网络程序设计是指利用Socket类编写通信程序。利用TCP协议进行通信的两个应用程序是有主次之分的,一个称为服务器程序,另一个称为客户机程序,两者的功能和编写方法大不一样。服务器端与客户端的交互过程。如右图所示
1||| 服务器程序创建一个ServerSocket(服务器端套接字),调用accept()方法等待客户机来连接。
2||| 客户端程序创建一个Socket,请求与服务器建立连接。
3||| 服务器接收客户机的连接请求,同时创建一个新的Socket与客户建立连接。随后服务器继续等待新的请求。
2. InetAddress类:java.net包中的InetAddress类是与IP地址相关的类,利用该类可以获取IP地址、主机地址等信息。InetAddress类的常用方法如右所示。
1||| getByName(String host):获取与Host相对应的InetAddress对象
返回值:InetAddress
2||| getHostAddress():获取InetAddress对象所包含的IP地址
返回值:String
3||| getHostName():获取此IP的主机名
返回值:String
4||| getLocalHost():返回本地主机的InetAddress对象
返回值:InetAddress
实例代码: package com.INTERNET; import java.net.*; public class Address { public static void main(String[] args) { InetAddress ip; //创建InetAddress对象 try{ ip=InetAddress.getLocalHost(); //实例化对象 String localname=ip.getHostName(); //获取本机名 String localip=ip.getHostAddress(); //获取本机ip地址 System.out.println("本机名:"+localname); System.out.println("本机ip地址"+localip); }catch(UnknownHostException e) { e.printStackTrace(); } } }
InetAddress类的方法会抛出UnknownHostException异常,所以必须进行异常处理。这个异常在主机不存在或网络连接错误时发生。
运行结果: 本机名:DESKTOP 本机ip地址192.168.31.12
3. ServerSocket类:java.net包中的ServerSocket类用于表示服务器套接字,其主要功能是等待来自网络上的“请求”,它可通过指定的端口来等待连接的套接字。服务器套接字一次可以与一个套接字连接。如果多台客户机同时提出连接请求,服务器套接字会将请求连接的客户机存入列队中,然后从中取出一个套接字,与服务器新建的套接字连接起来。若请求连接数大于最大容纳数,则多出的连接请求被拒绝。队列的默认大小是50。ServerSocket类的常用方法如右所示。
1||| accept():等待客户机的连接。若连续,则创建一个套接字
accept()方法会阻塞线程的继续执行,直到接收到客户的呼叫。如果没有客户呼叫服务器,那么System.out.println("连接中")语句将不会执行。语句如果没有客户请求,accept()方法没有发生阻塞,肯定是程序出现了问题。通常是使用了一个被其他程序占用的端口号,ServerSocket绑定没有成功。
返回值:Socket
2||| isBound():判断ServerSocket的绑定状态
返回值:boolean
3||| getInetAddress():返回此服务器套接字的本地地址
返回值:InetAddress
4||| isClosed():返回服务器套接字的关闭状态
返回值:boolean
5||| close():关闭服务器套接字
返回值:void
6||| bind(SocketAddress cndpoint):将ServerSocket绑定到特定地址(ip地址和端口号)
返回值:void
7||| getInetAddress():返回服务器套接字等待的端口号
返回值:int
调用ServerSocket类的accept()方法,会返回一个和客户端Socket对象相连接的Socket对象。服务器端的Socket对象使用getOutputStream()方法获得的输出流,将指向客户端Socket对象使用getInputStream()方法获得的那个输入流;同样,服务器端的Socket对象使用getInputStream()方法获得的输入流,将指向客户端Socket对象使用getOutputStream()方法获得的那个输出流。也就是说,当服务器向输出流写入信息时,客户端通过相应的输入流就能读取,反之亦然。
4. TCP网络程序:明白了TCP程序工作的过程,就可以编写TCP服务器程序了。在网络编程中如果只要求客户机向服务器发送消息,不要求服务器向客户机发送消息,称为单向通信。客户机套接字和服务器套接字连接成功后,客户机通过输出流发送数据,服务器则通过输入流接收数据。
服务器端实例代码: package com.INTERNET; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; public class TCP_Test { private BufferedReader reader; //创建BufferedReader对象 private ServerSocket serversocket; //创建ServerSocket对象 private Socket socket; //创建Socket对象 void getserver(){ try { serversocket=new ServerSocket(1010); //实例化ServerSocket对象 System.out.println("服务器套接字创建完毕"); while(true) { System.out.println("等待客户机连接...."); socket=serversocket.accept(); //实例化Socket对象 reader=new BufferedReader(new InputStreamReader(socket.getInputStream())); //实例化BufferdReader对象 getClientMessage(); //调用方法 } }catch(Exception e) { e.printStackTrace(); } } private void getClientMessage() { try { while(true) { System.out.println("客户机"+reader.readLine()); //如果套接字是连接状态则获得客户端信息 } }catch(Exception e) { e.printStackTrace(); } try{ if(reader!=null) { reader.close(); //关闭流 } if (socket!=null) { socket.close(); //关闭套接字 } }catch(IOException e) { e.printStackTrace(); } } public static void main(String[] args) { TCP_Test tcp=new TCP_Test(); tcp.getserver(); } }
客户端实例代码: package com.INTERNET; import javax.swing.*; import javax.swing.border.BevelBorder; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.PrintWriter; import java.net.Socket; public class TCP_Client extends JFrame { private PrintWriter writer; //声明PrintWriter对象 Socket socket; private JTextField tf=new JTextField(":"); private JTextArea ta=new JTextArea(); public TCP_Client() { JFrame frame=new JFrame("客户端发送数据"); Container container=frame.getContentPane(); //获取容器 final JScrollPane scrollPane=new JScrollPane(); scrollPane.setBorder(new BevelBorder(BevelBorder.RAISED)); //设置边框类型 frame.add(scrollPane,BorderLayout.CENTER); //添加面板,并设置位置居中 frame.add(tf,"South"); //将文本框放在窗体下部 scrollPane.setViewportView(ta); //向滚动面板中添加文本区域 frame.setBounds(200,200,580,460); frame.setVisible(true); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); tf.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { writer.println(tf.getText()); //将文本框中的信息写入流 ta.append(tf.getText()+"\n"); //将文本框中的信息显示在文本区域中 ta.setSelectionEnd(ta.getText().length()); tf.setText(":"); //清空文本框 } }); } private void connect() { ta.append("尝试连接服务器\n"); try { socket=new Socket("192.168.31.166",1010); //实例化Socket对象 writer=new PrintWriter(socket.getOutputStream(),true); ta.append("完成连接\n"); //文本域中提示信息 }catch(Exception e) { e.printStackTrace(); //输出异常信息 } } public static void main(String[] args) { TCP_Client tc=new TCP_Client(); tc.connect(); } }
运行服务器端,再运行客户端,运行结果如下:
三、 UDP程序设计基础
1. 用户数据报协议(UDP)是网络信息传输的另一种形式。基于UDP的通信和基于TCP的通信不同,基于UDP的信息传递更快,但不提供可靠的保证。使用UDP传递数据时,用户无法知道数据能否正确地到达主机,也不能确定到达目的地的顺序是否和发送的顺序相同。虽然UDP是一种不可靠的协议,但如果需要较快地传输信息,并能容忍小的错误,可以考虑使用UDP。基于UDP通信的基本模式如下: 1.将数据打包(称为数据包),然后将数据包发往目的地。 2.接收别人发来的数据包,然后查看数据包。
发送数据包的步骤
1||| 使用DatagramSocket()创建一个数据包套接字
2||| 使用DatagramPacket(byte[ ] buf,int offset,int length,InetAddress address,int port)创建要发送的数据包
3||| 使用DatagramSocket类的sent()方法发送数据包
接收数据包的步骤
1||| 使用DatagramSocket(int port)创建数据包套接字,绑定到指定的端口
2||| 使用DatagramPacket(byte[] buf, int length)创建字节数组来接收数据包
3||| 使用DatagramSocket类的receive()方法接收UDP包
DatagramSocket类的receive()方法接收数据时,如果还没有可以接收的数据,在正常情况下receive()方法将阻塞,一直等到网络上有数据传来,receive()方法接收该数据并返回。如果网络上没有数据发送过来,receive()方法也没有阻塞,肯定是程序有问题,大多数情况下是因为使用了一个被其他程序占用的端口号。
2. DatagramPacket类,java.net包的DatagramPacket类用来表示数据包。DatagramPacket类的构造函数有:
1||| DatagramPacket(byte[ ] buf,int length)
2||| DatagramPacket(byte[ ] buf,int length,InetAddress address,int port)
第一种构造函数在创建DatagramPacket对象时,指定了数据包的内存空间和大小。第二种构造函数不仅指定了数据包的内存空间和大小,还指定了数据包的目标地址和端口。在发送数据时,必须指定接收方的Socket地址和端口号,因此使用第二种构造函数可创建发送数据的DatagramPacket对象。
3. DatagramSocket类,java.net包中的DatagramSocket类用于表示发送和接收数据包的套接字。该类的构造函数有:
1||| DatagramSocket()
2||| DatagramSocket(int port)
3||| DatagramSocket(int port,InetAddress address)
第一种构造函数创建DatagramSocket对象,构造数据报套接字,并将其绑定到本地主机任何可用的端口上。第二种构造函数创建DatagramSocket对象,创建数据报套接字,并将其绑定到本地主机的指定端口上。第三种构造函数创建DatagramSocket对象,创建数据报套接字,并将其绑定到指定的本地地址上。第三种构造函数适用于有多块网卡和多个IP地址的情况。 在接收程序时必须指定一个端口号,不允许系统随机产生,此时可以使用第二种构造函数。比如有个朋友要你给他写信,那他的地址就必须确定,不确定是不行的。在发送程序时通常使用第一种构造函数,不指定端口号,而是系统为我们分配一个端口号,就像寄信不需要到指定的邮局去寄一样。
4. UDP网络程序
发送端实例代码: package com.INTERNET; import java.net.DatagramPacket; import java.net.InetAddress; import java.net.MulticastSocket; public class UDP_Sent extends Thread{ String weather="你嫌弃破澜~,抛弃了我~"; int port=2020; //定义端口号 InetAddress inetAddress=null; //创建InetAddress对象 MulticastSocket socket=null; //声明多点广播套接字 UDP_Sent(){ //构造方法 try { inetAddress=InetAddress.getByName("224.255.10.0"); //实例化对象并指定地址 socket=new MulticastSocket(port); //实例化多点广播套接字 socket.setTimeToLive(1); //指定发送范围是本地网络 socket.joinGroup(inetAddress); //加入广播组 }catch(Exception e) { e.printStackTrace(); } } public void run() { while(true) { DatagramPacket packet=null; //声明DatagramPacket对象 byte data[]=weather.getBytes(); //声明字节数组 packet=new DatagramPacket(data, data.length,inetAddress,port); //打包数据 String s1=new String(data); System.out.println(s1); try{ socket.send(packet); //发送数据 sleep(3000); //线程休眠 }catch(Exception e) { e.printStackTrace(); } } } public static void main(String[] args) { UDP_Sent u=new UDP_Sent(); u.start(); } }
接收端实例代码: package com.INTERNET; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.net.DatagramPacket; import java.net.InetAddress; import java.net.MulticastSocket; public class UDP_Receive extends JFrame implements Runnable, ActionListener { int port; //定义端口 InetAddress group=null; //声明InetAddress对象 MulticastSocket socket=null; //创建多点广播套接字对象 JButton rec=new JButton("开始接收"); JButton stop=new JButton("结束接收"); JTextArea recA=new JTextArea(10,10); JTextArea recd=new JTextArea(10,10); Thread thread; //创建Thread对象 boolean b=false; public UDP_Receive() { JFrame frame=new JFrame("广播数据测试"); Container container=frame.getContentPane(); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); thread=new Thread(this); rec.addActionListener(this); //添加按钮监听 stop.addActionListener(this); recA.setForeground(Color.CYAN); //设置文本域中的字体颜色 JPanel panel_north=new JPanel(); panel_north.add(rec); panel_north.add(stop); //将按钮添加到该面板里 frame.add(panel_north,BorderLayout.NORTH); JPanel panel_center=new JPanel(); panel_center.setLayout(new GridLayout(1,2)); panel_center.add(recA); panel_center.add(recd); frame.add(panel_center,BorderLayout.CENTER); //设置面板布局 validate(); //刷新 port=2020; //设置端口号 try{ group=InetAddress.getByName("224.255.10.0"); //指定接收地址 socket=new MulticastSocket(port); //绑定多点广播套接字 socket.joinGroup(group); //加入广播组 }catch(Exception e) { e.printStackTrace(); } frame.setBounds(200,200,560,480); frame.setVisible(true); } public void run() { while(true) { byte data[]=new byte[1024]; //创建byte数组 DatagramPacket packet=null; //创建DatagramPacket对象 packet=new DatagramPacket(data,data.length,group,port); //待接收的数据包 try{ socket.receive(packet); //接收数据包 String message=new String(packet.getData(),0, packet.getLength()); //获取数据包中的内容 recA.setText("正在接收消息:\n"+"波澜哥:"); recd.append(message+"\n"); }catch(Exception e) { e.printStackTrace(); } if (b=true){ break; } } } public void actionPerformed(ActionEvent event) { if (event.getSource()==rec) { //单击接收按钮时触发事件 rec.setBackground(Color.red); //设置按钮颜色 stop.setBackground(Color.yellow); if(!(thread.isAlive())){ thread=new Thread(this); //如果线程不处于新建状态就实例化新对象 } thread.start(); //启动线程 b=false; } if (event.getSource()==stop) { rec.setBackground(Color.yellow); stop.setBackground(Color.red); b=true; } } public static void main(String[] args) { UDP_Receive ur=new UDP_Receive(); } }
运行结果: