目录
?API介绍?ServerSocket API?Socket API ?TCP中的长短连接?建立TCP回显客户端与服务器?TCP搭建服务器?TCP搭建客户端 ⭕总结TCP服务器与客户端的搭建需要借助以下API
?API介绍
?ServerSocket API
ServerSocket 是创建TCP服务端Socket的API。
ServerSocket 构造方法:
方法签名 | 方法说明 |
---|---|
ServerSocket(int port) | 创建一个服务端流套接字Socket,并绑定到指定端口 |
ServerSocket 方法:
方法签名 | 方法说明 |
---|---|
Socket accept() | 开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket对象,并基于该Socket建立与客户端的连接,否则阻塞等待 |
void close() | 关闭此套接字 |
?Socket API
Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。
不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。
Socket 构造方法:
方法签名 | 方法说明 |
---|---|
Socket(String host, intport) | 创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接 |
Socket 方法:
方法签名 | 方法说明 |
---|---|
InetAddress getInetAddress() | 返回套接字所连接的地址 |
InputStream getInputStream() | 返回此套接字的输入流 |
OutputStream getOutputStream() | 返回此套接字的输出流 |
?TCP中的长短连接
博主在前面的博文里面说到,TCP是面向连接的通信方式,TCP发送数据时,需要先建立连接,而这个连接又分为长短连接:
短连接:每次接收到数据并返回响应后,都关闭连接,即是短连接。也就是说,短连接只能一次收发数据。
长连接:不关闭连接,一直保持连接状态,双方不停的收发数据,即是长连接。也就是说,长连接可以多次收发数据
对比以上长短连接,两者区别如下:
建立连接、关闭连接的耗时:短连接每次请求、响应都需要建立连接,关闭连接;而长连接只需要第一次建立连接,之后的请求、响应都可以直接传输。相对来说建立连接,关闭连接也是要耗时的,长连接效率更高。
主动发送请求不同:短连接一般是客户端主动向服务端发送请求;而长连接可以是客户端主动发送请求,也可以是服务端主动发。
两者的使用场景有不同:短连接适用于客户端请求频率不高的场景,如浏览网页等。长连接适用于客户端与服务端通信频繁的场景,如聊天室,实时游戏等
拓展:
基于BIO(同步阻塞IO)的长连接会一直占用系统资源。对于并发要求很高的服务端系统来说,这样的消耗是不能承受的。
由于每个连接都需要不停的阻塞等待接收数据,所以每个连接都会在一个线程中运行。一次阻塞等待对应着一次请求、响应,不停处理也就是长连接的特性:一直不关闭连接,不停的处理请求
实际应用时,服务端一般是基于NIO(即同步非阻塞IO)来实现长连接,性能可以极大的提升。
?建立TCP回显客户端与服务器
?TCP搭建服务器
我们分为以下几步来实现:
创建TcpEchoServer类来表示我们的服务器,并创建ServerSocket对象,初始值为null在TcpEchoServer的构造方法里进行ServerSocket对象的实例化用一个start()方法表示启动程序在该方法内我们首先要使用accept()进行连接,并用Socket对象进行接收我们再用一个processConnection(Socket clientSocket)方法处理我们的连接由于我们的TCP传输是以流的形式传播的,所以我们这里用到了读写数据流的方法来进行书写,不会这一部分的小伙伴,可以去看看博主所写【Java EE】文件内容的读写⸺数据流进行查看学习
接下来我们书写这个processConnection(Socket clientSocket)方法
读取请求,构造输入流的Scanner,并判断后面如果没有数据就关闭连接然后我们将读取的数据交给我们的 response()构造响应响应后的数据写入该套接字的输出流中,最后flush(),进行刷新,确保写入为了释放资源,我们每一次交互完毕都需要对我们的套接字进行关闭,这里我们使用fially来进行处理
代码如下:
import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.io.PrintWriter;import java.net.ServerSocket;import java.net.Socket;import java.util.Scanner;public class TcpEchoServer { private ServerSocket serverSocket = null; public TcpEchoServer(int port) throws IOException { serverSocket = new ServerSocket(port); } public void start() throws IOException { System.out.println("启动服务器"); Socket socket = serverSocket.accept(); processConnection(socket); } // 使用这个方法来处理一个连接. // 这一个连接对应到一个客户端. 但是这里可能会涉及到多次交互. private void processConnection(Socket clientSocket) { System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort()); // 基于上述 socket 对象和客户端进行通信 try (InputStream inputStream = clientSocket.getInputStream(); OutputStream outputStream = clientSocket.getOutputStream()) { // 由于要处理多个请求和响应, 也是使用循环来进行. while (true) { // 1. 读取请求 Scanner scanner = new Scanner(inputStream); if (!scanner.hasNext()) { // 没有下个数据, 说明读完了. (客户端关闭了连接) System.out.printf("[%s:%d] 客户端下线! \n", clientSocket.getInetAddress().toString(), clientSocket.getPort()); break; } // 注意!! 此处使用 next 是一直读取到换行符/空格/其他空白符结束, 但是最终返回结果里不包含上述 空白符 . String request = scanner.next(); // 2. 根据请求构造响应 String response = process(request); // 3. 返回响应结果. // OutputStream 没有 write String 这样的功能. 可以把 String 里的字节数组拿出来, 进行写入; // 也可以用字符流来转换一下. PrintWriter printWriter = new PrintWriter(outputStream); // 此处使用 println 来写入. 让结果中带有一个 \n 换行. 方便对端来接收解析. printWriter.println(response); // flush 用来刷新缓冲区, 保证当前写入的数据, 确实是发送出去了. printWriter.flush(); System.out.printf("[%s:%d] req: %s; resp: %s \n", clientSocket.getInetAddress().toString(), clientSocket.getPort(), request, response); } } catch (IOException e) { e.printStackTrace(); } finally { // 更合适的做法, 是把 close 放到 finally 里面, 保证一定能够执行到!! try { clientSocket.close(); clientSocket.close(); } catch (IOException e) { e.printStackTrace(); } } } public String process(String request) { return request; } public static void main(String[] args) throws IOException { TcpEchoServer server = new TcpEchoServer(9090); server.start(); }}
?TCP搭建客户端
搭建客户端我们也可以分为以下几步:
创建TcpEchoClient类表示我们的客户端,创建Soket对象用于与客户端通信·再TcpEchoClient构造方法里进行实例化Socket的对象创建start()方法用于我们的操作读取键盘所要输入的数据将所读的数据通过输出流进行写入读取响应的输入流,进行打印main函数中进行启动import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.io.PrintWriter;import java.net.Socket;import java.util.Scanner;public class TcpEchoClient { private Socket socket = null; public TcpEchoClient(String serverIp, int serverPort) throws IOException { // Socket 构造方法, 能够识别 点分十进制格式的 IP 地址. 比 DatagramPacket 更方便. // new 这个对象的同时, 就会进行 TCP 连接操作. socket = new Socket(serverIp, serverPort); } public void start() { System.out.println("客户端启动!"); Scanner scanner = new Scanner(System.in); try (InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream()) { while (true) { // 1. 先从键盘上读取用户输入的内容 System.out.print("> "); String request = scanner.next(); if (request.equals("exit")) { System.out.println("goodbye"); break; } // 2. 把读到的内容构造成请求, 发送给服务器. PrintWriter printWriter = new PrintWriter(outputStream); printWriter.println(request); // 此处加上 flush 保证数据确实发送出去了. printWriter.flush(); // 3. 读取服务器的响应 Scanner respScanner = new Scanner(inputStream); String response = respScanner.next(); // 4. 把响应内容显示到界面上 System.out.println(response); } } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) throws IOException { TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9090); client.start(); }}
⭕总结
关于《【网络原理】使用Java基于TCP实现简单客户端与服务器通信》就讲解到这儿,感谢大家的支持,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下!