一. Socket简介
1. Socket通信模型
TCP协议是面向连接、可靠的、有序的、以字节流的方式发送数据,那么我们如何使用Java来基于TCP协议实现网络通信的呢?共有两个类,客户端的Socket类,服务端的ServerSocket类,Socket的通信模型:
上图解析:
-
两台服务器通信,则必然一台是客户端(Server),一台是服务端(Client),首先在服务器端建立一个ServerSocket,绑定相应的端口,并且在指定的端口进行监听,等待客户端的连接
-
客户端创建Socket并向服务端发送请求
-
服务器收到请求并接受客户端的请求信息,接受请求后,创建连接socket,用来和客户端的socket通过InputStream和OutputStream进行通信交换
-
结束通信,关闭客户端和服务器的socket和相关资源
2. socket通信实现步骤
-
创建ServerSocket和Socket
-
打开连接到Socket的输入/输出流
-
按照协议对Socket进行读/写操作
-
关闭输入输出流、关闭Socket
3. ServerSocket
ServerSocket位于java.net包下 ,用于实现服务器套接字。服务器套接字等待请求通过网络传入。它基于该请求执行某些操作,然后可能向请求者返回结果。 服务器套接字的实际工作由 SocketImpl 类的实例执行。应用程序可以更改创建套接字实现的套接字工厂来配置它自身,从而创建适合本地防火墙的套接字。可以使用ServerSocket(int port)构造方法来创建绑定到特定端口的服务器套接字。其他常用方法:
-
public Socket accept() throws IOException:侦听并接受到此套接字的连接。此方法在连接传入之前一直阻塞
-
public void close()throws IOException:关闭此套接字。 在 accept() 中所有当前阻塞的线程都将会抛出 SocketException,如果此套接字有一个与之关联的通道,则关闭该通道
-
public InetAddress getInetAddress():返回此服务器套接字的本地地址
-
getLocalPort public int getLocalPort():返回此套接字在其上侦听的端口
4. Socket
Socket位于java.net包下 ,用于此类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器间通信的端点。
套接字的实际工作由 SocketImpl 类的实例执行。应用程序通过更改创建套接字实现的套接字工厂可以配置它自身,以创建适合本地防火墙的套接字
二. 基于TCP的Socket通信服务端
想要基于TCP进行Socket通信,服务端需要做的事情大概有以下几步
-
创建ServerSocket对象,绑定监听端口
-
通过accept()方法监听客户端请求
-
连接建立后,通过输入流读取客户端发送的请求信息
-
通过输出流向客户端发送响应信息
-
关闭相关资源
来看一下每一步对应的代码:
package com.example.sockettest.server; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; /** * @Author : ZhangJia * @Date : 13:55 * @Description : */ public class Server { public static void main(String[] args) { //1.创建ServerSocket对象,绑定监听端口 try { ServerSocket serverSocket = new ServerSocket(8887); // 2.通过accept()方法监听客户端请求 System.out.println("服务器即将启动,等待客户端连接"); Socket socket= serverSocket.accept(); // 3. 连接建立后,通过输入流读取客户端发送的请求信息 InputStream inputStream = socket.getInputStream(); InputStreamReader reader = new InputStreamReader(inputStream); BufferedReader bufferedReader = new BufferedReader(reader); // 4. 通过输出流向客户端发送响应信息 String info = ""; while ((info = bufferedReader.readLine()) != null) { //循环读取客户端信息 System.out.println("我是服务器,客户端发给我的消息是:" +info); } socket.shutdownInput(); //关闭输入流(close关闭的是套接字,shutdownInput是关闭此套接字的输入流 by宋光暖) // 5. 关闭相关资源 bufferedReader.close(); reader.close(); inputStream.close(); socket.close(); serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } } }
三. 基于TCP的Socket通信客户端
同样的,想要基于TCP进行Socket通信,客户端需要做的事情大体步骤为:
-
创建Socket对象,指明需要连接的服务器的地址和端口号
-
连接建立后,通过输出流向服务器端发送请求信息
-
通过输入流获取服务器响应的信息
-
关闭相关资源
再来看一下每一步对应的客户端的代码:
package com.example.sockettest.client; import java.io.*; import java.net.ServerSocket; import java.net.Socket; /** * @Author : ZhangJia * @Date : 13:55 * @Description : */ public class Client { public static void main(String[] args) { try { //1.创建Socket对象,指定服务器地址和端口 Socket socket = new Socket("localhost",8887); // 2.获取输出流,向服务器端发送信息 OutputStream outputStream = socket.getOutputStream(); PrintWriter printWriter = new PrintWriter(outputStream); //将输出流包装为打印流 printWriter.write("你好,我是ZhangJia"); printWriter.flush(); socket.shutdownOutput();//关闭输出流 //3. 关闭资源 printWriter.close(); outputStream.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } } }
接下来先启动服务器端,再启动客户端,控制台输出如下:
服务器即将启动,等待客户端连接 我是服务器,客户端发给我的消息是:你好,我是ZhangJia
这就简单完成了客户端和服务器端的通信
三. 服务器端响应客户端
在上面的例子中,服务器端仅仅是接收到了客户端的通信,并没有对其进行任何响应,接下来修改代码,实现服务器端响应客户端的功能,实现起来也非常简单,只需要给客户端获取输入流,给服务器端提供输出流发送消息即可。客户端:
package com.example.sockettest.client; import java.io.*; import java.net.ServerSocket; import java.net.Socket; /** * @Author : ZhangJia * @Date : 13:55 * @Description : */ public class Client { public static void main(String[] args) { try { //1.创建Socket对象,指定服务器地址和端口 Socket socket = new Socket("localhost", 8887); // 2.获取输出流,向服务器端发送信息 OutputStream outputStream = socket.getOutputStream(); PrintWriter printWriter = new PrintWriter(outputStream); //将输出流包装为打印流 printWriter.write("你好,我是ZhangJia"); printWriter.flush(); socket.shutdownOutput();//关闭输出流 // 3. 获取输入流,读取服务器端的响应信息 InputStream inputStream = socket.getInputStream(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String info = ""; while ((info = bufferedReader.readLine()) != null) { //循环读取服务器信息 System.out.println("我是客户端,服务器发给我的消息是:" + info); } socket.isInputShutdown(); //4. 关闭资源 bufferedReader.close(); inputStream.close(); printWriter.close(); outputStream.close(); printWriter.close(); outputStream.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } } }
服务器端:
package com.example.sockettest.server; import java.io.*; import java.net.ServerSocket; import java.net.Socket; /** * @Author : ZhangJia * @Date : 13:55 * @Description : */ public class Server { public static void main(String[] args) { //1.创建ServerSocket对象,绑定监听端口 try { ServerSocket serverSocket = new ServerSocket(8887); // 2.通过accept()方法监听客户端请求 System.out.println("服务器即将启动,等待客户端连接"); Socket socket= serverSocket.accept(); // 3. 连接建立后,通过输入流读取客户端发送的请求信息 InputStream inputStream = socket.getInputStream(); InputStreamReader reader = new InputStreamReader(inputStream); BufferedReader bufferedReader = new BufferedReader(reader); // 4. 通过输出流向客户端发送响应信息 String info = ""; while ((info = bufferedReader.readLine()) != null) { //循环读取客户端信息 System.out.println("我是服务器,客户端发给我的消息是:" +info); } socket.shutdownInput(); //关闭输入流(close关闭的是套接字,shutdownInput是关闭此套接字的输入流 by宋光暖) // 5. 获取输出流,响应客户端的请求 OutputStream outputStream = socket.getOutputStream(); PrintWriter printWriter = new PrintWriter(outputStream); //将输出流包装为打印流 printWriter.write("你好,我是ZhangYi"); printWriter.flush(); socket.shutdownOutput();//关闭输出流 // 6. 关闭相关资源 bufferedReader.close(); reader.close(); inputStream.close(); socket.close(); serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } } }
四. 使用多线程实现多客户端的通信
上面的代码存在这样的问题,一个服务器目前只能和一个客户端通话,我们可以使用多线程来实现多客户端的通信,基本步骤如下:
-
服务器端创建ServerSocket,循环调用accept()等待客户端连接
-
客户端创建一个socket并请求和服务器端连接
-
服务器端接受客户端请求,创建socket与该客户建立专线连接
-
建立连接的两个socket在一个单独的线程上对话
-
服务器端继续等待新的连接
首先创建多线程类:
package com.example.thread; import java.io.*; import java.net.Socket; /** * @Author : ZhangJia * @Date : 15:29 * @Description : */ public class ServerThread extends Thread { //和本线程相关的Socket private Socket socket; public ServerThread(Socket socket) { this.socket = socket; } //线程执行的操作,响应客户端的请求 @Override public void run() { // 3. 连接建立后,通过输入流读取客户端发送的请求信息 InputStream inputStream = null; InputStreamReader inputStreamReader = null; BufferedReader bufferedReader = null; OutputStream outputStream = null; PrintWriter printWriter = null; try { inputStream = socket.getInputStream(); inputStreamReader = new InputStreamReader(inputStream); bufferedReader = new BufferedReader(inputStreamReader); // 4. 通过输出流向客户端发送响应信息 String info = ""; while ((info = bufferedReader.readLine()) != null) { //循环读取客户端信息 System.out.println("我是服务器,客户端发给我的消息是:" + info); } socket.shutdownInput(); //关闭输入流(close关闭的是套接字,shutdownInput是关闭此套接字的输入流 by宋光暖) // 5. 获取输出流,响应客户端的请求 outputStream = socket.getOutputStream(); printWriter = new PrintWriter(outputStream); //将输出流包装为打印流 printWriter.write("你好,我是ZhangYi"); printWriter.flush(); socket.shutdownOutput();//关闭输出流 } catch (Exception e) { } finally { //关闭相关资源 try { printWriter.close(); outputStream.close(); bufferedReader.close(); inputStreamReader.close(); inputStream.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } } } }
接下来修改服务器端:
package com.example.sockettest.server; import com.example.thread.ServerThread; import java.io.*; import java.net.ServerSocket; import java.net.Socket; /** * @Author : ZhangJia * @Date : 13:55 * @Description : */ public class Server { public static void main(String[] args) { //1.创建ServerSocket对象,绑定监听端口 try { ServerSocket serverSocket = new ServerSocket(8887); int count = 0; //记录客户端的数量 System.out.println("服务器即将启动,等待客户端连接"); Socket socket = null; //循环监听等待客户端的连接 while (true) { // 通过accept()方法监听客户端请求 socket = serverSocket.accept(); // 创建一个新的线程 ServerThread serverThread = new ServerThread(socket); // 启动线程 serverThread.start(); System.out.println("当前客户端的数量是" + ++count); System.out.println("当前客户端计算机名" + socket.getInetAddress().getHostName()); System.out.println("当前客户端IP" + socket.getInetAddress().getHostAddress()); System.out.println("-------------------"); } } catch (IOException e) { e.printStackTrace(); } } }
此时启动服务器端,再启动多次客户端,控制台输出如下:
服务器即将启动,等待客户端连接 当前客户端的数量是1 我是服务器,客户端发给我的消息是:你好,我是ZhangJia 当前客户端计算机名127.0.0.1 当前客户端IP127.0.0.1 ------------------- 当前客户端的数量是2 当前客户端计算机名127.0.0.1 我是服务器,客户端发给我的消息是:你好,我是ZhangJia 当前客户端IP127.0.0.1 -------------------
参考资料
本文所有内容根据慕课网汤小洋老师:Java Socket应用—通信是这样练成的 教程整理而成
请登录之后再进行评论