Socket编程学习-一
Socket 编程学习篇
一、概念
TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。在简化的计算机网络OSI模型中(四层模型从下到上分别是:物理层/数据链路层、网络层/IP层、传输层TCP/UDP、应用层HTTP/FTP等),它完成第四层传输层所指定的功能,用户数据报协议(UDP)是同一层内 另一个重要的传输协议。在因特网协议族(Internet protocol suite)中,TCP层是位于IP层之上,应用层之下的中间层。不同主机的应用层之间经常需要可靠的、像管道一样的连接,但是IP层不提供这样的流机制,而是提供不可靠的包交换。 应用层向TCP层发送用于网间传输的、用8位字节表示的数据流,然后TCP把数据流分区成适当长度的报文段(通常受该计算机连接的网络的数据链路层的最大传输单元( MTU)的限制)。之后TCP把结果包传给IP层,由它来通过网络将包传送给接收端实体的TCP层。TCP为了保证不发生丢包,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序 接收。然后接收端实体对已成功收到的包发回一个相应的确认(ACK);如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据包就被假设为已丢失将会被进行重传。TCP用一个校验和函数来检验数据是否有错误;在发送和接收时都要计算校验和。
JAVA Socket
所谓socket 通常也称作”套接字“,用于描述IP地址和端口,是一个通信链的句柄。应用程序通常通过”套接字”向网络发出请求或者应答网络请求。
Socket和ServerSocket类库位于java.net包中。ServerSocket用于服务器端,Socket是建立网络连接时使用的。在连接成功时,应用程序两端都会产生一个Socket实例,操作这个实例,完成所需的会话。对于一个网络连接来说,套接字是平等的,并没有差别,不因为在服务器端或在客户端而产生不同级别。不管是Socket还是ServerSocket它们的工作都是通过SocketImpl类及其子类完成的。
重要的Socket API
java.net.Socket继承于java.lang.Object,有八个构造器,其方法并不多,下面介绍使用最频繁的三个方法,其它方法大家可以见JDK-1.3文档。
Accept方法用于产生”阻塞”,直到接受到一个连接,并且返回一个客户端的Socket对象实例。”阻塞”是一个术语,它使程序运行暂时”停留”在这个地方,直到一个会话产生,然后程序继续;通常”阻塞”是由循环产生的。 getInputStream方法获得网络连接输入,同时返回一个InputStream对象 实例。 getOutputStream方法连接的另一端将得到输入,同时返回一个OutputStream对象实例。 注意:其中getInputStream和getOutputStream方法均会产生一个IOException,它必须被捕获,因为它们返回的流对象,通常都会被另一个流对象使用。
TCP编程
socket通信时,都是必须先启动服务端,再启动客户端才能建立连接实现通信的
客户端实现
package socket;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
/*
参考文章:https://www.codenong.com/cs106175296/
*/
/**
* @author 030
* @date 15:34 2021/11/8
* @description TCP通信的客户端:向服务器发送连接请求,给服务器发送数据,读取服务器回写的数据
* 表示客户端的类:
* java.net.Socket:此类实现客户端套接字(也可以叫做“套接字”)。套接字是两台机器之间通信的端点。
* 套接字:包含了IP地址和端口号的网络单位
* 构造方法:
* Socket(String host, int port) 创建一个流套接字并将其连接到指定主机上的指定端口号
* 参数:
* String host:服务器主机的名称/服务器的ip地址
* int port:服务器的端口号
* 成员方法:
* OutputStream getOutputStream() 返回此套接字的输出流
* InputStream getInputStream() 返回此套接字的输入流
* void close() 关闭此套接字
* <p>
* 实现步骤:
* 1. 创建一个客户端对象 Socket,构造方法绑定服务器的IP地址和端口号
* 2. 使用 Socket 对象中的方法 getOutputStream() 获取网络字节输出流 OutputStream对象
* 3. 使用网络字节输出流 OutputStream 对象中的方法 write,给服务器发送数据
* 4. 使用 Socket 对象中的方法 getInputStream() 获取网络字节输入流 InputStream对象
* 5. 使用网络字节输入流 InputStream对象中的方法 read,读取服务器返回的数据。
* 6. 释放资源(Socket)
* 注意:
* 1. 客户端和服务器端进行交互,必须使用Socket中提供的网络流,不能使用自己创建的流对象
* 2. 当我们创建客户端对象Socket的时候,就会去请求服务器,并与服务器经过3次握手建立链接通路
* 这时如果服务器没有启动,那么就会抛出异常
* 如果服务器已经启动,那么就可以进行交互了
*/
public class TCPClient {
public static void main(String[] args) throws IOException {
// 1. 创建一个客户端对象Socket,构造方法绑定服务器的IP地址和端口号
Socket socket = new Socket("127.0.0.1", 8888);
// 2. 使用Socket对象中的方法getOutputStream获取网络字节输出流OutputStream对象
OutputStream os = socket.getOutputStream();
// 3. 使用网络字节输出流OutputStream对象中的方法write,给服务器发送数据
String message = "你好服务器,我是客户端";
os.write(message.getBytes(StandardCharsets.UTF_8));
/*********解决bug:服务端读取不到 len = -1 标识,会一直处在死循环等待状态ing**********/
//通过shutdownOutput高速服务器已经发送完数据,后续只能接受数据
socket.shutdownOutput();
// 4. 使用Socket对象中的方法getInputStream获取网络字节输入流InputStream对象
InputStream is = socket.getInputStream();
int len; // 基本数据类型在方法内部(局部变量)时,可以省略初始化,会默认初始化的;但是作为成员变量时则不可以
byte[] bytes = new byte[1024];
// 用于记录 服务端回写 的数据
StringBuilder sb = new StringBuilder(); // 注意:多线程时要使用 StringBuffer
while ((len = is.read(bytes)) != -1) {
// 5. 使用网络字节输入流InputStream对象中的方法read,读取服务器返回的数据。
// 注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
sb.append(new String(bytes, 0, len, StandardCharsets.UTF_8));
}
// 打印输出一下服务端回写的数据
System.out.println("get message from server: " + sb);
// 6. 释放资源(Socket)
is.close();
os.close();
socket.close();
}
}