JAVA网络编程之Socket

在前面博客中使用到的HttpsURLConnection实际上是基于HTTP协议完成获取网络资源的,而HTTP协议又是基于TCP/IP协议的,这一片博客将介绍在java中如何使用TCP/IP协议进行服务端与客户端之间的通信。
TCP/IP,Transmission Control Protocol/Internet Protocol的简写,中译名为传输控制协议/因特网互联协议,又名网络通讯协议,是Internet最基本的协议、Internet国际互联网络的基础,由网络层的IP协议和传输层的TCP协议组成。TCP/IP 定义了电子设备如何连入因特网,以及数据如何在它们之间传输的标准。协议采用了4层的层级结构,每一层都呼叫它的下一层所提供的协议来完成自己的需求。通俗而言:TCP负责发现传输的问题,一有问题就发出信号,要求重新传输,直到所有数据安全正确地传输到目的地。而IP是给因特网的每一台联网设备规定一个地址。
在JAVA中,如果要实现TCP/IP,我们需要使用ServerSocket和Socket两个类。下面简单的介绍一下这两个类。

ServerSocket

此类实现服务器套接字。服务器套接字等待请求通过网络传入。它基于该请求执行某些操作,然后可能向请求者返回结果。

构造方法

方法名说明
ServerSocket()创建非绑定服务器套接字
ServerSocket(int port)创建绑定到特定端口的服务器套接字
ServerSocket(int port, int backlog)利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号
ServerSocket(int port, int backlog, InetAddress bindAddr)使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址创建服务器

方法摘要

返回值方法名说明
Socketaccept()侦听并接受到此套接字的连接
voidbind(SocketAddress endpoint)将 ServerSocket 绑定到特定地址(IP 地址和端口号)
voidbind(SocketAddress endpoint, int backlog)将 ServerSocket 绑定到特定地址(IP 地址和端口号)
voidclose()关闭此套接字
ServerSocketChannelgetChannel()返回与此套接字关联的唯一 ServerSocketChannel 对象(如果有)
InetAddressgetInetAddress()返回此服务器套接字的本地地址
intgetLocalPort()返回此套接字在其上侦听的端口
SocketAddressgetLocalSocketAddress()返回此套接字绑定的端点的地址,如果尚未绑定则返回 null
intgetReceiveBufferSize()获取此 ServerSocket 的 SO_RCVBUF 选项的值,该值是将用于从此 ServerSocket 接受的套接字的建议缓冲区大小
booleangetReuseAddress()测试是否启用 SO_REUSEADDR。
intgetSoTimeout()获取 SO_TIMEOUT 的设置
protected voidimplAccept(Socket s)ServerSocket 的子类使用此方法重写 accept() 以返回它们自己的套接字子类
booleanisBound()返回 ServerSocket 的绑定状态
booleanisClosed()返回 ServerSocket 的关闭状态
voidsetPerformancePreferences(int connectionTime, int latency, int bandwidth)设置此 ServerSocket 的性能首选项
voidsetReceiveBufferSize(int size)为从此 ServerSocket 接受的套接字的 SO_RCVBUF 选项设置默认建议值
voidsetReuseAddress(boolean on)启用/禁用 SO_REUSEADDR 套接字选项
static voidsetSocketFactory(SocketImplFactory fac)为应用程序设置服务器套接字实现工厂
voidsetSoTimeout(int timeout)通过指定超时值启用/禁用 SO_TIMEOUT,以毫秒为单位
StringtoString()作为 String 返回此套接字的实现地址和实现端口

Socket

此类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器间通信的端点。

构造方法

方法名说明
Socket()通过系统默认类型的 SocketImpl 创建未连接套接字
Socket(InetAddress address, int port)创建一个流套接字并将其连接到指定 IP 地址的指定端口号
Socket(InetAddress address, int port, InetAddress localAddr, int localPort)创建一个套接字并将其连接到指定远程地址上的指定远程端口
Socket(Proxy proxy)创建一个未连接的套接字并指定代理类型(如果有),该代理不管其他设置如何都应被使用
protected Socket(SocketImpl impl)使用用户指定的 SocketImpl 创建一个未连接 Socket
Socket(String host, int port)创建一个流套接字并将其连接到指定主机上的指定端口号
Socket(String host, int port, InetAddress localAddr, int localPort)创建一个套接字并将其连接到指定远程主机上的指定远程端口

