【JAVA】Socket 编程

对于Java Socket编程而言,有两个概念,一个是ServerSocket,一个是Socket。服务端和客户端之间通过Socket建立连接,之后它们就可以进行通信了。首先ServerSocket将在服务端监听某个端口,当发现客户端有Socket来试图连接它时,它会accept该Socket的连接请求,同时在服务端建立一个对应的Socket与之进行通信。这样就有两个Socket了,客户端和服务端各一个。

对于Socket之间的通信其实很简单,服务端往Socket的输出流里面写东西,客户端就可以通过Socket的输入流读取对应的内容。Socket与Socket之间是双向连通的,所以客户端也可以往对应的Socket输出流里面写东西,然后服务端对应的Socket的输入流就可以读出对应的内容。


public class Server {

public static void main(String[] args) {

try {

ServerSocket server = new ServerSocket(9900);

// 阻塞式方法,尝试接收连接 调用native方法接收请求

Socket socket = server.accept();

//接收数据

Reader reader = new InputStreamReader(socket.getInputStream());

char[] clientstr = new char[64];

int length = reader.read(clientstr);

System.out.println(new String(clientstr, 0, length));

//发送数据

Writer writer = new OutputStreamWriter(socket.getOutputStream());

writer.write("Return param");

writer.flush();//注意刷新流缓冲 ,避免双向等待

writer.close();

reader.close();

socket.close();

server.close();

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

server.accept()方法执行时,挂起等待接收连接 并得到Socket,其中accept 中调用的socketAccept方法为native方法,通过内核得到连接信息


public class Client {

public static void main(String[] args) {

try {

Socket client = new Socket("127.0.0.1", 9900);

Writer writer = new OutputStreamWriter(client.getOutputStream());

writer.write("Request param");

writer.flush();// 刷新该流的缓冲

//获取返回结果

Reader reader = new InputStreamReader(client.getInputStream());

char[] clientstr = new char[64];

int length = reader.read(clientstr);

System.out.println(new String(clientstr, 0, length));

writer.close();

reader.close();

client.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

这里要flush一下,只有这样服务端才能收到客户端发送的数据,否则可能会引起两边无限的互相等待


多个客户端连接同一个服务端

像前面讲的两个例子都是服务端接收一个客户端的请求之后就结束了,不能再接收其他客户端的请求了,这往往是不能满足我们的要求的。通常我们会这样做:

public class Server {

   public static void main(String args[]) throws IOException {
      //为了简单起见,所有的异常信息都往外抛
     int port = 8899;
      //定义一个ServerSocket监听在端口8899上
     ServerSocket server = new ServerSocket(port);
      while (true) {
         //server尝试接收其他Socket的连接请求,server的accept方法是阻塞式的
       Socket socket = server.accept();
         //跟客户端建立好连接之后,我们就可以获取socket的InputStream,并从中读取客户端发过来的信息了。
       Reader reader = new InputStreamReader(socket.getInputStream());
         char chars[] = new char[64];
         int len;
         StringBuilder sb = new StringBuilder();
         String temp;
         int index;
         while ((len=reader.read(chars)) != -1) {
            temp = new String(chars, 0, len);
            if ((index = temp.indexOf("eof")) != -1) {//遇到eof时就结束接收
                sb.append(temp.substring(0, index));
                break;
            }
            sb.append(temp);
         }
         System.out.println("from client: " + sb);
         //读完后写一句
       Writer writer = new OutputStreamWriter(socket.getOutputStream());
         writer.write("Hello Client.");
         writer.flush();
         writer.close();
         reader.close();
         socket.close();
      }
   }

}

在上面代码中我们用了一个死循环,在循环体里面ServerSocket调用其accept方法试图接收来自客户端的连接请求。当没有接收到请求的时候,程序会在这里阻塞直到接收到来自客户端的连接请求,之后会跟当前建立好连接的客户端进行通信,完了后会接着执行循环体再次尝试接收新的连接请求。这样我们的ServerSocket就能接收来自所有客户端的连接请求了,并且与它们进行通信了。这就实现了一个简单的一个服务端与多个客户端进行通信的模式。

上述例子中虽然实现了一个服务端跟多个客户端进行通信,但是还存在一个问题。在上述例子中,我们的服务端处理客户端的连接请求是同步进行的,每次接收到来自客户端的连接请求后,都要先跟当前的客户端通信完之后才能再处理下一个连接请求。这在并发比较多的情况下会严重影响程序的性能,为此,我们可以把它改为如下这种异步处理与客户端通信的方式:

public class Server {

   public static void main(String args[]) throws IOException {
      //为了简单起见,所有的异常信息都往外抛
     int port = 8899;
      //定义一个ServerSocket监听在端口8899上
     ServerSocket server = new ServerSocket(port);
      while (true) {
         //server尝试接收其他Socket的连接请求,server的accept方法是阻塞式的
         Socket socket = server.accept();
         //每接收到一个Socket就建立一个新的线程来处理它
         new Thread(new Task(socket)).start();
      }
   }

   /**
    * 用来处理Socket请求的
   */
   static class Task implements Runnable {

      private Socket socket;

      public Task(Socket socket) {
         this.socket = socket;
      }

      public void run() {

         try {

            handleSocket();
         } catch (Exception e) {
            e.printStackTrace();
         }
      }

      /**
       * 跟客户端Socket进行通信
       * @throws Exception
       */
      private void handleSocket() throws Exception {
         Reader reader = new InputStreamReader(socket.getInputStream());
         char chars[] = new char[64];
         int len;
         StringBuilder sb = new StringBuilder();
         String temp;
         int index;
         while ((len=reader.read(chars)) != -1) {
            temp = new String(chars, 0, len);
            if ((index = temp.indexOf("eof")) != -1) {//遇到eof时就结束接收
             sb.append(temp.substring(0, index));
                break;
            }
            sb.append(temp);
         }
         System.out.println("from client: " + sb);
         //读完后写一句
       Writer writer = new OutputStreamWriter(socket.getOutputStream());
         writer.write("Hello Client.");
         writer.flush();
         writer.close();
         reader.close();
         socket.close();
      }

   }

}

在上面代码中,每次ServerSocket接收到一个新的Socket连接请求后都会新起一个线程来跟当前Socket进行通信,这样就达到了异步处理与客户端Socket进行通信的情况。

在从Socket的InputStream中接收数据时,像上面那样一点点的读就太复杂了,有时候我们就会换成使用BufferedReader来一次读一行,如:

public class Server {

   public static void main(String args[]) throws IOException {
      //为了简单起见,所有的异常信息都往外抛
     int port = 8899;
      //定义一个ServerSocket监听在端口8899上
     ServerSocket server = new ServerSocket(port);
      while (true) {
         //server尝试接收其他Socket的连接请求,server的accept方法是阻塞式的
         Socket socket = server.accept();
         //每接收到一个Socket就建立一个新的线程来处理它
         new Thread(new Task(socket)).start();
      }
   }

   /**
    * 用来处理Socket请求的
   */
   static class Task implements Runnable {

      private Socket socket;

      public Task(Socket socket) {
         this.socket = socket;
      }

      public void run() {
         try {
            handleSocket();
         } catch (Exception e) {
            e.printStackTrace();
         }
      }

      /**
       * 跟客户端Socket进行通信
      * @throws Exception
       */
      private void handleSocket() throws Exception {
         BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
         StringBuilder sb = new StringBuilder();
         String temp;
         int index;
         while ((temp=br.readLine()) != null) {
            System.out.println(temp);
            if ((index = temp.indexOf("eof")) != -1) {//遇到eof时就结束接收
             sb.append(temp.substring(0, index));
                break;
            }
            sb.append(temp);
         }
         System.out.println("from client: " + sb);
         //读完后写一句
       Writer writer = new OutputStreamWriter(socket.getOutputStream());
         writer.write("Hello Client.");
         writer.write("eof\n");
         writer.flush();
         writer.close();
         br.close();
         socket.close();
      }
   }
}

这个时候需要注意的是,BufferedReader的readLine方法是一次读一行的,这个方法是阻塞的,直到它读到了一行数据为止程序才会继续往下执行,那么readLine什么时候才会读到一行呢?直到程序遇到了换行符或者是对应流的结束符readLine方法才会认为读到了一行,才会结束其阻塞,让程序继续往下执行。所以我们在使用BufferedReader的readLine读取数据的时候一定要记得在对应的输出流里面一定要写入换行符(流结束之后会自动标记为结束,readLine可以识别),写入换行符之后一定记得如果输出流不是马上关闭的情况下记得flush一下,这样数据才会真正的从缓冲区里面写入。对应上面的代码我们的客户端程序应该这样写:

public class Client {

   public static void main(String args[]) throws Exception {
      //为了简单起见,所有的异常都直接往外抛
     String host = "127.0.0.1";  //要连接的服务端IP地址
     int port = 8899;   //要连接的服务端对应的监听端口
     //与服务端建立连接
     Socket client = new Socket(host, port);
      //建立连接后就可以往服务端写数据了
     Writer writer = new OutputStreamWriter(client.getOutputStream());
      writer.write("Hello Server.");
      writer.write("eof\n");
      writer.flush();
      //写完以后进行读操作
     BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
      StringBuffer sb = new StringBuffer();
      String temp;
      int index;
      while ((temp=br.readLine()) != null) {
         if ((index = temp.indexOf("eof")) != -1) {
            sb.append(temp.substring(0, index));
            break;
         }
         sb.append(temp);
      }
      System.out.println("from server: " + sb);
      writer.close();
      br.close();
      client.close();
   }
}

4、设置超时时间

假设有这样一种需求,我们的客户端需要通过Socket从服务端获取到XX信息,然后给用户展示在页面上。我们知道Socket在读数据的时候是阻塞式的,如果没有读到数据程序会一直阻塞在那里。在同步请求的时候我们肯定是不能允许这样的情况发生的,这就需要我们在请求达到一定的时间后控制阻塞的中断,让程序得以继续运行。Socket为我们提供了一个setSoTimeout()方法来设置接收数据的超时时间,单位是毫秒。当设置的超时时间大于0,并且超过了这一时间Socket还没有接收到返回的数据的话,Socket就会抛出一个SocketTimeoutException。

假设我们需要控制我们的客户端在开始读取数据10秒后还没有读到数据就中断阻塞的话我们可以这样做:

public class Client {

   public static void main(String args[]) throws Exception {
      //为了简单起见,所有的异常都直接往外抛
     String host = "127.0.0.1";  //要连接的服务端IP地址
     int port = 8899;   //要连接的服务端对应的监听端口
     //与服务端建立连接
     Socket client = new Socket(host, port);
      //建立连接后就可以往服务端写数据了
     Writer writer = new OutputStreamWriter(client.getOutputStream());
      writer.write("Hello Server.");
      writer.write("eof\n");
      writer.flush();
      //写完以后进行读操作
     BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
      //设置超时间为10秒
     client.setSoTimeout(10*1000);
      StringBuffer sb = new StringBuffer();
      String temp;
      int index;
      try {
         while ((temp=br.readLine()) != null) {
            if ((index = temp.indexOf("eof")) != -1) {
                sb.append(temp.substring(0, index));
                break;
            }
            sb.append(temp);
         }
      } catch (SocketTimeoutException e) {
         System.out.println("数据读取超时。");
      }
      System.out.println("from server: " + sb);
      writer.close();
      br.close();
      client.close();
   }
}

5、接收数据乱码

对于这种服务端或客户端接收中文乱码的情况通常是因为数据发送时使用的编码跟接收时候使用的编码不一致。比如有下面这样一段服务端代码:

public class Server {

   public static void main(String args[]) throws IOException {
      //为了简单起见,所有的异常信息都往外抛
      int port = 8899;
      //定义一个ServerSocket监听在端口8899上
      ServerSocket server = new ServerSocket(port);
      while (true) {
         //server尝试接收其他Socket的连接请求,server的accept方法是阻塞式的
         Socket socket = server.accept();
         //每接收到一个Socket就建立一个新的线程来处理它
         new Thread(new Task(socket)).start();
      }
   }

   /**
    * 用来处理Socket请求的
    */
   static class Task implements Runnable {

      private Socket socket;

      public Task(Socket socket) {
         this.socket = socket;
      }

      public void run() {
         try {
            handleSocket();
         } catch (Exception e) {
            e.printStackTrace();
         }
      }

      /**
       * 跟客户端Socket进行通信
      * @throws Exception
       */
      private void handleSocket() throws Exception {
         BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "GBK"));
         StringBuilder sb = new StringBuilder();
         String temp;
         int index;
         while ((temp=br.readLine()) != null) {
            System.out.println(temp);
            if ((index = temp.indexOf("eof")) != -1) {//遇到eof时就结束接收
             sb.append(temp.substring(0, index));
                break;
            }
            sb.append(temp);
         }
         System.out.println("客户端: " + sb);
         //读完后写一句
       Writer writer = new OutputStreamWriter(socket.getOutputStream(), "UTF-8");
         writer.write("你好,客户端。");
         writer.write("eof\n");
         writer.flush();
         writer.close();
         br.close();
         socket.close();
      }
   }
}

这里用来测试我就弄的混乱了一点。在上面服务端代码中我们在定义输入流的时候明确定义了使用GBK编码来读取数据,而在定义输出流的时候明确指定了将使用UTF-8编码来发送数据。如果客户端上送数据的时候不以GBK编码来发送的话服务端接收的数据就很有可能会乱码;同样如果客户端接收数据的时候不以服务端发送数据的编码,即UTF-8编码来接收数据的话也极有可能会出现数据乱码的情况。所以,对于上述服务端代码,为使我们的程序能够读取对方发送过来的数据,而不出现乱码情况,我们的客户端应该是这样的:

public class Client {

public static void main(String args[]) throws Exception {

//为了简单起见,所有的异常都直接往外抛

String host = "127.0.0.1";  //要连接的服务端IP地址

int port = 8899;   //要连接的服务端对应的监听端口

//与服务端建立连接

Socket client = new Socket(host, port);

//建立连接后就可以往服务端写数据了

Writer writer = new OutputStreamWriter(client.getOutputStream(), "GBK");

writer.write("你好,服务端。");

writer.write("eof\n");

writer.flush();

//写完以后进行读操作

BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8"));

//设置超时间为10秒

client.setSoTimeout(10*1000);

StringBuffer sb = new StringBuffer();

String temp;

int index;

try {

while ((temp=br.readLine()) != null) {

if ((index = temp.indexOf("eof")) != -1) {

sb.append(temp.substring(0, index));

break;

}

sb.append(temp);

}

} catch (SocketTimeoutException e) {

System.out.println("数据读取超时。");

}

System.out.println("服务端: " + sb);

writer.close();

br.close();

client.close();

}

}

时间: 2024-10-17 06:40:26

【JAVA】Socket 编程的相关文章

Java Socket编程

对于Java Socket编程而言,有两个概念,一个是ServerSocket,一个是Socket.服务端和客户端之间通过Socket建立连接,之后它们就可以进行通信了.首先ServerSocket将在服务端监听某个端口,当发现客户端有Socket来试图连接它时,它会accept该Socket的连接请求,同时在服务端建立一个对应的Socket与之进行通信.这样就有两个Socket了,客户端和服务端各一个. 客户端写服务端读 服务端代码 public class Server { public s

Java Socket编程readLine返回null,read返回-1的条件

客户端正常关闭socket的时候,服务器端的readLine()方法会返回null,或者read()方法会返回-1 Java Socket编程readLine返回null,read返回-1的条件,布布扣,bubuko.com

【转】Java Socket编程

原文地址:Java Socket编程 Java Socket编程 对于Java Socket编程而言,有两个概念,一个是ServerSocket,一个是Socket.服务端和客户端之间通过Socket建立连接,之后它们就可以进行通信了.首先ServerSocket将在服务端监听某个端口,当发现客户端有Socket来试图连接它时,它会accept该Socket的连接请求,同时在服务端建立一个对应的Socket与之进行通信.这样就有两个Socket了,客户端和服务端各一个. 对于Socket之间的通

【Java】Java Socket编程(1)基本的术语和概念

计算机程序能够相互联网,相互通讯,这使一切都成为可能,这也是当今互联网存在的基础.那么程序是如何通过网络相互通信的呢?这就是我记录这系列的笔记的原因.Java语言从一开始就是为了互联网而设计的,它为实现程序的相互通信提供了许多有用API,这类应用编程接口被称为套接字(Socket).在开始学习Java Socket之前我们需要先来了解一下基本的术语和概念. 1.计算机网络 计算机网络由一组通过通信信道(Communication channel)相互连接的机器组成.这些机器被称为:主机(host

Java Socket编程详细解说

Java Socket编程 JavaSocketServerSocket乱码超时 Java Socket编程 对于Java Socket编程而言,有两个概念,一个是ServerSocket,一个是Socket.服务端和客户端之间通过Socket建立连接,之后它们就可以进行通信了.首先ServerSocket将在服务端监听某个端口,当发现客户端有Socket来试图连接它时,它会accept该Socket的连接请求,同时在服务端建立一个对应的Socket与之进行通信.这样就有两个Socket了,客户

如何为可扩展系统进行Java Socket编程

从简单I/O到异步非阻塞channel的Java Socket模型演变之旅 上世纪九十年代后期,我在一家在线视频游戏工资工作,在哪里我主要的工作就是编写Unix Unix Berkley Socket和Windows WinSock代码.我的任务是确保视频游戏客户端和一个游戏服务器通信.很幸运有这样的机会写一些Java Socket代码,我对Java流式网络编程和简洁明了的API着迷.这一点都不让人惊讶,Java最初就是设计促进智能设备之间的通信,这一点很好的转移到了桌面应用和服务器应用. 19

Java Socket编程基础篇

原文地址:Java Socket编程----通信是这样炼成的 Java最初是作为网络编程语言出现的,其对网络提供了高度的支持,使得客户端和服务器的沟通变成了现实,而在网络编程中,使用最多的就是Socket.像大家熟悉的QQ.MSN都使用了Socket相关的技术.下面就让我们一起揭开Socket的神秘面纱. Socket编程 网络基础知识点: 两台计算机间进行通讯需要以下三个条件 IP地址.协议.端口号: IP地址:定位应用所在机器的网络位置.(比如家庭住址:北京市朝阳区XX街道XX小区) 端口号

20182332 实验四《Java Socket编程 》实验报告

20182332 实验肆<数据结构与面向对象程序设计>实验报告 课程:<程序设计与数据结构> 班级: 1823 姓名: 盛国榕 学号:20182332 实验教师:王志强 实验日期:2019年9月30日 必修/选修: 必修 1.实验内容 (一)Java Socket编程 1.学习蓝墨云上教材<Java和Android编程>"第16章 输入/输出 "和"第22章 网络",学习JavaSocket编程 2.结对编程.结对伙伴A编写客户端

(转)Java Socket编程

原文出自:http://www.cnblogs.com/rocomp/p/4790340.html Socket是网络驱动层提供给应用程序编程接口和一种机制.可以把Socket比喻成一个港口码头,应用程序只要把货物放到港口码头上,就算完成了货物的运送.对于接收方应用程序也要创建一个港口码头,只需等待货物到达码头后将货物取走. InetAddress          InetAddress类用于标识网络上的硬件资源,标识互联网协议(IP)地址.           该类没有构造方法       

Java Socket 编程

1. 背景 网络编程是指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来. java.net 包中 J2SE 的 API 包含有类和接口,它们提供低层次的通信细节.你可以直接使用这些类和接口,来专注于解决问题,而不用关注通信细节. java.net 包中提供了两种常见的网络协议的支持: TCP:TCP 是传输控制协议的缩写,它保障了两个应用程序之间的可靠通信.通常用于互联网协议,被称 TCP / IP. UDP:UDP 是用户数据报协议的缩写,一个无连接的协议.提供了应用程序之间要