博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JAVA网络编程之Socket
阅读量:6294 次
发布时间:2019-06-22

本文共 15278 字,大约阅读时间需要 50 分钟。

在前面博客中使用到的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 地址创建服务器

方法摘要

返回值 方法名 说明
Socket accept() 侦听并接受到此套接字的连接
void bind(SocketAddress endpoint) 将 ServerSocket 绑定到特定地址(IP 地址和端口号)
void bind(SocketAddress endpoint, int backlog) 将 ServerSocket 绑定到特定地址(IP 地址和端口号)
void close() 关闭此套接字
ServerSocketChannel getChannel() 返回与此套接字关联的唯一 ServerSocketChannel 对象(如果有)
InetAddress getInetAddress() 返回此服务器套接字的本地地址
int getLocalPort() 返回此套接字在其上侦听的端口
SocketAddress getLocalSocketAddress() 返回此套接字绑定的端点的地址,如果尚未绑定则返回 null
int getReceiveBufferSize() 获取此 ServerSocket 的 SO_RCVBUF 选项的值,该值是将用于从此 ServerSocket 接受的套接字的建议缓冲区大小
boolean getReuseAddress() 测试是否启用 SO_REUSEADDR。
int getSoTimeout() 获取 SO_TIMEOUT 的设置
protected void implAccept(Socket s) ServerSocket 的子类使用此方法重写 accept() 以返回它们自己的套接字子类
boolean isBound() 返回 ServerSocket 的绑定状态
boolean isClosed() 返回 ServerSocket 的关闭状态
void setPerformancePreferences(int connectionTime, int latency, int bandwidth) 设置此 ServerSocket 的性能首选项
void setReceiveBufferSize(int size) 为从此 ServerSocket 接受的套接字的 SO_RCVBUF 选项设置默认建议值
void setReuseAddress(boolean on) 启用/禁用 SO_REUSEADDR 套接字选项
static void setSocketFactory(SocketImplFactory fac) 为应用程序设置服务器套接字实现工厂
void setSoTimeout(int timeout) 通过指定超时值启用/禁用 SO_TIMEOUT,以毫秒为单位
String toString() 作为 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) 创建一个套接字并将其连接到指定远程主机上的指定远程端口

方法摘要

返回值 方法名 说明
void bind(SocketAddress bindpoint) 将套接字绑定到本地地址
void close() 关闭此套接字
void connect(SocketAddress endpoint) 将此套接字连接到服务器
void connect(SocketAddress endpoint, int timeout) 将此套接字连接到服务器,并指定一个超时值
SocketChannel getChannel() 返回与此数据报套接字关联的唯一 SocketChannel 对象(如果有)
InetAddress getInetAddress() 返回套接字连接的地址
InputStream getInputStream() 返回此套接字的输入流
boolean getKeepAlive() 测试是否启用 SO_KEEPALIVE
InetAddress getLocalAddress() 获取套接字绑定的本地地址
int getLocalPort() 返回此套接字绑定到的本地端口
SocketAddress getLocalSocketAddress() 返回此套接字绑定的端点的地址,如果尚未绑定则返回 null
boolean getOOBInline() 测试是否启用 OOBINLINE
OutputStream getOutputStream() 返回此套接字的输出流
int getPort() 返回此套接字连接到的远程端口
int getReceiveBufferSize() 获取此 Socket 的 SO_RCVBUF 选项的值,该值是平台在 Socket 上输入时使用的缓冲区大小
SocketAddress getRemoteSocketAddress() 返回此套接字连接的端点的地址,如果未连接则返回 null
boolean getReuseAddress() 测试是否启用 SO_REUSEADDR
int getSendBufferSize() 获取此 Socket 的 SO_SNDBUF 选项的值,该值是平台在 Socket 上输出时使用的缓冲区大小
int getSoLinger() 返回 SO_LINGER 的设置
int getSoTimeout() 返回 SO_TIMEOUT 的设置
boolean getTcpNoDelay() 测试是否启用 TCP_NODELAY
int getTrafficClass() 为从此 Socket 上发送的包获取 IP 头中的流量类别或服务类型
boolean isBound() 返回套接字的绑定状态
boolean isClosed() 返回套接字的关闭状态
boolean isConnected() 返回套接字的连接状态
boolean isInputShutdown() 返回是否关闭套接字连接的半读状态 (read-half)
boolean isOutputShutdown() 返回是否关闭套接字连接的半写状态 (write-half)
void sendUrgentData(int data) 在套接字上发送一个紧急数据字节
void setKeepAlive(boolean on) 启用/禁用 SO_KEEPALIVE
void setOOBInline(boolean on) 启用/禁用 OOBINLINE(TCP 紧急数据的接收者) 默认情况下,此选项是禁用的,即在套接字上接收的 TCP 紧急数据被静默丢弃
void setPerformancePreferences(int connectionTime, int latency, int bandwidth) 设置此套接字的性能偏好
void setReceiveBufferSize(int size) 将此 Socket 的 SO_RCVBUF 选项设置为指定的值
void setReuseAddress(boolean on) 启用/禁用 SO_REUSEADDR 套接字选项
void setSendBufferSize(int size) 将此 Socket 的 SO_SNDBUF 选项设置为指定的值
static void setSocketImplFactory(SocketImplFactory fac) 为应用程序设置客户端套接字实现工厂
void setSoLinger(boolean on, int linger) 启用/禁用具有指定逗留时间(以秒为单位)的 SO_LINGER
void setSoTimeout(int timeout) 启用/禁用带有指定超时值的 SO_TIMEOUT,以毫秒为单位
void setTcpNoDelay(boolean on) 启用/禁用 TCP_NODELAY(启用/禁用 Nagle 算法)
void setTrafficClass(int tc) 为从此 Socket 上发送的包在 IP 头中设置流量类别 (traffic class) 或服务类型八位组 (type-of-service octet)
void shutdownInput() 此套接字的输入流置于“流的末尾”
void shutdownOutput() 禁用此套接字的输出流
String toString() 将此套接字转换为 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
通道关闭

源程序下载:

转载地址:http://kstta.baihongyu.com/

你可能感兴趣的文章
Linux下区分物理CPU、逻辑CPU和CPU核数
查看>>
EDAS ScheduleX 问题
查看>>
Android 表格HorizontalScrollView+ListView
查看>>
mybatis 联查
查看>>
如何使用阿里云服务器
查看>>
科创板7天受理28家公司,但后者“含金量”备受质疑
查看>>
交通运输部部长李小鹏谈及自动驾驶:包容失败、反对垄断,力争在国家层面出台指导意见...
查看>>
退市35年后,牛仔裤品牌李维斯要重新IPO了
查看>>
PHP 7.3声称速度比PHP 5快3倍还多,值得更新了!
查看>>
elasticsearch使用指南之Elasticsearch Document Index API详解、原理与示例
查看>>
操作符分类
查看>>
VCTransitionsLibrary –自定义iOS交互式转场动画的库
查看>>
11家车企联手高通、大唐,加速V2X在华商用部署
查看>>
WPF Viewport3D 解决透视模式时窗体模糊
查看>>
PowerDesigner反向生成物理数据模型
查看>>
杰思安全获数千万元A+轮投资,绿盟科技领投,德联资本跟投
查看>>
Google 的最后努力 :请求最高法院撤回 88 亿罚单
查看>>
服气!3小时竟能写出风靡全球的小游戏,还顺手就赚的盆满钵满
查看>>
第七篇:SpringBoot 2.x集成Lombok
查看>>
【对讲机的那点事】带你玩转灵通LT33公网集群对讲机
查看>>