Socket(一)
2.0什么是套接字
Sokcet(套接字)是一种抽象层,应用程序通过它来发送和接收数据,就像应用程序打开一个文件句柄,将数据读写到稳定的存储器上一样. 一个socket允许应用程序添加到网络中,并与处于同一个网络中的其他应用程序进行通信。一台计算机上的应用程序向socket写入的信息能够被另一台计算机上的另一个应用程序读取,反之亦然
Applications:应用程序 TCP sockets:TCP套接字 TCP ports:TCP端口
Soket References:套接字引用 UDP sockets:UDP套接字
现在TCP/OP协议族中的主要socket类型为流套接字(Sockets sockets)和数据报套接字(datagram sockets)
流套接字将TCP作为其端对端协议(底层使用IP协议),提供了一个可信赖的字节流服务。一个TCP/IP流套接字代表了TCP连接的一端。数据报套接字使用UDP协议(底层同样使用IP协议),提供了一个"尽力而为"(best-effort)的数据报服务,应用程序可以通过它发送最长65500字节的个人信息。
一个TCP/IP套接字由一个互联网地址,一个端对端协议(TCP或UDP协议)以及一个端口号唯一确定
2.1 套接字地址
一个客户端要发起一次通信,首先必须知道运行服务器端程序的主机的IP地址, 然后由网络的基础结构利用目标地址(destination address),将客户端发送的信息传递到正确的主机.
在Java中,地址可以由一个字符串来定义 可以是数字型的地址(IPv4 IPv6) 也可以是主机名(主机名必须被解析成数字型地址才能进行通信)
InetAddress类代表了一个网络目标地址,包括主机名和数字类型的地址信息。该类有两个子类,Inet4Address和Inet6Address,分别对应了目前IP地址的两个版本。InetAddress实例是不可变的,一旦创建,每个实例就始终指向同一个地址
为了获得本地主机地址, 利用了NetworkInterface类的功能
InetAddressAPI:
字符串表示
String toString() 返回如” hostname.example.com/192.0.2.127”
或” never.example.net/2000::620:1a30:95b2”
String getHostAddress() 返回单一数字类型
String getHostName(). 返回主机名
String getCanonicalHostName()
2.2.TCP套接字(建立连接,形成传输数据的通道;在连接中进行大的数据量传输;通过三次握手完成连接,是可靠协议必须建立连接,效率会稍低,类似打电话)
三次握手:
客户端向服务器发送一个SYN J
(当客户端调用connect时,触发了连接请求,向服务器发送了SYN J包,这时connect进入阻塞状态;)
服务器向客户端响应一个SYN K,并对SYN J进行确认ACK J+1
(服务器监听到连接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞状态)
客户端再想服务器发一个确认ACK K+1
(客户端收到服务器的SYN K ,ACK J+1之后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立。)
四次挥手:
1.某个应用进程首先调用close主动关闭连接,这时TCP发送一个FIN M;
2.另一端接收到FIN M之后,执行被动关闭,对这个FIN进行确认。它的接收也作为文件结束符传递给应用进程,因为FIN的接收意味着应用进程在相应的连接上再也接收不到额外数据;
3.一段时间之后,接收到文件结束符的应用进程调用close关闭它的socket。这导致它的TCP也发送一个FIN N;
4.接收到这个FIN的源发送端TCP对它进行确认。
Java为TCP协议提供了两个类:Socket类和ServerSocket类.
一个Socket实例代表了TCP连接的一端,一个TCP连接是一条抽象的双向通道,两端分别由IP地址和端口号确定. 在开始通信之前,要建立一个TCP连接,这需要先由客户端TCP向服务器端TCP发送连接请求。ServerSocket实例则监听TCP连接请求,并为每个请求创建新的Socket实例。也就是说,服务器端要同时处理ServerSocket实例和Socket实例,而客户端只需要使用Socket实例
2.2.1TCP客户端
1.创建一个Socket实例
2. 通过套接字的输入输出流(I/O streams)进行通信:一个Socket连接实例包括一个InputStream和一个OutputStream,它们的用法同于其他Java输入输出流
3. 使用Socket类的close()方法关闭连接。
2.2.2TCP服务器端
步骤
- 创建一个ServerSocket实例(唯一目的是为新的TCP连接请求提供一个新的已连接的Socket实例.当服务器端已经准备好处理客户端请求时,就调用accept()方法.该方法将阻塞等待,知道有向ServerSocket实例指定端口的新的连接请求到来)并指定本地端口,此套接字的功能是监听该指定端口收到的连接
- 重复执行
- 调用ServerSocket的accept()方法(阻塞)以获取下一个客户端连接。基于新建立的客户端连接,创建一个Socket实例,并由accept()方法返回。
- 使用所返回的Socket实例的InputStream和OutputStream与客户端进行通信。
- 通信完成后,使用Socket类的close()方法关闭该客户端套接字连接。
2.2.3输入输出流
TCP套接字的基本输入输出形式是流(Stream)抽象. 流只是一个简单有序的字节序列。Java的输入流(input streams)支持读取字节,而输出流(output streams)则支持写出字节
当我们向Socket的输出流写了数据后,这些字节最终将能从连接另一端的Socket的输入流中读取。
这些write()方法分别向输出流传输一个字节、整个字节数组,或从一个数组中offset所指定偏移量开始,输出长度为length的字节。输出一个字节的write()方法只将其整型参数的低8位输出.如果在一个TCP套接字关联的输出流上进行这些操作,当大量的数据已发送,而连接的另一端所关联的输入流最近没有调用read()方法时,这些方法可能会阻塞。如果不作特殊处理,这可能会产生一些不想得到的后果
第二种形式的read方法从输入流中获取长度为data.length的字节序列,并将其存入data数组中,该方法的返回值是实际传输的字节数;第三种形式与第二种的功能相似,但在把数据存入data数组时,将从offset所指定的偏移量开始存放,而且最多只传输长度为length的字节序列. 当没有数据可读,而又没有检测到流结束标记时,所有的read()方法都将阻塞等待,直到至少有一个字节可读。在没有数据可读,同时又检测到流结束标记时,以上所有方法都将返回-1。
UDP套接字(面向无连接的通讯协议,传输小数据时,不管对端服务是否启动,直接发送,类似发邮件)
UDP协议只实现两个功能
- 在IP协议的基础上添加另一层地址(端口)
- 对数据传输过程中可能产生的数据错误进行了检测,并抛弃已经损坏的数据
UDP与TCP不同点在于
1.对信息边界的处理方式不同
UDP套接字将保留边界信息。这个特性使应用程序在接受信息时,从某些方面来说比使用TCP套接字更简单
- 所提供的端到端传输服务是尽力而为(best-effort)(高效) 即UDP套接字将尽可能地传送信息,但并不保证信息一定能成功到达目的地址,而且信息到达的顺序与其发送顺序不一定一致
Java通过DatagramPacket类和DatagramSocket类来使用UDP套接字.
客户端和服务器端都使用DatagramSockets来发送数据,使用DatagramPackets来接收数据