学习目标:能够使用套接字技术建立TCP服务,并通过客户端连接TCP服务。
构建TCP服务
在客户端和服务器端通信模式中,可以使用ServerSocket对象来构建TCP服务端。服务端需要创建监听特定端口的ServerSocket,ServerSocket负责接收客户连接请求,并生成与客户端连接的Socket。
ServerSocket类的构造方法说明如下:
● ServerSocket()
该构造方法创建一个未绑定的服务器套接字。
● ServerSocket(int port)
该构造方法创建绑定到指定端口的服务器套接字。参数port为指定的端口号。套接字的默认绑定IP地址为0.0.0.0,用来表示本机任意的ipv4地址。
● ServerSocket(int port, int backlog)
该构造方法创建服务器套接字并将其绑定到具有指定backlog的指定本地端口号。参数port为端口号,参数backlog为请求传入连接队列的最大长度。如果连接请求在队列已满时到达,则连接将被拒绝。套接字的默认绑定IP地址为0.0.0.0,用来表示本机任意的ipv4地址。
● ServerSocket(int port, int backlog, InetAddress bindAddr)
该构造方法使用指定的端口、侦听backlog和要绑定到的指定的IP地址创建服务器。参数bindAddr为指定IP地址的InetAddress对象。
ServerSocket类的常用方法说明如下:
● Socket accept()
该方法用于侦听与此套接字的连接请求,并接收该请求,该方法将阻塞,直到建立连接。该方法将返回一个新的套接字。
● InetAddress getInetAddress()
该方法返回此服务器套接字的本地地址。如果套接字在关闭前已绑定,则此方法将在套接字关闭后继续返回本地地址。
● void bind(SocketAddress endpoint)
该方法将服务器套接字绑定到特定地址(IP地址和端口号)。如果地址为空,那么系统将获取一个临时端口和一个有效的本地地址来绑定套接字。参数endpoint是SocketAddress类型,指定了要绑定的IP地址和端口号。
● boolean isBound()
该方法返回服务器套接字的绑定状态。
● netAddress getInetAddress()
该方法返回此服务器套接字的本地地址。
● int getLocalPort()
该方法返回此套接字侦听的端口号。
● void close()
该方法关闭该套接字。关闭该套接字后,当前在accept()塞的任何线程都将抛出SocketException。
在服务器端创建ServerSocket对象,并绑定监听端口。调用ServerSocket对象的accept()方法监听客户端的请求。与客户端建立连接后,通过输入流读取客户端发送的请求信息,然后通过输出流向客户端发送响应信息,最后关闭socket及相关资源。
案例4:编写一个TCP服务端程序。建立TcpServer类,在TcpServer类中创建套接字,侦听指定的端口,等待连接请求。
在PUnit16项目新建tcp包,在tcp包下新建TcpServer类。代码如下:
package tcp;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class TcpServer {
// 定义ServerSocket对象
ServerSocket serverSocket = null;
// 定义端口号
int port;
// 构造方法
public TcpServer() {
this.port = 9099;
}
/**
* @Title: getIp
* @Description: 获取服务器的IP地址
* @param @return 参数
* @return String 返回类型
* @throws
*/
public String getIp()
{
if( null != serverSocket )
{
InetAddress address = serverSocket.getInetAddress();
return address.getHostAddress();
}
return "";
}
/**
* @Title: init
* @Description: 初始化和启动服务器
* @param 参数
* @return void 返回类型
* @throws
*/
public void init() {
System.out.println("服务器启动...");
try {
/**
* 创建一个ServerSocket,这里可以指定连接请求的队列长度 new ServerSocket(port,3)
* 意味着当队列中有3个连接请求时,如果客户端再请求连接,就会被服务器拒绝
*/
serverSocket = new ServerSocket(port, 3);
// 服务器循环监听连接请求
while (true) {
// 从请求队列中取出一个客户端的连接请求
Socket client = serverSocket.accept();
// 建立一个新线程处理这次连接
new HandlerThread(client);
}
} catch (Exception e) {
System.out.println("服务器异常: " + e.getMessage());
}
}
/**
* @ClassName: HandlerThread
* @Description: 客户端请求线程处理类
* @author 编程训练营
* @date
*
*/
private class HandlerThread implements Runnable {
// 定义socket
Socket socket;
// 构造方法
public HandlerThread(Socket socket) {
this.socket = socket;
// 启动线程
new Thread(this).start();
}
/**
* <p>
* Title: run
* </p>
* <p>
* Description:
* </p>
*
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
try {
// 读取客户端数据
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 这里要注意和客户端输出流的写方法对应,否则会抛 EOFException
String clientInputStr = input.readLine();
// 处理客户端数据
System.out.println("客户端发过来的内容:" + clientInputStr);
// 向客户端回复信息
PrintStream out = new PrintStream(socket.getOutputStream());
System.out.print("请输入:\t");
// 发送键盘输入的一行
String s = new BufferedReader(new InputStreamReader(System.in)).readLine();
out.println(s);
out.close();
input.close();
} catch (Exception e) {
System.out.println("服务器 run 异常: " + e.getMessage());
} finally {
if (socket != null) {
try {
socket.close();
} catch (Exception e) {
socket = null;
System.out.println("服务端 finally 异常:" + e.getMessage());
}
}
}
}
}
}
编写TCP服务端程序的主要步骤如下:
(1) 实例化ServerSocket对象,创建套接字;
(2) 调用ServerSocket对象的accept()方法监听端口;
(3) accept()方法监听到连接请求后,会返回Socket对象,accept()是堵塞方法,如果没有客户端的连接请求,方法后面的代码不会执行;
(4) 建立一个新的线程,利用I/O流与客户端进行通信,
(5) 通信完成后,关闭Socket。
在tcp包下新建TcpServerTest测试类,启动服务器。代码如下:
package tcp;
public class TcpServerTest {
public static void main(String[] args) {
// 实例化TcpServer对象
TcpServer tcpserver = new TcpServer();
// 启动服务
tcpserver.init();
}
}
程序TcpServerTest实例化TcpServer对象,并调用TcpServer对象的init()方法启动服务器。
连接TCP服务
TCP客户端用于连接TCP服务器,并向服务器发送请求,接收服务器返回的数据。客户端的套接字由Socket类创建。
Socket类的主要构造方法说明如下:
● Socket(InetAddress address, int port)
该构造方法创建流套接字并将其连接到指定IP地址处的指定端口号。
Socket类的主要方法说明如下:
● void bind(SocketAddress bindpoint)
该方法将将套接字绑定到bindpoint指定的IP地址和端口号。
● void connect(SocketAddress endpoint)
该方法将此套接字连接到指定的IP地址和端口号,IP地址和端口号由endpoint指定。
● void connect(SocketAddress endpoint, int timeout)
该方法将此套接字连接到指定的IP地址和端口号,IP地址和端口号由endpoint指定。参数timeout为连接超时时间。
● void close()
该方法关闭此套接字。
案例5:编写TCP客户端程序。建立TcpClient类,连接前面课程创建的TCP服务,并输出服务器的返回数据到控制台。
在tcp包下新建TcpClient类。代码如下:
package tcp;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
public class TcpClient {
// 定义一个套接字
Socket socket;
// 定义端口号
int port;
// 定义IP地址
String ip;
// 构造方法
public TcpClient(String ip, int port) {
this.ip = ip;
this.port = port;
}
/**
* @Title: send @Description: 向TCP服务器发送请求 @param 参数
*
* @return void 返回类型 @throws
*/
public void send() {
System.out.println("客户端正在连接服务器...");
while (true) {
try {
// 实例化套接字,指定IP地址和端口号
// 套接字实例化后,会自动向服务器发送请求
socket = new Socket(ip, port);
// 向服务器端发送数据
PrintStream out = new PrintStream(socket.getOutputStream());
System.out.print("请输入: \t");
String str = new BufferedReader(new InputStreamReader(System.in)).readLine();
out.println(str);
// 读取服务器端返回的数据
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String ret = input.readLine();
System.out.println("服务器端返回过来的是: " + ret);
// 如接收到 "OK" 则断开连接
if ("OK".equals(ret)) {
System.out.println("客户端将关闭连接");
break;
}
out.close();
input.close();
} catch (Exception e) {
System.out.println("客户端异常:" + e.getMessage());
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
socket = null;
System.out.println("客户端 finally 异常:" + e.getMessage());
}
}
}
}
}
}
编写TCP客户端程序的主要步骤如下:
(1)实例化Socket对象,创建套接字。套接字的IP地址需要和服务器的IP地址一致,如果TCP服务器没有指定IP地址,而是采用默认的IP地址,客户端套接字的IP地址可以是本地地址127.0.0.1,端口号必须一致。
(2)应用I/O类向服务器端写入数据,发送请求;
(3)应用I/O类读取服务端返回的数据;
(4)通信完成后,关闭Socket。
在tcp包下新建TcpClientTest测试类。代码如下:
package tcp;
public class TcpClientTest {
public static void main(String[] args) {
// 实例化client对象
TcpClient client = new TcpClient("127.0.0.1",9099);
// 向服务端发送请求
client.send();
}
}
TcpClientTest程序实例化TcpClient对象,调用TcpClient对象的send()方法向服务器端发送请求。
客户端和服务器端测试步骤:
(1) 启动TCP服务器TcpServerTest;
(2) 启动客户端程序TcpClientTest;
(3) 在客户端程序控制台窗口输入hello,向服务器端发送输入的内容;
(4) 服务器端控制台窗口回显客户端发送的内容;
(5) 在服务器端控制台窗口输入OK,服务端向客户端返回在控制台输入的内容;
(6) 客户端判断返回的内容是OK时,结束连接。