TCP程序

TCP 网络程序设计是指利用 Socket 类编写通信程序。利用 TCP 协议进行通信的两个应用程序是有主次之分的,一个被称为服务器程序,另一个被称为客户机程序,二者的功能和编写方法大不一样。服务器端与客户端的交互过程如图22.5所示。

image 2024 03 06 14 24 46 101
Figure 1. 图22.5 服务器端与客户端的交互

①—服务器程序创建一个 ServerSocket(服务器端套接字)对象,调用 accept() 方法等待客户机来连接。

②—客户端程序创建一个 Socket 对象,请求与服务器建立连接。

③—服务器接收客户机的连接请求,同时创建一个新的 Socket 对象与客户建立连接。随后服务器继续等待新的请求。

InetAddress类

java.net 包中的 InetAddress 类是与 IP 地址相关的类,利用该类可以获取 IP 地址、主机地址等信息。InetAddress 类的常用方法如表22.1所示。

image 2024 03 06 14 25 55 859
Figure 2. 表22.1 InetAddress类的常用方法

【例22.1】获取计算机的本机名与IP地址(实例位置:资源包\TM\sl\22\1)

使用 InetAddress 类的 getHostName() 和 getHostAddress() 方法获得本地主机的本机名、本机 IP 地址。

import java.net.*; 									//导入java.net包
public class Address { 								//创建类
	public static void main(String[] args) {
		InetAddress ip; 							//创建InetAddress对象
		try { 									//使用try语句块捕捉可能出现的异常
			ip = InetAddress.getLocalHost(); 			//实例化对象
			String localname = ip.getHostName(); 		//获取本机名
			String localip = ip.getHostAddress(); 		//获取本机IP地址
			System.out.println("本机名:" + localname);	//将本机名输出
			System.out.println("本机IP地址:" + localip); 	//将本机IP地址输出
		} catch (UnknownHostException e) {
			e.printStackTrace(); 						//输出异常信息
		}
	}
}

该实例在不同系统、不同网络环境下运行的结果会不同,例如笔者的运行结果如下:

     本机名:SC-202004221619
     本机IP地址:192.168.56.1

InetAddress 类的方法会抛出 UnknownHostException 异常,因此必须进行异常处理。这个异常在主机不存在或网络连接错误时发生。

ServerSocket类

java.net 包中的 ServerSocket 类用于表示服务器套接字,其主要功能是等待来自网络上的 “请求”,它可通过指定的端口来等待连接的套接字。服务器套接字一次可以与一个套接字连接。如果多台客户机同时提出连接请求,则服务器套接字会将请求连接的客户机存入列队中,然后从中取出一个套接字,与服务器新建的套接字连接起来。若请求连接数大于最大容纳数,则多出的连接请求被拒绝。队列的默认大小是 50。

ServerSocket 类的构造方法通常会抛出 IOException 异常,具体有以下几种形式。

  • ServerSocket():创建非绑定服务器套接字。

  • ServerSocket(int port):创建绑定到特定端口的服务器套接字。

  • ServerSocket(int port, int backlog):利用指定的 backlog 创建服务器套接字,并将其绑定到指定的本地端口号上。

  • ServerSocket(int port, int backlog, InetAddress bindAddress):使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址创建服务器。这种情况适用于计算机上有多块网卡和多个 IP 地址的情况,用户可以明确规定 ServerSocket 在哪块网卡或哪个 IP 地址上等待客户的连接请求。

ServerSocket 类的常用方法如表22.2所示。

image 2024 03 06 14 50 40 447
Figure 3. 表22.2 ServerSocket类的常用方法

调用 ServerSocket 类的 accept() 方法,会返回一个和客户端 Socket 对象相连接的 Socket 对象。服务器端的 Socket 对象使用 getOutputStream() 方法获得的输出流,将指向客户端 Socket 对象使用 getInputStream() 方法获得的那个输入流;同样,服务器端的 Socket 对象使用 getInputStream() 方法获得的输入流,将指向客户端 Socket 对象使用 getOutputStream() 方法获得的那个输出流。也就是说,当服务器向输出流中写入信息时,客户端通过相应的输入流就能读取,反之亦然。

accept() 方法会阻塞线程的继续执行,直到服务器接收到客户的呼叫。如果客户没有呼叫服务器,那么 System.out.println("连接中") 语句将不会被执行。如果服务器没有接收到客户的请求,accept() 方法就没有发生阻塞,肯定是程序出现了问题。通常是使用了一个被其他程序占用的端口号,ServerSocket 绑定没有成功。

yu = server.accept();
System.out.println("连接中");

TCP网络程序设计

明白了 TCP 程序工作的过程,就可以编写 TCP 服务器程序了。在网络编程中,如果只要求客户机向服务器发送消息,不要求服务器向客户机发送消息,则被称为单向通信。客户机套接字和服务器套接字连接成功后,客户机通过输出流发送数据,服务器则通过输入流接收数据。下面是简单的单向通信的实例。

