🌺个人主页:Dawn黎明开始
🎀系列专栏:Java
⭐每日一句:知不足而奋进,望远山而前行📢欢迎大家:关注🔍+点赞👍+评论📝+收藏⭐️
文章目录
在Socket编程中,编写服务器端程序需要使用ServerSocket类。ServerSocket类在java.net包中,java.net. ServerSocket继承自java.lang.Object类。ServerSocket类的主要作用是接收客户端的连接请求。
了解了ServerSocket类的构造方法后,下面学习一下ServerSocket类的常用方法
Socket类在java.net包中定义,java.net.Socket继承自java.lang.Object类。Socket类用于编写客户端程序,用户通过创建一个Socket对象建立与服务器的连接。
下面通过一个TCP通信的案例进一步学习ServerSocket类和Socket类的用法。要实现TCP通信需要创建一个服务器端程序和一个客户端程序。具体步骤如下。
步骤一:首先实现服务器端程序。
代码如下👇🏻
- public class TCPServer {
- public static void main(String[] args) throws Exception {
- Socket client = null; //声明Socket对象
- OutputStream os = null; //声明OutputStream对象
- //创建ServerSocket对象并指定端口号(7788)
- ServerSocket serverSocket = new ServerSocket(7788);
- System.out.println("服务器正在运行,等待与客户端连接");
- client = serverSocket.accept(); //程序阻塞,等待客户端连接
- os = client.getOutputStream(); //获取客户端的输出流
- System.out.println("开始与客户端交互数据");
- // 当客户端连接到服务器端时,向客户端输出数据
- os.write(("北京欢迎你!").getBytes());
- Thread.sleep(5000); //模拟执行其他功能占用的时间
- System.out.println("结束与客户端交互数据");
- os.close();
- client.close();
- }
- }
运行结果👇🏻
从图的运行结果可以看出,控制台打印出了“服务器正在运行,等待与客户端连接”,并且控制台中的光标一直在闪动,这是因为accept()方法在执行时发生阻塞,直到客户端连接之后才会结束这种阻塞状态。
步骤二:接下来编写客户端程序,并介绍如何通过客户端访问服务器端。
代码如下👇🏻
- public class TCPClient {
- public static void main(String[] args) throws Exception {
- Socket client = null; //声明Socket对象
- client = new Socket("localhost",7788); //指定连接的主机端口号
- BufferedReader buf = null; //声明BufferedReader对象,用于接收信息
- buf = new BufferedReader(
- new InputStreamReader(
- client.getInputStream() //取得客户端的输入流
- )
- );
- String str = buf.readLine(); //读取信息
- System.out.println("服务器端输出内容:"+str);
- client.close(); //关闭Socket
- buf.close(); //关闭输入流
- }
- }
运行结果👇🏻
服务器端控制台变化
在客户端创建的Socket对象成功读取服务器端发来的数据并打印在控制台后,同时服务器端程序会结束阻塞状态,并在控制台中打印出“开始与客户端交互数据”,然后向客户端发送数据“北京欢迎你!”,在服务器端休眠5秒钟后会在控制台打印出“结束与客户端交互数据”,本次通信才结束。如下图所示👇🏻。
案例演示多线程的TCP网络程序
步骤一:使用多线程的方式编写了一个服务器端程序。通过在while循环中调用accept()方法不停的接收客户端发送的请求。
代码如下👇🏻
- public class TCPServer {
- public static void main(String[] args) throws Exception {
- // 创建ServerSocket对象,监听指定的端口
- ServerSocket serverSocket = new ServerSocket(7788);
- // 使用while循环不停的接收客户端发送的请求
- while (true) {
- // 调用ServerSocket的accept()方法等待客户端的连接
- final Socket client = serverSocket.accept();
- int port = client.getPort();//获取Socket对象与服务器端连接的端口号
- System.out.println("与端口号为"+port+"的客户端连接成功!");
- // 下面的代码在步骤二中完成,开启一个新的线程处理客户端发送的数据
- }
- }
- }
步骤二:创建一个线程并开启,处理客户端发送的数据。
代码如下👇🏻
- new Thread() {
- public void run() {
- OutputStream os= null; // 定义一个输出流对象
- try {
- os = client.getOutputStream(); // 获取客户端的输出流
- System.out.println("开始与客户端交互数据");
- os.write(("北京欢迎你!").getBytes());
- Thread.sleep(5000); // 使线程休眠5000毫秒
- System.out.println("结束与客户端交互数据");
- os.close(); // 关闭输出流
- client.close(); // 关闭Socket对象
- } catch (Exception e) {
- e.printStackTrace();
- }
- };
- }.start();
步骤三:为了验证服务器端程序是否实现了多线程,这里需要再创建两个与13.2.3节中相同的客户端程序,只需修改其类名即可。客户端程序创建完成之后,首先运行本节中的服务器端程序,之后连续运行3个客户端程序。
测试过程
在测试过程中,当运行第一个客户端程序时,服务器端马上就进行数据处理,打印出“与端口号为60351的客户端连接成功!”的信息(端口是随机的,可能不同),紧接着再运行第2个和第3个客户端程序,会发现服务端也立刻做出回应,同时启动的这3个客户端都能够接收到服务端响应的信息。
运行结果👇🏻
由图可知,通过多线程的方式,可以实现多个客户端程序对同一个服务器端程序的访问。
DatagramPacket类用于封装UDP通信中发送或者接收的数据,DatagramPacket类对象也称为数据报对象。利用UDP通信时,发送端使用DatagramPacket类将数据打包,即用DatagramPacket类创建一个数据报对象,这个数据报对象包含有需要传输的数据、数据报的长度、IP地址和端口号等信息。
想要创建一个DatagramPacket对象,首先需要了解一下它的构造方法。在创建发送端和接收端的DatagramPacket对象时,使用的构造方法有所不同,接收端的构造方法只需要接收一个字节数组作为参数,用于存放接收到的数据;而发送端的构造方法不但要接收存放了发送数据的字节数组,还需要指定发送端IP地址和端口号。
DatagramSocket类用于在发送主机中建立数据报通信方式,提出发送请求,实现数据报的发送与接收。在创建发送端和接收端的DatagramSocket对象时,使用的构造方法也有所不同。
UDP连接注意事项
由于UDP连接是不可靠的通信方式,所以调用receive()方法时不一定能接收到数据,为了防止线程死掉,应该调用setSoTimeout()方法设置超时参数timeout()。另外,receive()方法和send()方法都可能产生输入、输出异常,因此都可能抛出IOException异常。
(1)创建一个用于发送数据报的DatagramPacket对象,使其包含如下信息:
1.要发送的数据
2.数据报分组的长度
3.发送目的地的主机IP地址和目的端口号
(2)在指定的或可用的本机端口创建DatagramSocket对象。
(3)调用DatagramSocket对象的send()方法,以DatagramPacket对象为参数发送数据报。
(1)创建一个用于接收数据报的DatagramSocket对象,其中包含空白数据缓冲区和指定数据报分组的长度。
(2)在指定的或可用的本机端口创建DatagramSocket对象。
(3)调用DatagramSocket对象的receive()方法,以DatagramPacket对象为参数接收数据报,接收到的信息有:
1.收到的数据报分组。
2.发送端主机的IP地址。
3.发送端主机的发送端口号。
要实现UDP通信需要创建一个发送端程序和一个接收端程序。在通信时只有接收端程序先运行,才能避免发送端发送数据时找不到接收端而造成数据丢失的问题。因此,首先需要完成接收端程序的编写,然后再完成发送端程序的编写。
步骤一:首先需要完成接收端程序的编写。
代码如下👇🏻
- public class Receiver {
- public static void main(String[] args) throws Exception {
- byte[] buf = new byte[1024]; // 创建一个字节数组,用于接收数据
- // 定义一个DatagramSocket对象,端口号为8954
- DatagramSocket ds = new DatagramSocket(8954);
- // 定义一个DatagramPacket对象,用于接收数据
- DatagramPacket dp = new DatagramPacket(buf, buf.length);
- System.out.println("等待接收数据");
- ds.receive(dp); // 接收数据
- /*调用DatagramPacket的方法获得接收到的信息包括数据的内容、长
- 度、发送的IP地址和端口号*/
- String str = new String(dp.getData(), 0, dp.getLength()) +
- " from "+ dp.getAddress().getHostAddress() + ":" + dp.getPort();
- System.out.println(str); // 打印接收到的信息
- ds.close(); // 关闭数据报连接
- }
- }
接收端程序运行结果👇🏻
从图可以看出,接收端程序运行后,程序一直处于阻塞状态,这是因为DatagramSocket的receive()方法在等待接收发送端发送过来的数据,只有接收到发送端发送的数据,该方法才会结束这种阻塞状态,程序才能继续向下执行。
步骤二:实现了接收端程序之后,接下来编写发送端程序,用于给接收端发送数据。
代码如下👇🏻
- public class Sender {
- public static void main(String[] args) throws Exception {
- // 创建一个DatagramSocket对象
- DatagramSocket ds = new DatagramSocket(3000);
- String str = "hello world"; //要发送的数据
- byte[] arr = str.getBytes(); //将定义的字符串转为字节数组
- /*创建一个要发送的数据报,数据报包括发送的数据,
- 数据的长度,接收端的IP地址以及端口号*/
- DatagramPacket dp = new DatagramPacket(arr, arr.length,
- InetAddress.getByName("localhost"), 8954);
- System.out.println("发送信息");
- ds.send(dp); // 发送数据
- ds.close(); // 释放资源
- }
- }
发送端程序运行结果👇🏻
接收端控制台变化
运行发送端程序后,接收端程序就会收到发送端发送的数据而结束阻塞状态,并打印接收的数据。
UDP程序所使用的端口号被占用时发生运行异常
需要注意的是,当UDP程序所使用的端口号被占用时,程序会抛出异常
运行结果👇🏻
出现图中所示的情况,是因为在一台计算机中,一个端口号只能被一个应用程序占用,当我们编写的UDP程序所使用的端口号已经被其他的应用程序占用时,就会出现这种情况。遇到这种情况时,可以在命令行窗口输入netstat -ano命令来查看当前计算机端口的占用情况。
netstat -ano命令运行结果👇🏻
如上图所示,显示了所有正在运行的应用程序及它们所占用的端口号。想要解决端口号占用的问题,只需关掉占用端口号的应用程序或者使用一个未被占用的端口号重新运行程序即可。
接下来通过一个案例演示使用UDP通信方式实现多线程的UDP网络程序,具体步骤如下👇🏻。
步骤一:创建接收端程序Receive类继承Thread类。
代码如下👇🏻
- class Receive extends Thread {
- public void run() {
- try {
- //创建socket相当于创建码头
- DatagramSocket socket = new DatagramSocket(6666);
- //创建packet相当于创建集装箱
- DatagramPacket packet = new DatagramPacket(new byte[1024], 1024);
- while(true) {
- socket.receive(packet);//接收货物
- byte[] arr = packet.getData();
- int len = packet.getLength();
- String ip = packet.getAddress().getHostAddress();
- System.out.println(ip + ":" + new String(arr,0,len));
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
步骤二:创建发送端程序Send类继承Thread类。
代码如下👇🏻
- class Send extends Thread {
- public void run() {
- try {
- DatagramSocket socket = new DatagramSocket();//创建socket相当于创建码头
- Scanner sc = new Scanner(System.in);
- while(true) {
- String str = sc.nextLine();
- if("quit".equals(str))
- break;
- DatagramPacket packet = new DatagramPacket(str.getBytes(),
- str.getBytes().length, InetAddress.getByName("127.0.0.1"), 6666);
- socket.send(packet);//发货
- }
- socket.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
运行结果👇🏻
本文主要讲解了TCP程序设计中相关的ServerSocket类和Socket类,并通过两个案例实现了简单的TCP通信和多线程的TCP通信;最后介绍了UDP程序设计相关的DatagramPacket类和DatagramSocket类,并通过两个的案例实现了简单的UDP通信和多线程的UDP通信。通过对本文的学习,希望大家能够对网络编程的底层原理有一个简单的了解,为以后的网络编程开发打下基础。