方法摘要

返回值方法名说明
voidbind(SocketAddress bindpoint)将套接字绑定到本地地址
voidclose()关闭此套接字
voidconnect(SocketAddress endpoint)将此套接字连接到服务器
voidconnect(SocketAddress endpoint, int timeout)将此套接字连接到服务器,并指定一个超时值
SocketChannelgetChannel()返回与此数据报套接字关联的唯一 SocketChannel 对象(如果有)
InetAddressgetInetAddress()返回套接字连接的地址
InputStreamgetInputStream()返回此套接字的输入流
booleangetKeepAlive()测试是否启用 SO_KEEPALIVE
InetAddressgetLocalAddress()获取套接字绑定的本地地址
intgetLocalPort()返回此套接字绑定到的本地端口
SocketAddressgetLocalSocketAddress()返回此套接字绑定的端点的地址,如果尚未绑定则返回 null
booleangetOOBInline()测试是否启用 OOBINLINE
OutputStreamgetOutputStream()返回此套接字的输出流
intgetPort()返回此套接字连接到的远程端口
intgetReceiveBufferSize()获取此 Socket 的 SO_RCVBUF 选项的值,该值是平台在 Socket 上输入时使用的缓冲区大小
SocketAddressgetRemoteSocketAddress()返回此套接字连接的端点的地址,如果未连接则返回 null
booleangetReuseAddress()测试是否启用 SO_REUSEADDR
intgetSendBufferSize()获取此 Socket 的 SO_SNDBUF 选项的值,该值是平台在 Socket 上输出时使用的缓冲区大小
intgetSoLinger()返回 SO_LINGER 的设置
intgetSoTimeout()返回 SO_TIMEOUT 的设置
booleangetTcpNoDelay()测试是否启用 TCP_NODELAY
intgetTrafficClass()为从此 Socket 上发送的包获取 IP 头中的流量类别或服务类型
booleanisBound()返回套接字的绑定状态
booleanisClosed()返回套接字的关闭状态
booleanisConnected()返回套接字的连接状态
booleanisInputShutdown()返回是否关闭套接字连接的半读状态 (read-half)
booleanisOutputShutdown()返回是否关闭套接字连接的半写状态 (write-half)
voidsendUrgentData(int data)在套接字上发送一个紧急数据字节
voidsetKeepAlive(boolean on)启用/禁用 SO_KEEPALIVE
voidsetOOBInline(boolean on)启用/禁用 OOBINLINE(TCP 紧急数据的接收者) 默认情况下,此选项是禁用的,即在套接字上接收的 TCP 紧急数据被静默丢弃
voidsetPerformancePreferences(int connectionTime, int latency, int bandwidth)设置此套接字的性能偏好
voidsetReceiveBufferSize(int size)将此 Socket 的 SO_RCVBUF 选项设置为指定的值
voidsetReuseAddress(boolean on)启用/禁用 SO_REUSEADDR 套接字选项
voidsetSendBufferSize(int size)将此 Socket 的 SO_SNDBUF 选项设置为指定的值
static voidsetSocketImplFactory(SocketImplFactory fac)为应用程序设置客户端套接字实现工厂
voidsetSoLinger(boolean on, int linger)启用/禁用具有指定逗留时间(以秒为单位)的 SO_LINGER
voidsetSoTimeout(int timeout)启用/禁用带有指定超时值的 SO_TIMEOUT,以毫秒为单位
voidsetTcpNoDelay(boolean on)启用/禁用 TCP_NODELAY(启用/禁用 Nagle 算法)
voidsetTrafficClass(int tc)为从此 Socket 上发送的包在 IP 头中设置流量类别 (traffic class) 或服务类型八位组 (type-of-service octet)
voidshutdownInput()此套接字的输入流置于“流的末尾”
voidshutdownOutput()禁用此套接字的输出流
StringtoString()将此套接字转换为 String

