Socket所遇到的坑
本文记录使用Socket通信时所遇到坑,不断更新。
目前记录三个问题:
- 初始化一个Socket连接
- 通过输入流读取数据和通过输出流发送数据
- 监控网络拥塞情况或检测数据包丢失
建立Socket连接
Socket连接由一个ServerSocket充当服务器,并且还有许多socket用作客户端连接到ServerSocket. 在Eclipse中具体实现见下文.
服务器端:
Socket socket;
System.out.println("wait for a client.");
socket = serverSocket.accept();
System.out.println("accept a socket.");
writer = new ObjectOutputStream(socket.getOutputStream());
System.out.println("get writer.");
//ObjectInputStream is waiting for a header, we will be stuck until we completely receive it,
//we should call flush() on the other side.
reader = new ObjectInputStream(socket.getInputStream());
System.out.println("get reader.");
代码解释
socket = serverSocket.accept(); 这句话是一个阻塞状态代码,在没有任何新连接请求的情况下,应用程序会长时间等待直到有一个新的Socket连接请求到来时才能继续执行后续操作。
在客户端:
public class SocketClient extends Thread {
private Scanner scanner;
private Socket socket;
private ObjectInputStream reader;
private ObjectOutputStream writer;
public SocketClient() throws IOException{
scanner = new Scanner(System.in);
socket = new Socket("10.30.131.230", 5555);
System.out.println("A client has start.");
reader = new ObjectInputStream(socket.getInputStream());
writer = new ObjectOutputStream(socket.getOutputStream());
writer.flush();
System.out.println("reader writer.");
代码解释
客户端只需配置一个Socket对象,并传递服务器的IP地址和端口参数即可;连接则会自然地被创建。
使用输入流和输出流进行通信
服务器与客户端之间的通信必须将ServerSocket与Socket实现相同的交互方式。Java提供的API已经实现了封装功能,在我们的开发中只需简单地调用相关方法就可以完成功能。
首先客户端Socket先得到自己的输入流和输出流:
public class SocketClient extends Thread {
private Scanner scanner;
private Socket socket;
private ObjectInputStream reader;
private ObjectOutputStream writer;
public SocketClient() throws IOException{
scanner = new Scanner(System.in);
socket = new Socket("10.30.131.230", 5555);
System.out.println("A client has start.");
reader = new ObjectInputStream(socket.getInputStream());
writer = new ObjectOutputStream(socket.getOutputStream());
writer.flush();
System.out.println("reader writer.");
代码解释
代码相同, 这里我们看到通过
socket.getInputStream();
socket.getOutputStream();
代码解释
通过ObjectStream实现了对客户端Socket自身输入输出流的打包处理,并将其与这里的reader和writer对象集成应用到与ServerSocket的数据交换中。需要注意的是,在通信过程中涉及的自定义对象必须经过序列化处理以确保数据一致性。
在服务端的代码如下:
Message message = null;
Socket socket;
System.out.println("wait for a client.");
socket = serverSocket.accept();
System.out.println("accept a socket.");
writer = new ObjectOutputStream(socket.getOutputStream());
System.out.println("get writer.");
//ObjectInputStream is waiting for a header, we will be stuck until we completely receive it,
//we should call flush() on the other side.
reader = new ObjectInputStream(socket.getInputStream());
System.out.println("get reader.");
代码解释
我们注意到服务端也是采用了先通过socket方法获得socket的方式后再调用socket方法来获取输入输出流的流程。然而,在客户端这边我们需要注意的是:对于接收来的输出流应该立即调用flush()方法完成数据清除 ,这是因为当首次连接建立时,服务端对应的输入流会等待接收到一个header信息直到全部接收完毕;而flush()操作则能够及时清除缓冲区中的所有数据内容。
使用心跳保持Socket连接
当socket建立成功后能够实现通信时,请稍后再喜。因为socket在长时间不通信息时会关闭连接。而维护这一状态的方法就是通过定时发送心跳报文(即向服务器发送空数据包),从而保证两端能够持续通信。
常用的方法包括编写一个类继承自Thread类用于发送心跳信息并设定一个最大等待时间值当达到最大等待时间后如果没有收到来自服务器的消息也没有接收到服务器的任何反馈则会触发向服务发送心跳信息的行为需要注意的是在这一段时间内如果接收到反馈信息则需重新设置等待时间
