1 简单通信
回顾 Socket 编程给我们最大的感受,是可以在多台电脑之间进行数据的传输,这就是网络编程的开端和基础,通过客户端请求服务器端通信,直观了解 Web 编程。
Server
/** * 服务端,接收客户端请求并给出简单的响应 * @author Cushier * */public class Server { public static void main(String[] args) throws IOException{ // 创建服务器,指定端口ServerSocket(int port) ServerSocket socket = new ServerSocket(8888); // 接收客户端连接 Socket client = socket.accept(); System.out.println("******************"); // 获取数据的输入流 InputStream is = client.getInputStream(); // 使用缓冲字符输入流 BufferedReader br = new BufferedReader(new InputStreamReader(is)); String msg = ""; while ((msg = br.readLine()) != null) { System.out.println(msg); } br.close(); } }复制代码
Client
/** * 客户端:向服务器发送请求,并发送简单的消息 * @author Cushier * */public class Client { public static void main(String[] args) throws UnknownHostException, IOException { // 创建客户端 必须指定服务器+端口 Socket client = new Socket("localhost", 8888); // 发送消息 请求资源// 获取输出流 OutputStream os = client.getOutputStream(); // 使用缓冲字符输出流 BufferedWriter br = new BufferedWriter(new OutputStreamWriter(os)); // 写出消息,发送内容 String msg = "Hello, I am Client, I need some resources"; br.write(msg); br.close(); } }复制代码
服务端控制台:
从上面的例子总结通信条件如下:
- 需要有服务器端(server):等待被请求,需要暴露 ip 和 port
- 需要有客户端(client):主动发起请求,知晓服务端的 ip 和 port
- 通信规则(协议):TCP/IP 协议
ip 用于定位计算机;端口号(定位程序),用于标识进程的逻辑地址,不同进程的标志;有效端口:065535,其中 01024 由系统使用或者保留端口,开发中建议使用 1024 以上的端口。
2 不同请求
Client
/** * 客户端:向服务器发送请求,发送不同的请求 * @author Cushier * */public class Client { public static void main(String[] args) throws IOException { // 通过系统默认类型的 SocketImpl 创建未连接套接字 Socket socket = new Socket(); // 此类实现 IP 套接字地址(IP 地址 + 端口号)。它还可以是一个对(主机名 + 端口号),在此情况下,将尝试解析主机名 SocketAddress address = new InetSocketAddress("localhost", 8898); // 将此套接字连接到服务器,并指定一个超时值。或者不指定超时时间 socket.connect(address, 1000); OutputStream os = socket.getOutputStream(); os.write("time".getBytes()); os.flush(); socket.close(); } }复制代码
Server
/** * 服务端 * public class ServerSocketextends Object:此类实现服务器套接字。 * 服务器套接字等待请求通过网络传入。 * 它基于该请求执行某些操作,然后可能向请求者返回结果。 * * @author Cushier * */public class Server { public static void main(String[] args) throws IOException { // 创建绑定到特定端口的服务器套接字。 ServerSocket server = new ServerSocket(8898); // Socket accept() 侦听并接受到此套接字的连接。 Socket client = server.accept(); System.out.println("接收到连接"); InputStream is = client.getInputStream(); BufferedInputStream bis = new BufferedInputStream(is); byte[] req = new byte[1024]; // 接收客户端请求int len = bis.read(req); String reqStr = new String(req, 0, len); System.out.println(reqStr); if (reqStr.equals("money")) { System.out.println("here's the money"); } else if (reqStr.equals("time")) { System.out.println("you have so much time"); } client.close(); server.close(); } }复制代码
服务端控制台:
3 复杂请求
Client
/** * 客户端 * * @author Cushier * */public class Client { public static void main(String[] args) throws IOException { // 通过系统默认类型的 SocketImpl 创建未连接套接字 Socket socket = new Socket(); // 此类实现 IP 套接字地址(IP 地址 + 端口号)。它还可以是一个对(主机名 + 端口号),在此情况下,将尝试解析主机名 SocketAddress address = new InetSocketAddress("localhost", 8898); // 将此套接字连接到服务器,并指定一个超时值。或者不指定超时时间 socket.connect(address, 1000); OutputStream os = socket.getOutputStream(); os.write("money".getBytes()); os.flush(); // 接收响应,显示结果 InputStream is = socket.getInputStream(); byte[] result = new byte[1024]; int len = is.read(result); String resultStr = new String(result, 0, len); System.out.println(resultStr); socket.close(); } }复制代码
Server
/** * 服务端 * @author Cushier * */public class Server2 { public static void main(String[] args) throws IOException { // 创建绑定到特定端口的服务器套接字。 ServerSocket server = new ServerSocket(8898); // Socket accept() 侦听并接受到此套接字的连接。 Socket client = server.accept(); System.out.println("接收到连接"); InputStream is = client.getInputStream(); BufferedInputStream bis = new BufferedInputStream(is); byte[] req = new byte[1024]; // 接收客户端请求int len = bis.read(req); String reqStr = new String(req, 0, len); System.out.println(reqStr); // 将接收到的请求封装成对象,传送给请求的类 MyRequest request = new MyRequest(); MyResponse response = new MyResponse(); OutputStream os = client.getOutputStream(); if (reqStr.equals("money")) { // 根据请求的信息,构造处理的类 MyServlet s1 = new ServletMoney(); s1.service(request, response); // 通过client的响应,将结果响应回客户端 os.write("here's the money".getBytes()); os.flush(); } else if (reqStr.equals("time")) { // 根据请求的信息,构造处理的类 MyServlet s2 = new ServletTime(); s2.service(request, response); // 通过client的响应,将结果响应回客户端 os.write("you have somuch time".getBytes()); os.flush(); } client.close(); server.close(); } }/* * 我是一个有要求的人,你请求的这个资源必须是满足我要求格式的类,作用:防止混乱,方便调用 这是我的标准 */interface MyServlet { void service(MyRequest req, MyResponse resp); }class ServletMoney implements MyServlet { @Overridepublic void service(MyRequest req, MyResponse resp) { // 做出力所能及的处理 } }class ServletTime implements MyServlet { @Overridepublic void service(MyRequest req, MyResponse resp) { // 做出力所能及的处理 } }/* * 请求信息都按规律封装在该对象 */class MyRequest { }class MyResponse { }复制代码
服务端控制台: 客户端控制台:
随着客户需求越来越复杂,需要的功能越来越多,我们的服务器端需要处理的请求越来越多,需要区分不同的请求,还需要按照不同请求进行请求数据的提取以及资源的分配和运算还有逻辑的处理,最后还需要响应给客户端,这就使得服务器端代码越来越复杂,实现越来越困难。
根据以往的经验,双方进行通信只需要遵循一定的规则就可以很明确地知道各部分数据的含义,于是出现了网络更上层的应用协议(后面讲的 HTTP 协议),规定服务器端和客户端通信的规则。
客户端请求服务器端和服务器端响应客户端,都按照固定的规则,那么接收请求和响应数据这部分操作就可以固定下来,交给特定的一段代码来执行,从而减少服务器端的代码量,于是出现了接下来说的服务器。
扩展
服务器的出现
当客户端请求的资源越来越丰富,需求越来越复杂,程序的核心就应该放在解决业务和计算响应数据上,于是出现了服务器统一接收客户端数据进行处理并分发到不同的资源,由各个资源进行处理,最后结果交由服务器响应。
从上面的描述可以发现,现在所说的服务器只是负责接收请求,对请求进行分发,以及最后将获取的数据进行相应的固定框架,至于数据怎么计算得出还得根据具体的业务需求编写(填充)代码。在没有业务需求的情况下就能将服务器准备出来,现在市面上的服务器有很多,比较常用的有:Tomcat、JBOOS、IBM 的 WebSphere、BEA的 WebLogic 以及 Apache 等。