上面为ServerSocket和Socket两个类的概要信息,ServerSocket为服务端,调用ServerSocket的accept() 方法可以监听客户端的连接,当有客户端连接成功,将在服务端与客户端之间建立通道。

Created with Raphaël 2.1.0ServerSocketServerSocketSocketSocket实例化对象调用accept()监听客户端连接(堵塞)实例化对象连接服务端建立通道读取或发送数据读取或发送数据读取或发送数据的顺序与客户端无先后关系,根据实际情况close()close()

下面我们通过一些示例来演示如何使用Socket进行通信。

单工

单工(simplex)指仅能单方向传输数据。通信双方中,一方固定为发送端,一方则固定为接收端。
示例代码:
服务端:

package com.jianggujin.socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 单工通信服务端
 * 
 * @author jianggujin
 * 
 */
public class SimplexServerDemo
{
   public static void main(String[] args) throws IOException
   {
      ServerSocket serverSocket = new ServerSocket(9999);
      System.out.println("单工通信服务端启动成功...");
      // 等待客户端连接
      Socket socket = serverSocket.accept();
      // 获得通道输入流
      BufferedReader reader = new BufferedReader(new InputStreamReader(
            socket.getInputStream()));
      String data = null;
      while ((data = reader.readLine()) != null && !data.equals("quit"))
      {
         System.out.println("接收到客户端消息:" + data);
      }
      socket.close();
      System.out.println("通道关闭");
      serverSocket.close();
   }
}

客户端

package com.jianggujin.socket;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;

/**
 * 单工通信客户端
 * 
 * @author jianggujin
 * 
 */
public class SimplexClientDemo
{
   public static void main(String[] args) throws IOException
   {
      // 连接服务端
      Socket socket = new Socket("127.0.0.1", 9999);
      System.out.println("连接服务端成功");
      // 获得通道输出流
      PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
      for (int i = 0; i < 10; i++)
      {
         String data = System.currentTimeMillis() + "";
         writer.println(data);
         System.out.println("向服务端发送数据:" + data);
         try
         {
            // 睡眠100毫秒
            Thread.sleep(100);
         }
         catch (Exception e)
         {
         }
      }
      writer.println("quit");
      socket.close();
      System.out.println("通道关闭");
   }
}

运行结果:
服务端:
单工通信服务端启动成功…
接收到客户端消息:1451985005390
接收到客户端消息:1451985005484
接收到客户端消息:1451985005593
接收到客户端消息:1451985005687
接收到客户端消息:1451985005796
接收到客户端消息:1451985005890
接收到客户端消息:1451985006000
接收到客户端消息:1451985006093
接收到客户端消息:1451985006187
接收到客户端消息:1451985006296
通道关闭

客户端:
连接服务端成功
向服务端发送数据:1451985005390
向服务端发送数据:1451985005484
向服务端发送数据:1451985005593
向服务端发送数据:1451985005687
向服务端发送数据:1451985005796
向服务端发送数据:1451985005890
向服务端发送数据:1451985006000
向服务端发送数据:1451985006093
向服务端发送数据:1451985006187
向服务端发送数据:1451985006296
通道关闭

半双工

半双工(half-duplex)的系统允许二台设备之间的双向资料传输,但不能同时进行。因此同一时间只允许一设备传送资料,若另一设备要传送资料,需等原来传送资料的设备传送完成后再处理。
半双工在通信过程中,信息既可由A传到B,又能由B传A,但只能有一个方向上的传输存在。采用半双工方式时,通信系统每一端的发送器和接收器,通过收/发开关转接到通信线上,进行方向的切换,因此,会产生时间延迟。收/发开关实际上是由软件控制的电子开关。
半双工的系统可以比喻作单线铁路。若铁道上无列车行驶时,任一方向的车都可以通过。但若路轨上有车,相反方向的列车需等该列车通过道路后才能通过。
无线电对讲机就是使用半双工系统。由于对讲机传送及接收使用相同的频率,不允许同时进行。因此一方讲完后,需设法告知另一方讲话结束(例如讲完后加上”OVER”),另一方才知道可以开始讲话。
示例代码:
服务端:

package com.jianggujin.socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 半双工通信服务端
 * 
 * @author jianggujin
 * 
 */
