本文共 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(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() | 通过系统默认类型的 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() 方法可以监听客户端的连接,当有客户端连接成功,将在服务端与客户端之间建立通道。
下面我们通过一些示例来演示如何使用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/