您的位置:首页 > 理论基础 > 计算机网络

JAVA网络编程之Socket

2016-01-05 19:06 603 查看
在前面博客中使用到的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通信示例
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息