public class HalfDuplexServerDemo
{
   public static void main(String[] args) throws IOException
   {
      ServerSocket serverSocket = new ServerSocket(9999);
      System.out.println("半双工通信服务端启动成功...");
      // 等待客户端连接
      Socket socket = serverSocket.accept();
      // 获得通道输入流
      BufferedReader reader = new BufferedReader(new InputStreamReader(
            socket.getInputStream()));
      // 获得通道输出流
      PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
      String data = null;
      while ((data = reader.readLine()) != null && !data.equals("quit"))
      {
         System.out.println("接收到客户端消息:" + data);
         String backData = System.currentTimeMillis() + "";
         writer.println(backData);
         System.out.println("向客户端发送数据:" + backData);
      }
      socket.close();
      System.out.println("通道关闭");
      serverSocket.close();
   }
}

客户端

package com.jianggujin.socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

/**
 * 半双工通信客户端
 * 
 * @author jianggujin
 * 
 */
public class HalfDuplexClientDemo
{
   public static void main(String[] args) throws IOException
   {
      // 连接服务端
      Socket socket = new Socket("127.0.0.1", 9999);
      System.out.println("连接服务端成功");
      // 获得通道输入流
      BufferedReader reader = new BufferedReader(new InputStreamReader(
            socket.getInputStream()));
      // 获得通道输出流
      PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
      for (int i = 0; i < 10; i++)
      {
         String data = System.currentTimeMillis() + "";
         writer.println(data);
         System.out.println("向服务端发送数据:" + data);
         String backData = reader.readLine();
         System.out.println("服务端返回数据:" + backData);
         try
         {
            // 睡眠100毫秒
            Thread.sleep(100);
         }
         catch (Exception e)
         {
         }
      }
      writer.println("quit");
      socket.close();
      System.out.println("通道关闭");
   }
}

运行结果:
服务端:
半双工通信服务端启动成功…
接收到客户端消息:1451990773738
向客户端发送数据:1451990773738
接收到客户端消息:1451990773848
向客户端发送数据:1451990773848
接收到客户端消息:1451990773941
向客户端发送数据:1451990773941
接收到客户端消息:1451990774035
向客户端发送数据:1451990774035
接收到客户端消息:1451990774144
向客户端发送数据:1451990774144
接收到客户端消息:1451990774238
向客户端发送数据:1451990774238
接收到客户端消息:1451990774348
向客户端发送数据:1451990774348
接收到客户端消息:1451990774441
向客户端发送数据:1451990774441
接收到客户端消息:1451990774551
向客户端发送数据:1451990774551
接收到客户端消息:1451990774644
向客户端发送数据:1451990774644
通道关闭

客户端:
连接服务端成功
向服务端发送数据:1451990773738
服务端返回数据:1451990773738
向服务端发送数据:1451990773848
服务端返回数据:1451990773848
向服务端发送数据:1451990773941
服务端返回数据:1451990773941
向服务端发送数据:1451990774035
服务端返回数据:1451990774035
向服务端发送数据:1451990774144
服务端返回数据:1451990774144
向服务端发送数据:1451990774238
服务端返回数据:1451990774238
向服务端发送数据:1451990774348
服务端返回数据:1451990774348
向服务端发送数据:1451990774441
服务端返回数据:1451990774441
向服务端发送数据:1451990774551
服务端返回数据:1451990774551
向服务端发送数据:1451990774644
服务端返回数据:1451990774644
通道关闭

全双工

全双工(full-duplex)的系统允许二台设备间同时进行双向资料传输。一般的电话、手机就是全双工的系统,因为在讲话时同时也可以听到对方的声音。
全双工在通信过程中,线路上存在A到B和B到A的双向信号传输。在全双工方式下,通信系统的每一端都设置了发送器和接收器,因此,能控制数据同时在两个方向上传送。全双工方式无需进行方向的切换,因此,没有切换操作所产生的时间延迟,这对那些不能有时间延误的交互式应用(例如远程监测和控制系统)十分有利。这种方式要求通讯双方均有发送器和接收器,同时,需要两根数据线传送数据信号(可能还需要控制线和状态线,以及地线)。
全双工的系统可以用一般的双向车道形容。两个方向的车辆因使用不同的车道,因此不会互相影响

