端与端通信,经常由客户端和服务端两者组成,其中客户端发送请求给服务端,而服务端则响应请求。这两者的通信可以通过Socket来实现两端的数据传输。其中Java自带的Socket类可以创建客户端socket,而ServerSocket可以创建一个服务端Socket。
1.服务端-客户端的通信创建以及工作流程
由于Socket是两台主机之间的一个连接,Socket简化了网络的底层细节,比如传输数据过程中的错误检测、包大小、包分解、包重传、网络地址等,简化了网络编程的部分工作。以下分别讲解服务端、客户端创建以及工作流程
1.1.服务端Socket的创建流程
1.使用一个ServerSocket()构造函数在一个特定端口创建一个新的ServerSocket。
2.ServerSocket使用其accept()方法监听这个端口的入站连接。accept()方法会一直阻塞,直到一个客户端尝试建立连接,此时accept(0将返回一个连接客户端和服务器的socket对象。
3.根据服务器的类型,会调用socket的getInputStream()方法或getOutputStream()方法,或者两者都调用,以获得与客户端通信的输入和输出流。
4.服务端和客户端根据已协商好的协议进行交互,直到关闭连接。
5.服务端关闭连接
6.服务器返回步骤2,等待下一次连接。
1.2.客户端Socket的创建流程
1.使用一个Socket构造函数创建一个连接远程ip,特定端口的主机的socket。与服务端建立通信。
2.调用socket的getInputStream()方法和getOutputStream()方法,以获得与服务端通信的输入输出流。
3.通过将输出流写入socket,向服务端发送请求;并通过获取socket的输入流返回服务端的响应。
4.关闭socket。
2.通信过程中常用的一些类
Java中JDK提供了许多API实现网络编程,以下便是几个常用的类。
2.1.Socket类
一般我们都是通过Socket类的构造函数创建Socket,而一个socket对象需要一个远程ip和对应端口两个参数才能确定。因此有如下构造函数:
Socket(InetAddress/String remoteAddress,int port):创建连接到指定远程主机、远程端口的socket,该构造器没有指定本地地址、本地端口,默认使用本地主机的IP地址,默认使用系统动态分配的端口。
Socket(InetAddress/String remoteAddress,int port,InetAddress localAddr,int localPort):创建连接到指定远程主机、远程端口的socket,并指定本地IP地址和本地端口,适用于本地主机有多个IP地址的情形。
2.2.ServerSocket类
ServerSocket类用于服务端创建ServerSocket对象,建立监听连接。具有以下构造函数:
ServerSocket(int port):用指定的端口port来创建一个ServerSocket。该端口应该有一个有效的端口整数值,即0~65535.
ServerSocket(int port,int backlog):增加一个用来改变连接队列长度的参数backlog。
ServerSocket(int port,int backlog,InetAddress localAddr):在机器存在多个IP地址的情况下,允许通过localAddr参数来指定将ServerSocket绑定到指定的IP地址。
一般服务端与客户端相互连接之后,需要用accept方法用于监听来自客户端的Socket连接。如果没有连接,将一直处于等待状态,如果有连接,则返回socket对象。
Socket accept():如果接收到一个客户端Socket的连接请求,该方法将返回一个与客户端Socket对应的Socket,否则一直处于等待状态,线程也被阻塞。
3.用例
该例子的目标是在服务端实现字符串的翻转。即客户端通过在控制台写入一行字符串,然后发送请求给服务端,服务端进行翻转,然后返回给客户端输出。由于客户端与服务端都在本地,所以客户端连接的远程ip为本地ip即可。
客户端:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.net.UnknownHostException;
public class Client {
public static BufferedReader bufferedReader = null;
public static PrintStream pStream = null;
public static BufferedReader keyIn= null;//用于键盘输入
public static void main(String[] args) throws UnknownHostException, IOException {
// TODO Auto-generated method stub
keyIn = new BufferedReader(new InputStreamReader(System.in));
//创建socket连接,连接对应ip指定port
Socket socket = new Socket("127.0.0.1", 3002);
String in = keyIn.readLine();//获取键盘输入
pStream = new PrintStream(socket.getOutputStream());
pStream.println(in);//将请求写入socket发送
bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));//获取输入流,即获取服务端响应。
String anString = bufferedReader.readLine();
if(anString==null) {
System.out.println("网络通信出现故障");
}
else {
System.out.println("服务端响应结果:"+anString);
}
//将资源关闭
pStream.close();
bufferedReader.close();
keyIn.close();
socket.close();
}
}
服务端:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static BufferedReader bufferedReader = null;
public static PrintStream pStream = null;
public static String reverse(String line) {
return new StringBuilder(line).reverse().toString();
}
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
//建立监听的serversocket
ServerSocket serverSocket = new ServerSocket(3002);
//利用死循环来接收客户端的请求
while(true) {
Socket socket = serverSocket.accept();
//获取客户端请求
bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//输出流,用于写入服务端响应
pStream = new PrintStream(socket.getOutputStream());
String line = bufferedReader.readLine();
String out = null;
if(line!=null) {
out = reverse(line);
}
if(out==null) {
pStream.println("该请求无法实现!");
}
else {
pStream.println("请求响应成功:"+out);
}
//关闭资源
pStream.close();
bufferedReader.close();
socket.close();
}
}
}
先启动服务端,打开监听,等待客户端请求发送,然后启动服务端,并在控制台输入一行字符串,便可获取响应。
这个简单例子说明了socket作为客户端和服务端通信的虚拟链路的连接。说明了如何获取输入,发送请求,以及获取响应的过程。