我们软件工作者,着重编写的是应用层的代码,但是发送这个数据,我们就需要将应用层传输到传输层,也就意味着我们需要调用应用层的API,统称为 Socket API。
套接字的分类:
什么是全双工和半双工?
全双工:一条路径,双向通信
半双工:一条路径,单向通信
网络传输数据的基本单位:报(Datagram)、包(Packet)、段(Segment)、帧(Frame)
Socket 对象,相当于系统中Socket文件,这个文件并非对应到硬盘上的某个数据存储区域,而是对应到网卡这个硬件设备
java中使用UDP协议通信,主要基于 DatagramSocket 类来创建数据报套接字,并使用DatagramPacket 作为发送或接收的UDP数据报 。DatagramSocket 是UDP Socket,用于发送和接收UDP数据报。
DatagramSocket API:
DatagramPacket API:(DatagramPacket是UDP Socket发送和接收的数据报)
InetSocketAddress API:
UDP服务器:
注意:
public class UdpServer {
//服务器socket要绑定固定的端口
private static final int PORT = 8888;
public static void main(String[] args) throws IOException {
// 1.创建服务端DatagramSocket,指定端口,可以发送及接收UDP数据报
DatagramSocket socket = new DatagramSocket(PORT);
//不停的接收客户端udp数据报
while (true){
// 2.创建数据报,用于接收客户端发送的数据
byte[] bytes = new byte[1024];//1m=1024kb, 1kb=1024byte, UDP最多64k(包含UDP首部8byte)
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
System.out.println("---------------------------------------------------");
System.out.println("等待接收UDP数据报...");
// 3.等待接收客户端发送的UDP数据报,该方法在接收到数据报之前会一直阻塞,接收到数据报以后,DatagramPacket对象,包含数据(bytes)和客户端ip、端口号
socket.receive(packet);
System.out.printf("客户端IP:%s%n",
packet.getAddress().getHostAddress());
System.out.printf("客户端端口号:%s%n", packet.getPort());
System.out.printf("客户端发送的原生数据为:%s%n",
Arrays.toString(packet.getData()));
System.out.printf("客户端发送的文本数据为:%s%n", new
String(packet.getData()));
}
}
}
一旦服务器一起动,调用start方法,就会立即执行到,receive这里,但是如果此时还有没有客户端发来的数据,receive就会阻塞等待,一直持续到有数据发过来。
细节:网卡这里收到数据,就会进行分用,解析UDP这一层 看到端口号,然后将数据放入接收缓冲区,然后将数据到了参数中的DatagramSocket 对象中
UDP客户端:
public class UdpClient {
// 服务端socket地址,包含域名或IP,及端口号
private static final SocketAddress ADDRESS = new
InetSocketAddress("localhost", 8888);
public static void main(String[] args) throws IOException {
// 4.创建客户端DatagramSocket,开启随机端口就行,可以发送及接收UDP数据报
DatagramSocket socket = new DatagramSocket();
// 5-1.准备要发送的数据
byte[] bytes = "hello world!".getBytes();
// 5-2.组装要发送的UDP数据报,包含数据,及发送的服务端信息(服务器IP+端口号)
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, ADDRESS);
// 6.发送UDP数据报
socket.send(packet);
}
}
ServerSocket API:
ServerSocket 是创建TCP服务端Socket的API。
Socket API:
Socket 是客户端 Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。
不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。
TCP发送数据时,需要先建立连接,而什么时候关闭连接就决定是短连接还是长连接。
短连接:每次接收数据并返回响应后,都关闭连接。也就是说,短连接只能一次收发。
长连接:不关闭连接,一直保持连接状态,双方不停的收发数据,即是长连接。也就是说,长连接可以
多次收发数据
长连接和短连接的区别:
public class TcpEchoServer {
//serverSocket 就是外场拉客的小哥(类似集合),只有一个
//clientSocket 内场服务的人(),会给每个客服分配一个
private ServerSocket serverSocket=null;
//1
public TcpEchoServer(int port) throws IOException {
serverSocket=new ServerSocket(port);
}
public void start() throws IOException {
ExecutorService executorService= Executors.newCachedThreadPool();
System.out.println("服务器启动");
while (true){
Socket clientSocket=serverSocket.accept();
//如果直接调用,该方法会影响这个循环的二次执行.导致accept不及时了
//创建新的线程,用新的线程来调用processConnetion
//每次来一个新的客户端都搞一个新的线程即可
/* Thread t=new Thread(()->{
try {
processConnection(clientSocket);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
t.start();*/
//创建一个线程池,从池子中拿取线程
executorService.submit(new Runnable() {
@Override
public void run() {
try {
processConnection(clientSocket);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
}
}
//通过这个方法处理一个链接
//读取请求
//根据请求计算响应
//把响应返回给客户端
private void processConnection(Socket clientSocket) throws IOException {
System.out.printf("[%s:%d] 客户端上线!\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
try(InputStream inputStream=clientSocket.getInputStream();
OutputStream outputStream=clientSocket.getOutputStream()) {
//没有这两个也可以,但是代价就是得一个字节一个字节的处理,找到那个是结束符
//将字节流包装成了字符流
Scanner scanner=new Scanner(inputStream);
PrintWriter printWriter=new PrintWriter(outputStream);
while (true){
//3
//读取请求
if (!scanner.hasNext()){
//读取的流到了结尾了
System.out.printf("[%s:%d] 客户端下线",clientSocket.getInetAddress().toString(),clientSocket.getPort());
break;
}
//直接使用scanner读取一段字符串
String request=scanner.next();//往后读,一直读到空白符,空格,换行,翻页符....都算空白符
//5
//根据请求计算响应
String response=process(request);
//把响应返回给客户端
printWriter.println(response);
//刷新缓冲区
printWriter.flush();
System.out.printf("[%s:%d] req:%s; resp:%s\n",clientSocket.getInetAddress().toString()
,clientSocket.getPort(),request,response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
clientSocket.close();
}
}
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer tcpEchoServer=new TcpEchoServer(9090);
tcpEchoServer.start();
}
}
硬件的读写速度:
读写硬盘和网卡口可以视为 IO 操作。
因为网卡读写速度慢,如果平凡的写入,读出对于效率太慢了。为了提高IO操作的效率,此时就需要引入一个内存构成的缓冲区。等缓冲区达到一定数量,就统一写入网卡中。
缓存(cache)!=缓冲区(buffer)