示例代码:
服务端:

package com.jianggujin.socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 全双工通信服务端
 * 
 * @author jianggujin
 * 
 */
public class FullDuplexServerDemo
{
   public static void main(String[] args) throws IOException
   {
      ServerSocket serverSocket = new ServerSocket(9999);
      System.out.println("全双工通信服务端启动成功...");
      // 等待客户端连接
      final Socket socket = serverSocket.accept();
      // 获得通道输入流
      BufferedReader reader = new BufferedReader(new InputStreamReader(
            socket.getInputStream()));
      // 获得通道输出流
      final PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
      String data = null;
      new Thread()
      {
         public void run()
         {
            while (!socket.isClosed())
            {
               String backData = System.currentTimeMillis() + "";
               writer.println(backData);
               System.out.println("向客户端发送数据:" + backData);
               try
               {
                  // 睡眠1000毫秒
                  Thread.sleep(500);
               }
               catch (Exception e)
               {
               }
            }
         };
      }.start();
      while ((data = reader.readLine()) != null && !data.equals("quit"))
      {
         System.out.println("接收到客户端消息:" + data);
      }
      socket.close();
      System.out.println("通道关闭");
      serverSocket.close();
   }
}

客户端

package com.jianggujin.socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.SocketException;

/**
 * 全双工通信客户端
 * 
 * @author jianggujin
 * 
 */
public class FullDuplexClientDemo
{
   public static void main(String[] args) throws IOException
   {
      // 连接服务端
      final Socket socket = new Socket("127.0.0.1", 9999);
      System.out.println("连接服务端成功");
      // 获得通道输入流
      final BufferedReader reader = new BufferedReader(new InputStreamReader(
            socket.getInputStream()));
      // 获得通道输出流
      PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
      new Thread()
      {
         public void run()
         {
            String data = null;
            try
            {
               while (!socket.isClosed() && (data = reader.readLine()) != null)
               {
                  System.out.println("接收到服务端消息:" + data);
               }
            }
            catch (SocketException e)
            {
               if (!"Socket closed".equalsIgnoreCase(e.getMessage()))
               {
                  e.printStackTrace();
               }
            }
            catch (IOException e)
            {
               e.printStackTrace();
            }
         };
      }.start();
      for (int i = 0; i < 5; i++)
      {
         String data = System.currentTimeMillis() + "";
         writer.println(data);
         System.out.println("向服务端发送数据:" + data);
         try
         {
            // 睡眠100毫秒
            Thread.sleep(1000);
         }
         catch (Exception e)
         {
         }
      }
      writer.println("quit");
      socket.close();
      System.out.println("通道关闭");
   }
}

运行结果
服务端:
全双工通信服务端启动成功…
向客户端发送数据:1451991642926
接收到客户端消息:1451991642926
向客户端发送数据:1451991643426
向客户端发送数据:1451991643926
接收到客户端消息:1451991643926
向客户端发送数据:1451991644426
向客户端发送数据:1451991644926
接收到客户端消息:1451991644926
向客户端发送数据:1451991645426
向客户端发送数据:1451991645926
接收到客户端消息:1451991645926
向客户端发送数据:1451991646426
向客户端发送数据:1451991646926
接收到客户端消息:1451991646926
向客户端发送数据:1451991647426
向客户端发送数据:1451991647926
通道关闭

客户端:
连接服务端成功
向服务端发送数据:1451991642926
接收到服务端消息:1451991642926
接收到服务端消息:1451991643426
接收到服务端消息:1451991643926
向服务端发送数据:1451991643926
接收到服务端消息:1451991644426
接收到服务端消息:1451991644926
向服务端发送数据:1451991644926
接收到服务端消息:1451991645426
接收到服务端消息:1451991645926
向服务端发送数据:1451991645926
接收到服务端消息:1451991646426
接收到服务端消息:1451991646926
向服务端发送数据:1451991646926
接收到服务端消息:1451991647426
接收到服务端消息:1451991647926
通道关闭

源程序下载:
JAVA Socket通信示例

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值