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

①—服务器程序创建一个 ServerSocket(服务器端套接字)对象,调用 accept() 方法等待客户机来连接。
②—客户端程序创建一个 Socket 对象,请求与服务器建立连接。
③—服务器接收客户机的连接请求,同时创建一个新的 Socket 对象与客户建立连接。随后服务器继续等待新的请求。
InetAddress类
java.net 包中的 InetAddress 类是与 IP 地址相关的类,利用该类可以获取 IP 地址、主机地址等信息。InetAddress 类的常用方法如表22.1所示。

【例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所示。

调用 ServerSocket 类的 accept() 方法,会返回一个和客户端 Socket 对象相连接的 Socket 对象。服务器端的 Socket 对象使用 getOutputStream() 方法获得的输出流,将指向客户端 Socket 对象使用 getInputStream() 方法获得的那个输入流;同样,服务器端的 Socket 对象使用 getInputStream() 方法获得的输入流,将指向客户端 Socket 对象使用 getOutputStream() 方法获得的那个输出流。也就是说,当服务器向输出流中写入信息时,客户端通过相应的输入流就能读取,反之亦然。
accept() 方法会阻塞线程的继续执行,直到服务器接收到客户的呼叫。如果客户没有呼叫服务器,那么 System.out.println("连接中") 语句将不会被执行。如果服务器没有接收到客户的请求,accept() 方法就没有发生阻塞,肯定是程序出现了问题。通常是使用了一个被其他程序占用的端口号,ServerSocket 绑定没有成功。
|
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所示。

运行服务器端程序,将输出提示信息,等待客户呼叫。下面再来看客户端程序。
编写客户端程序,将用户在文本框中输入的信息发送至服务器端,并将文本框中输入的信息显示在客户端的文本域中。
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所示。



当一台机器上安装了多个网络应用程序时,很可能指定的端口号已被占用。还可能遇到以前运行良好的网络程序突然运行不了的情况,这种情况很可能也是由于端口号被别的程序占用了。此时可以运行 netstat-help 来获得帮助,使用 netstat -an 命令来查看该程序使用的端口号,如图21.10所示。 ![]() Figure 8. 图22.10 查看端口号
|
编程训练(答案位置:资源包\TM\sl\22\编程训练)
【训练1】获取内网所有IP地址 在进行网络编程时,有时需要对局域网内的所有主机进行遍历,为此需要获取内网的所有IP地址。请编写程序演示如何在Java应用程序中获取内网的所有IP地址,效果如图22.11所示。
【训练2】一对一聊天程序 在使用套接字进行网络编程时,需要在服务器端和客户端之间进行通信,请编写程序实现一个服务器与一个客户端之间的通信,效果如图22.12和图22.13所示。


