Socket编程实践模拟通信

简介: Socket编程实践模拟通信

本篇记录Socket与ServerSocket进行通信过程,服务器端使用线程,一直监听客户端的请求,并返回消息给客户端。

【1】客户端代码

  @Test
  public void testClient(){
    Socket client = null;
    OutputStream outputStream = null;
    OutputStreamWriter ow = null;
    int i =1;
    for(;i<3;i++){
      try {
        // 创建一个socket对象,并建立网络连接(可以使用以下三种方式任何一种)
        client = new Socket("localhost",8989);
//        client = new Socket("127.0.0.1",8989);
//        client = new Socket(InetAddress.getByName("127.0.0.1"),8989);
        System.out.println("client socket "+client.hashCode()+",建立了socket实例时间:"+new Date());
        //设置等待
        client.setSoTimeout(15000);
        //获取远程请求地址和端口
        InetAddress inetAddress = client.getInetAddress();
        int port = client.getPort();
        String hostAddress = inetAddress.getHostAddress();
        String hostName = inetAddress.getHostName();
        InetAddress localHost = inetAddress.getLocalHost();
        System.out.println("远程主机 :"+inetAddress+",远程端口:"+port+",远程hostAddress:"+hostAddress+",远程hostName:"+hostName);
        //获取发起的地址和端口
        InetAddress localAddress = client.getLocalAddress();
        int localPort = client.getLocalPort();
        String hostAddress2 = localAddress.getHostAddress();
        String hostName2 = localAddress.getHostName();
        System.out.println("本地主机 :"+localAddress+",本地端口 :"+localPort+",本地hostAddress:"+hostAddress2+",本地hostName:"+hostName2);
        //打开输出流,要发送消息
        outputStream = client.getOutputStream();
        ow = new OutputStreamWriter(outputStream);
        ow.write("I'm jane,please take care!");
        ow.flush();
        // 关掉输出流,说明客户端已经写完了
        client.shutdownOutput();
        //获取输入流,接收服务器返回的消息
        InputStream inputStream = client.getInputStream();
        InputStream is=inputStream;
        BufferedReader br=new BufferedReader(new InputStreamReader(is));
        String info=null;
        while((info=br.readLine())!=null){
            System.out.println("我是客户端,服务器说:"+info);
        }
      }catch (IOException e) {
        e.printStackTrace();
      }finally{
        try {
          outputStream.close();
          client.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
  }

【2】服务端代码

① 测试方法如下:

  @Test
  public void testServer(){
    try{
      ServerSocket server = new ServerSocket(8989);
      Socket socket=null;
          //记录客户端的数量
          int count=0;
          System.out.println("***服务器即将启动,等待客户端的连接***");
          //循环监听等待客户端的连接
          while(true){
              //调用accept()方法开始监听,等待客户端的连接
              socket=server.accept();
              //统计客户端的数量
              count++;
              System.out.println("建立了socket实例时间:"+new Date()+",当前客户端的数量:"+count+",当前的socket:"+socket.hashCode());
//              InetAddress inetAddress = socket.getInetAddress();
//              System.out.println("当前的IP:"+inetAddress.getHostAddress()+",当前的本地主机IP:"+inetAddress.getLocalHost()+",当前的socket 是"+socket.hashCode());
         //获取远程请求地址和端口
        InetAddress inetAddress = socket.getInetAddress();
        int port = socket.getPort();
        String hostAddress = inetAddress.getHostAddress();
        String hostName = inetAddress.getHostName();
        InetAddress localHost = inetAddress.getLocalHost();
        System.out.println("远程主机 :"+inetAddress+",远程端口:"+port+",远程hostAddress:"+hostAddress+",远程hostName:"+hostName);
        //获取发起的地址和端口
        InetAddress localAddress = socket.getLocalAddress();
        int localPort = socket.getLocalPort();
        String hostAddress2 = localAddress.getHostAddress();
        String hostName2 = localAddress.getHostName();
        System.out.println("本地主机 :"+localAddress+",本地端口 :"+localPort+",本地hostAddress:"+hostAddress2+",本地hostName:"+hostName2);
              //创建一个新的线程
              ServerThread serverThread=new ServerThread(socket);
              //启动线程
              serverThread.start();
          }
      } catch (IOException e) {
          e.printStackTrace();
      }
  }

② 线程如下

package com.web.hh.controller;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
public class ServerThread extends Thread{
  // 和本线程相关的Socket
    Socket socket = null;
    public ServerThread(Socket socket) {
        this.socket = socket;
    }
    //线程执行的操作,响应客户端的请求
    public void run(){
        InputStream is=null;
        InputStreamReader isr=null;
        BufferedReader br=null;
        OutputStream os=null;
        PrintWriter pw=null;
        try {
            //获取输入流,并读取客户端信息
            is = socket.getInputStream();
            isr = new InputStreamReader(is);
            br = new BufferedReader(isr);
            String info=null;
            //循环读取客户端的信息,直到读完
            while((info=br.readLine())!=null){
                System.out.println("我是服务器,客户端说:"+info);
            }
            //socket关闭输入通道--读完了
            socket.shutdownInput();
            //获取输出流,响应客户端的请求
            os = socket.getOutputStream();
            pw = new PrintWriter(os);
            pw.write("欢迎您!client: "+socket.getLocalSocketAddress());
            //调用flush()方法将缓冲强制刷新输出
            pw.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            //关闭资源
            try {
                if(pw!=null)
                    pw.close();
                if(os!=null)
                    os.close();
                if(br!=null)
                    br.close();
                if(isr!=null)
                    isr.close();
                if(is!=null)
                    is.close();
                if(socket!=null)
                    socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

【3】运行测试

首先运行服务端:



此时还没有socket实例化,等待连接建立。


运行客户端:


此时,服务端输出如下:

注意对比客户端和服务端建立socket实例的时间和二者的hashcode!


【4】频繁通信

当然不想只发送一次,这里模拟从控制台频繁输入,发送给服务器。服务器返回消息。


服务端代码不用改,客户端测试代码如下:

  @Test
  public void testClientThread(){
    while(true){
      InputStream is=null;
      InputStreamReader isr=null;
      BufferedReader br=null;
      try {
        // 从控制台输入
        is = System.in;
        isr = new InputStreamReader(is);
        br = new BufferedReader(isr);
        String info=null;
        //这里一次输入一行
        while((info=br.readLine())!=null){
          System.out.println("当前输入的信息是:"+info);
          ClientThread thread = new ClientThread(info);
          thread.start();
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

客户端线程如下:

package com.web.hh.controller;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
public class ClientThread extends Thread{
  String info = null;
  public ClientThread(String info){
    this.info = info;
  }
  public void run(){
    try {
      Socket socket = new Socket("127.0.0.1", 8989);
      OutputStream os = socket.getOutputStream();
      os.write(info.getBytes());
      //强制刷新缓冲
      os.flush();
      // socket关闭输出通道
      socket.shutdownOutput();
      // 接受服务器返回的消息
          InputStream inputStream = socket.getInputStream();
          BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
          System.out.println("服务器返回消息说:"+br.readLine());
      os.close();
      br.close();
      inputStream.close();
      socket.close();
    } catch (UnknownHostException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

同样先运行服务端(效果同上),然后运行客户端,输入字符:


服务端输出如下:


【5】改进方案及问题分析

上面使用了多线程,可能会考虑到服务端维护一个线程池,避免为每个请求都创建一个独立线程造成的线程资源耗尽问题,这种模式称之为伪异步I/O通信。但是它底层的通信依然采用同步阻塞模型,无法从根本上解决问题。

首先我们看两个Java同步I/O的API说明。


从输入流读取:

public int read(byte b[]) throws IOException{
  return read(b,0,b.length);
}

当对Socket的输入流进行读取操作的时候,它会一直阻塞下去,直到如下三种事件发生:


有数据可读;

可用数据已经读取完毕;

发生空指针或者I/O异常;

这意味着当对方发送请求或者应答消息比较缓慢,或者网络传输较慢时,读取输入流一方的通信线程将被长时间阻塞。如果对方要求60s才能够将数据发送完成,读取一方的I/O线程也将会被同步阻塞60S,在此期间,其他接入消息只能在消息队列中排队。


写到输出流

public void write(byte b[]) throws IOException

当调用Outputstream的write方法写输出流的时候,它将会被阻塞,直到所有要发送的字节全部写入完毕,或者发生异常。当消息的接收方处理缓慢的时候,将不能及时地从TCP缓冲区读取数据,这将会导致发送方的TCP window size不断减小,直到为0,双方处于keep-alive状态,消息发送方将不能再向TCP缓冲区写入数据。这是如果采用的是同步阻塞I/O,write操作将会被无限期阻塞,直到TCP window size大于0或者发生I/O异常。


伪异步IO仅仅是对之前IO线程模型的一个简单优化,它无法从根本上解决同步IO导致的通信线程阻塞问题。下面我们就简单分析下通信对方返回应答时间过长会引起的级联故障。


(1)服务端处理缓慢,返回应答消息耗费60s,平时只需要10ms;

(2)采用伪异步IO的线程正在读取故障服务节点的响应,由于读取输入流是阻塞的,它将会被同步阻塞60s;

(3)假如所有的可用线程都被故障服务器阻塞,那后续所有的IO消息都将在队列中排队;

(4)由于线程池采用阻塞队列实现,当队列积满以后,后续入队列的操作将被阻塞;

(5)由于前端只有一个Acceptor线程接收客户端请求,它被阻塞在线程池的同步阻塞队列之后,新的客户端请求消息将被拒绝,客户端会发生大量的连接超时;

(6)由于几乎所有的连接都超时,调用者会认为系统已经崩溃,无法接收新的请求消息。

目录
相关文章
|
6月前
|
缓存 监控 Java
Java Socket编程最佳实践:优化客户端-服务器通信性能
【6月更文挑战第21天】Java Socket编程优化涉及识别性能瓶颈,如网络延迟和CPU计算。使用非阻塞I/O(NIO)和多路复用技术提升并发处理能力,减少线程上下文切换。缓存利用可减少I/O操作,异步I/O(AIO)进一步提高效率。持续监控系统性能是关键。通过实践这些策略,开发者能构建高效稳定的通信系统。
187 1
|
4月前
|
Python
python socket 简单通信
python socket 简单通信
46 1
|
4月前
|
网络协议 安全 网络安全
网络编程:基于socket的TCP/IP通信。
网络编程:基于socket的TCP/IP通信。
272 0
|
6月前
|
Java 应用服务中间件 开发者
【实战指南】Java Socket编程:构建高效的客户端-服务器通信
【6月更文挑战第21天】Java Socket编程用于构建客户端-服务器通信。`Socket`和`ServerSocket`类分别处理两端的连接。实战案例展示了一个简单的聊天应用,服务器监听端口,接收客户端连接,并使用多线程处理每个客户端消息。客户端连接服务器,发送并接收消息。了解这些基础,加上错误处理和优化,能帮你开始构建高效网络应用。
459 10
|
6月前
|
IDE Java 开发工具
从零开始学Java Socket编程:客户端与服务器通信实战
【6月更文挑战第21天】Java Socket编程教程带你从零开始构建简单的客户端-服务器通信。安装JDK后,在命令行分别运行`SimpleServer`和`SimpleClient`。服务器监听端口,接收并回显客户端消息;客户端连接服务器,发送“Hello, Server!”并显示服务器响应。这是网络通信基础,为更复杂的网络应用打下基础。开始你的Socket编程之旅吧!
102 3
|
6月前
|
Java 数据挖掘 开发者
Java网络编程进阶:Socket通信的高级特性与应用
【6月更文挑战第21天】Java Socket通信是分布式应用的基础,涉及高级特性如多路复用(Selector)和零拷贝,提升效率与响应速度。结合NIO和AIO,适用于高并发场景如游戏服务器和实时数据分析。示例展示了基于NIO的多路复用服务器实现。随着技术发展,WebSockets、HTTP/2、QUIC等新协议正变革网络通信,掌握Socket高级特性为应对未来挑战准备。
57 1
|
2月前
|
网络协议 Linux 应用服务中间件
Socket通信之网络协议基本原理
【10月更文挑战第10天】网络协议定义了机器间通信的标准格式,确保信息准确无损地传输。主要分为两种模型:OSI七层模型与TCP/IP模型。
|
6月前
|
Java
Java Socket编程与多线程:提升客户端-服务器通信的并发性能
【6月更文挑战第21天】Java网络编程中,Socket结合多线程提升并发性能,服务器对每个客户端连接启动新线程处理,如示例所示,实现每个客户端的独立操作。多线程利用多核处理器能力,避免串行等待,提升响应速度。防止死锁需减少共享资源,统一锁定顺序,使用超时和重试策略。使用synchronized、ReentrantLock等维持数据一致性。多线程带来性能提升的同时,也伴随复杂性和挑战。
112 0
|
6月前
|
安全 Java 网络安全
Java Socket编程教程:构建安全可靠的客户端-服务器通信
【6月更文挑战第21天】构建安全的Java Socket通信涉及SSL/TLS加密、异常处理和重连策略。示例中,`SecureServer`使用SSLServerSocketFactory创建加密连接,而`ReliableClient`展示异常捕获与自动重连。理解安全意识,如防数据截获和中间人攻击,是首要步骤。通过良好的编程实践,确保网络应用在复杂环境中稳定且安全。
113 0
|
3月前
|
网络协议 Linux 应用服务中间件
Socket通信之网络协议基本原理
【9月更文挑战第14天】网络协议是机器间交流的约定格式,确保信息准确传达。主要模型有OSI七层与TCP/IP模型,通过分层简化复杂网络环境。IP地址全局定位设备,MAC地址则在本地网络中定位。网络分层后,数据包层层封装,经由不同层次协议处理,最终通过Socket系统调用在应用层解析和响应。
下一篇
DataWorks