【例22.2】创建TCP/IP协议服务器(实例位置:资源包\TM\sl\22\2)

本实例是一个 TCP 服务器端程序,在 getserver() 方法中建立服务器套接字,调用 getClientMessage() 方法获取客户机信息。

import java.io.*;
import java.net.*;

public class MyServer {
	private ServerSocket server; // 服务器套接字
	private Socket socket; // 客户端套接字

	void start() {// 启动服务器
		try {
			server = new ServerSocket(8998); // 服务器启用8998端口
			System.out.println("服务器套接字已经创建成功");
			while (true) {
				System.out.println("等待客户端的连接");
				socket = server.accept(); // 服务器监听客户端连接
				// 根据套接字字节流创建字符输入流
				BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
				while (true) {// 循环接受信息
					String message = reader.readLine();// 读取一行文本
					if ("exit".equals(message)) {// 如果客户端发来的内容为“exit”
						System.out.println("客户端退出");
						break;// 停止接受信息
					}
					System.out.println("客户端:" + message);
				}
				reader.close(); // 关闭流
				socket.close(); // 关闭套接字
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) {
		MyServer tcp = new MyServer();
		tcp.start(); // 启动服务器
	}
}

运行结果如图22.6所示。

image 2024 03 06 14 53 52 351
Figure 4. 图22.6 例22.2的运行结果

运行服务器端程序,将输出提示信息,等待客户呼叫。下面再来看客户端程序。

编写客户端程序,将用户在文本框中输入的信息发送至服务器端,并将文本框中输入的信息显示在客户端的文本域中。

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.Socket;
import javax.swing.*;

public class MyClient extends JFrame {
	private PrintWriter writer;// 根据套接字字节流创建的字符输出流
	Socket socket; // 客户端套接字
	private JTextArea area = new JTextArea();// 展示信息的文本域
	private JTextField text = new JTextField(); // 发送信息的文本框

	public MyClient() {
		setTitle("向服务器送数据");
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		Container c = getContentPane(); // 主容器
		JScrollPane scrollPane = new JScrollPane(area);// 滚动面板
		getContentPane().add(scrollPane, BorderLayout.CENTER);
		c.add(text, "South"); // 将文本框放在窗体的下部
		text.addActionListener(new ActionListener() {// 文本框触发回车事件
			public void actionPerformed(ActionEvent e) {
				writer.println(text.getText().trim()); // 将文本框中的信息写入流
				area.append(text.getText() + '\n'); // 将文本框中的信息显示在文本域中
				text.setText(""); // 将文本框清空
			}
		});
	}

	private void connect() { // 连接服务器方法
		area.append("尝试连接\n"); // 文本域中提示信息
		try {
			socket = new Socket("127.0.0.1", 8998); // 连接本地计算机的8998端口
			writer = new PrintWriter(socket.getOutputStream(), true);
			area.append("完成连接\n");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) {
		MyClient clien = new MyClient();
		clien.setSize(200, 200); // 窗体大小
		clien.setVisible(true); // 显示窗体
		clien.connect(); // 连接服务器
	}
}

先运行例22.2的服务器端程序,再运行这个客户端程序,运行结果如图22.7所示。

从图22.7中可以看出,客户端与服务器端已经创建了连接。向文本框中输入信息,会发现输入的信息被输出到服务器端,并显示在客户端的文本域中,如图22.8和图22.9所示。

image 2024 03 06 14 55 20 354
Figure 5. 图22.7 升级后的运行结果
image 2024 03 06 14 55 41 467
Figure 6. 图22.8 服务器端运行结果
image 2024 03 06 14 56 18 112
Figure 7. 图22.9 客户端运行结果

当一台机器上安装了多个网络应用程序时,很可能指定的端口号已被占用。还可能遇到以前运行良好的网络程序突然运行不了的情况,这种情况很可能也是由于端口号被别的程序占用了。此时可以运行 netstat-help 来获得帮助,使用 netstat -an 命令来查看该程序使用的端口号,如图21.10所示。

image 2024 03 06 14 57 09 628
Figure 8. 图22.10 查看端口号

编程训练(答案位置:资源包\TM\sl\22\编程训练)

【训练1】获取内网所有IP地址 在进行网络编程时,有时需要对局域网内的所有主机进行遍历,为此需要获取内网的所有IP地址。请编写程序演示如何在Java应用程序中获取内网的所有IP地址,效果如图22.11所示。

【训练2】一对一聊天程序 在使用套接字进行网络编程时,需要在服务器端和客户端之间进行通信,请编写程序实现一个服务器与一个客户端之间的通信,效果如图22.12和图22.13所示。

image 2024 03 06 14 57 51 233
Figure 9. 图22.11 获取内网的所有IP地址
image 2024 03 06 14 58 15 671
Figure 10. 图22.12 服务器端发送和接收信息的效果
image 2024 03 06 14 58 45 559
Figure 11. 图22.13 客户端接收和发送信息的效果