背景:
在互联网上,数据按有限大小的包在网上传输,这些包称为数据报(datagram).每个包包含一个首部和一个有效载荷。首部包含发送的地址和端口、包来自的地址和端口、检验和以及其他管理信息。有效载荷包含数据本身,由于数据长度有限,通常将数据分解为多个包,在接收方在重新组合。在传输的过程,包可能会丢失、数据被破话,需要重传或者各个包重新排序。而这个过程是很繁重的工作,需要大量负载的代码。所以这时候,Socket就出现了。Socket允许程序员将网络看作是另外一个可以读写字节的流,掩盖了网络底层细节的实现,如错误检查、包大小、包分解、包重传、网络地址等。
Socket的使用:
Socket是两台主机之间的一个连接,七个基本步骤:
- 连接远程主机
- 发送数据
- 接收数据
- 关闭连接
- 绑定端口
- 监听入站数据
- 在绑定端口接受来自远程服务器的连接
客户端Socket创建:1、使用构造函数创建一个新的Socket 2、Socket尝试连接远程主机。一旦建立连接(全双工),客户端和服务器就可以互通数据,现在大多数网络协议都支持重复2、3步骤,如HTTP 1.1和FTP协议。
客户端Socket
每个客户端Socket构造函数指定要连接的主机和端口。主机可以是String或IntelAddress,远程端口指定为1到65535之间的int值。
public Socket(String host,
int port)
throws UnknownHostException, IOException
publicSocket(InetAddress address,
int port)
throws IOException
从服务器读数据
Socket socket=new Socket("time.nist.gov",13);
这里"time.nist.gov"是域名,要连接的远程服务器,13是端口,通过该Socket连接国家标准与技术研究院的daytime服务器,获取当前时间,创建Socket实例,会自动建立连接。连接成功后,可调用获取输入输出流和设置相应的属性,从而实现和服务器互通数据。
String url=
"time.nist.gov";
try( Socket socket= new Socket(url, 13)){
socket.setSoTimeout( 10000);
InputStream is=socket.getInputStream();
InputStreamReader isr= new InputStreamReader(is);
BufferedReader br= new BufferedReader(isr);
StringBuilder sb= new StringBuilder();
char[] bytes = new char[ 1204];
while ((br.read(bytes))!=- 1){
sb.append(bytes);
}
Log. e( "192",sb.toString());
br.close();
isr.close();
is.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
try( Socket socket= new Socket(url, 13)){
socket.setSoTimeout( 10000);
InputStream is=socket.getInputStream();
InputStreamReader isr= new InputStreamReader(is);
BufferedReader br= new BufferedReader(isr);
StringBuilder sb= new StringBuilder();
char[] bytes = new char[ 1204];
while ((br.read(bytes))!=- 1){
sb.append(bytes);
}
Log. e( "192",sb.toString());
br.close();
isr.close();
is.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
close()方法会同时关闭Socket的输入输出流,有时可能只希望关闭一半,半关闭Socket,即只关闭输入流或输出流,shutdownInput()和shutdownOutput()方法可以只关闭连接的一般。
向服务器写数据
那么,如何使用客户端Socket向服务器写入数据呢?先看代码,再分析
//获得原始输出流
OutputStream os=socket.getOutputStream();
//转为更方便的字符输出流
OutputStreamWriter writer=new OutputStreamWriter(os,"UTF-8");
//转为缓冲流
BufferedWriter bf=new BufferedWriter(writer);
//写入数据
bf.write("wwww.androidzhang.com\r\n");
//刷出所有数据
OutputStream os=socket.getOutputStream();
//转为更方便的字符输出流
OutputStreamWriter writer=new OutputStreamWriter(os,"UTF-8");
//转为缓冲流
BufferedWriter bf=new BufferedWriter(writer);
//写入数据
bf.write("wwww.androidzhang.com\r\n");
//刷出所有数据
writer.flush();
可知,通过Socket的getOutputStream()方法获取输出流,最后封装为BufferWriter,向服务器写入www.androidzhang.com字符串,这样就完成了客户端Socket向服务器写入数据。操作和普通的输出流没什么区别。
上面构造的Socket对象的同时,都会同时打开与远程主机的一个网络连接。那么有没有办法构造但不连接呢,调用Socket的无参构造函数即可。
try {
//构造Socket但不连接
Socket socket = new Socket();
//构建Socket地址
SocketAddress address = new InetSocketAddress("www.androidzhang.com", 80);
//连接远程主机
socket.connect(address);
//使用socket
} catch (IOException e) {
e.printStackTrace();
//构造Socket但不连接
Socket socket = new Socket();
//构建Socket地址
SocketAddress address = new InetSocketAddress("www.androidzhang.com", 80);
//连接远程主机
socket.connect(address);
//使用socket
} catch (IOException e) {
e.printStackTrace();
}
也就是将操作进行分解,从而控制Socket的步骤。这里的SocketAddress表示一个连接端点,主要用途为暂时的Socket连接信息提供方便的存储。通过Socket的getRemoteSocketAddress()方法和getLocalSocketAddress()方法可以分别获取远程的
SocketAddress和本地
SocketAddress,从而进一步获得端口,主机名,地址等信息。
Socket相关方法
获取远程地址:
getIntelAddress()
获取远程端口:
getPort()
获取本地地址:
getLocalAddress()
获取本地端口:
getLocalPort()
判断Socket是否打开:
boolean connected = socket.isConnected && ! socket.isClosed();