目录
TCP 网络编程
- TCP 通信同 UDP 通信一样,都能实现两台计算机之间的通信,但 TCP 通信的两端需要创建 Socket 对象。UDP 通信与 TCP 通信的区别在于:UDP 中只有发送端和接收端,不区分客户端与服务器端,计算机之间可以任意地发送数据;而 TCP 通信是严格区分客户端与服务器端的,在通信时,必须先由客户端主动连接服务器端才能实现通信,服务器端不可以主动连接客户端,并且服务器端程序需要事先启动,等待客户端的连接;
- 在 JDK 中提供了两个用于实现 TCP 程序的类:一个是 ServerSocket 类,用于表示服务器端;另一个是 Socket 类,用于表示客户端。通信时,首先要创建代表服务器端的 ServerSocket 对象,创建该对象相当于开启一个服务,此服务会等待客户端的连接;然后创建代表客户端的 Socket 对象,使用该对象向服务器端发出连接请求,服务器端响应请求后,两者才建立连接,开始通信;
1. ServerSocket 类
- 在开发 TCP 程序时,首先需要创建服务器端程序。JDK 的 java.net 包中提供了 ServerSocket 类,该类的实例对象可以实现一个服务器端的程序。ServerSocket 类提供了多种构造方法:
(1)ServerSocket()
(2)ServerSocket(int port)
(3)ServerSocket(int port, int backlog)
(4)ServerSocket(int port, int backlog, InetAddress bindAddr)
- java.net.ServerSocket,详见:Class ServerSocket;
a. 服务器端程序
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class Test {
public static void main(String[] args) {
ServerSocket server_socket = null;
Socket socket = null;
DataInputStream in = null;
DataOutputStream out = null;
int port = 5050;
try {
server_socket = new ServerSocket(port); // 创建绑定端口的服务器端Socket
} catch (IOException e) {
System.out.println(e);
}
try {
System.out.println("服务器启动!");
socket = server_socket.accept();
// 监听并接受到此Socket 的连接。此方法在连接传入之前处于阻塞状态
in = new DataInputStream(socket.getInputStream()); // 创建输入流
out = new DataOutputStream(socket.getOutputStream()); // 创建输出流
String str = in.readUTF(); // 从输入流读取字符串,读取结束之前处于阻塞状态
System.out.println("客户机发送过来的信息是: " + str);
out.writeUTF("你好,我是服务器B"); // 向输出流写入字符串
} catch (Exception e) {
System.out.println(e);
} finally {
try// 关闭网络连接
{
out.close();
in.close();
socket.close();
server_socket.close();
} catch (Exception e) {
}
}
}
}
- 该程序首先创建了绑定 5050 端口的服务器端 Socket,并进行连接监听。如果有连接要求,则创建 Socket 连接,并且建立输入流对象和输出流对象。通过输入流对象读取客户端发来的内容。通过输出流对象向客户端发送相应内容;
2. Socket 类
- 使用 ServerSocket 对象可以实现服务器端程序,但只实现服务器端程序还不能完成通信,此时还需要一个客户端程序与之交互,为此 JDK 提供了一个 Socket 类,用于实现 TCP 客户端程序,Socket 类的常用构造方法:
(1)Socket()
(2)Socket(String host, int port)
(3)Socket(InetAddress address, int port)
- java.net.Socket,详见:Class Socket;
a. 客户端程序
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
public class Test {
public static void main(String[] args) {
Socket client_socket = null;
DataInputStream in = null;
DataOutputStream out = null;
String ip = "127.0.0.1"; // 服务器IP 地址
int port = 5050; // 服务器端口号
try {
client_socket = new Socket(ip, port); // 与服务器建立连接
in = new DataInputStream(client_socket.getInputStream()); // 创建输入流
out = new DataOutputStream(client_socket.getOutputStream()); // 创建输出流
out.writeUTF("你好,我是客户机A"); // 向服务器端发送信息
System.out.println("客户机启动,向服务器发送信息:你好,我是客户机A");
String str = in.readUTF();// 等待读取服务器端响应的信息,进入阻塞状态
System.out.println("服务器端的响应信息:" + str);
} catch (Exception e) {
System.out.println(e);
} finally {
try// 关闭网络连接
{
in.close();
out.close();
client_socket.close();
} catch (Exception e) {
}
}
}
}
- 本例中的客户端程序必须在服务器端程序运行后再运行,当服务器端程序处于运行状态时,客户端向其发出链接请求。链接建立成功,客户端通过输出流向服务器端发送请求内容,并从输入流读取服务器响应的内容,最后关闭链接;
3. 多线程的 TCP 网络编程
- 如果需要进行多次数据交互,就可以在程序中设置一个循环,不断地向对方发送请求,即可完成多次数据交互。同样,如果需要让服务器端同时响应多个客户端的请求,可以使用多线程的方法,也就是服务器端每接收到一个新的连接请求,就启动一个专门的线程与该客户端进行交互;
a. 用多线程实现四则运算
- 本例分为 3 个类:客户端类、服务器端类和逻辑线程类;
(1)客户端类:主要功能是与服务器建立连接,连接成功后生成 Socket 对象,并且进一步建立输入流对象和输出流对象;
(2)服务器端类:接收客户端的连接请求,启动专门的逻辑处理线程进行处理;
(3)逻辑线程类:主要实现对客户端的逻辑处理,对接收到的表达式进行计算,并将结果返回给客户端;
i. 客户端类
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
import java.util.Scanner;
public class ClientTest {
public static void main(String args[]) {
Scanner scanner = new Scanner(System.in);
String input = null;
Socket socket = null;
DataInputStream in = null;
DataOutputStream out = null;
String serverIP = "127.0.0.1"; //服务器地址
int port = 5050;//服务器端口
try {
socket = new Socket(serverIP, port);//连接服务器
in = new DataInputStream(socket.getInputStream());//创建输入流
out = new DataOutputStream(socket.getOutputStream());//创建输出流
System.out.println("请输入一个正整数的四则运算表达式:");
while (scanner.hasNext()) {
input = scanner.nextLine();//从键盘输入一个待计算的四则运算表达式
if (!input.equals("0")) {
out.writeUTF(input);//向服务器端发送运算请求
String result = in.readUTF();//等待读取运算结果
System.out.println("服务器返回的计算结果:" + result);
System.out.println("请输入一个正整数的四则运算表达式(输入0退出):");
} else
break;//请求结束
}
} catch (Exception e) {
System.out.println("与服务器连接中断");
} finally {
try//关闭网络连接
{
in.close();
out.close();
socket.close();
System.out.println("连接结束");
} catch (Exception e) {
}
}
}
}
ii. 服务器类
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerTest {
public static void main(String args[]) {
ServerSocket server_socket = null;
Socket socket = null;
int port = 5050;
while (true) {
try {
server_socket = new ServerSocket(port);
System.out.println("服务器启动!");
} catch (IOException e1) {
System.out.println("正在监听"); //ServerSocket对象不能重复创建
}
try {
System.out.println("等待客户请求");
socket = server_socket.accept();
System.out.println("客户的地址:" + socket.getInetAddress() + ":" + socket.getPort());
} catch (IOException e) {
System.out.println("正在等待客户");
}
if (socket != null) {
new ThreadTest(socket); //为每个客户启动一个专门的线程
}
}
}
}
iii. 逻辑线程类
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
public class ThreadTest extends Thread {
Socket socket = null;
DataInputStream in = null;
DataOutputStream out = null;
String str;
String response;
String ip;
int port;
public ThreadTest(Socket socket) {
this.socket = socket;
start();
}
public void run() {
try {
in = new DataInputStream(socket.getInputStream());//创建输入流
out = new DataOutputStream(socket.getOutputStream());//创建输出流
ip = socket.getInetAddress().getHostAddress();//客户端IP地址
port = socket.getPort();//客户端的端口号
while (true) {
str = in.readUTF();//获取客户端的表达式
System.out.println("客户端" + ip + ":" + port + "发送的请求内容:");
System.out.println(str + "=?");
if (str.equals("0")) {
System.out.println("连接结束");
break;
} else {
response = doComputer(str);//对表达式进行计算
out.writeUTF(response);//响应计算结果
}
}
} catch (Exception e) {
System.out.println("连接结束");
} finally {
try {
in.close();
out.close();
socket.close();
} catch (Exception e) {
}
}
}
public String doComputer(String str) {
String input;
String[] sym;
String[] data;
int a = 0, b = 0, result = 0;
input = str;
data = input.split("\\D+");//分解表达式中的正整数
sym = input.split("\\d+");//分解表达式中的运算符
a = Integer.parseInt(data[0]);//第一个正整数
b = Integer.parseInt(data[1]);//第二个正整数
try {
switch (sym[1])//判断运算符,完成相应的运算
{
case "+":
result = a + b;
break;
case "-":
result = a - b;
break;
case "*":
result = a * b;
break;
case "/":
result = a / b;
}
System.out.println("计算结果:" + input + "=" + result);
return String.valueOf(result);
} catch (java.lang.ArithmeticException e) {
System.out.println("数据错误!");
return "数据错误!";
}
}
}
iv. 运行演示
- 首先运行服务器端 ServerTest:
- 然后启动客户端 ClientTest:
- 输入一个正整数的四则运算表达式 9*9:
- 输入 0,断开链接: