Socket是什么?
想知道Socket是什么就先得了解一下什么是网络编程
网络编程,通过代码来控制两个主机的进程之间能够进行数据交互。
操作系统就把网络编程的一些相关操作,封装起来了,提供了一组API供程序员使用。操作系统提供的功能,访问网络核心的硬件设备,网卡。网卡也是归操作系统来管理的
操作系统提供的socket api 是C语言风格的接口,在Java中是不能直接使用的。JDK其实也针对C语言这里的 socket API 进行了封装,在标准库中有一组类,这组类就能够让我们完成网络编程,这组类本质上仍然是调用的操作系统提供的socketAPI
操作系统,提供的 socket API主要有两类(实际上不止两类),它属于传输层
TCP/UDP
TCP和UDP这里只是简单说一下它们的特点,便于理解Socket编程
TCP
有连接
可靠传输
面向字节流
全双工
UDP
无连接
不可靠传输
面向数据报
全双工
有连接:类似于微信视频,需要接通才能说话
无连接:类似于发微信消息,直接发就好了
可靠传输:发送方能知道对方是否收到消息
不可靠传输:发送方不知道是不是收到了消息
注意:可靠性 != 安全性
面向字节流:
假设发送数据为1000个字节,可以一次性发10个字节重复发100次,也可以一次发100个字节,重复发送10次,可以非常灵活的完成这里的发送,接收也是同理
TCP的文件读写都是面向字节流的
面向数据报:
以一个一个的数据报为基本单位(每个数据报多大,不同的协议里面是有不同的约定的)
发送的时候,一次至少发送一个数据报,如果尝试发送一个半,实际可能只能发出去一个
接收的时候,一次至少接收一个数据,如果尝试接收半个,剩下半个就没了
全双工:双向通信,A和B可以同时向对方发送数据
半双工:单向通信,要么A给B发,要么B给A发,不能同时发
就类似于两根水管和一根水管的区别
套接字
一个服务器的核心流程
- 读取请求并解析
- 根据请求计算响应
- 把响应写回客户端
一个客户端的核心流程
- 根据用户输入,构造请求
- 发送请求给服务器
- 读取服务器的响应
- 解析响应并显示
UDP套接字
DatagramSocket API
DatagramSocket API 是UDP Socket,用于发送和接收UDP数据报
DatagramSocket构造方法
DatagramSocket 方法
构造UDP发送的数据报时,需要传入 SocketAddress ,该对象可以使用 InetSocketAddress 来创建。
InetSocketAddress API
InetSocketAddress ( SocketAddress 的子类 )构造方法
基于UDP实现回显服务器
回显服务器就是客户端发送什么请求服务器就返回什么请求,UDP是不需要建立连接的
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UdpEchoServer {
private DatagramSocket socket;
public UdpEchoServer(int port) throws SocketException {
this.socket = new DatagramSocket(port);
}
private void start() throws IOException {
System.out.println("服务器启动成功");
while (true) {
// 1.读取请求并解析
DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
this.socket.receive(requestPacket);
String request = new String(requestPacket.getData());
// 2.根据请求计算响应
String response = process(request);
// 3.把响应返回给客户端
DatagramPacket responsePacket = new DatagramPacket(request.getBytes(),0,request.getBytes().length,
requestPacket.getSocketAddress());
this.socket.send(responsePacket);
// 4.打印日志
String log = String.format("[%s:%d] request: %s response: %s",requestPacket.getAddress().toString(),requestPacket.getPort(),
request,response);
System.out.println(log);
}
}
/**
* 这是一个回显服务器
* @param request
*/
private String process(String request) {
//发送什么请求就返回什么响应
return request;
}
public static void main(String[] args) throws IOException {
UdpEchoServer udpEchoServer = new UdpEchoServer(9090);
udpEchoServer.start();
}
}
客户端代码
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.util.Scanner;
public class UdpEchoClient {
private DatagramSocket socket;
private int serverPort;
private String serverIp;
private InetSocketAddress inetSocketAddress;
public UdpEchoClient(int port,String ip) throws SocketException {
//客户端的IP和端口号由操作系统自动分配
this.socket = new DatagramSocket();
this.serverPort = port;
this.serverIp = ip;
this.inetSocketAddress = new InetSocketAddress(this.serverIp, this.serverPort);
}
public void start() throws IOException {
System.out.println("客户端启动成功");
Scanner sc = new Scanner(System.in);
while (true) {
// 1.从键盘输入请求并构造
System.out.print("-> ");
String request = sc.nextLine();
if ("exit".equals(request)) {
String log = String.format("客户端退出[%s:%d]",this.serverIp,this.socket.getPort());
System.out.println(log);
break;
}
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,this.inetSocketAddress);
// 2.把请求发送给服务器
this.socket.send(requestPacket);
// 3.从服务器获取响应
DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
this.socket.receive(responsePacket);
String response = new String(responsePacket.getData());
// 4.打印日志
String log = String.format("[%s:%d] request: %s response: %s",this.serverIp,responsePacket.getPort(),request,response);
System.out.println(log);
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient udpEchoClient = new UdpEchoClient(9090,"127.0.0.1");
udpEchoClient.start();
}
}
TCP套接字
TCP的套接字API和UDP是完全 不同的
ServerSocket API
ServerSocket 是创建TCP服务端Soket的API
ServerSocket构造方法
ServerSocket方法
Socket API
Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,放回的服务端Socket
不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来和对方收发数据的
Socket 构造方法
Socket方法
TCP中的长短连接
TCP发送数据时,需要先建立连接,什么时候关闭连接就决定是短连接还是长连接
短连接: 每次收到数据并返回响应后,都关闭
长连接: 不关闭连接,一直保持连接状态,双方不停的收发数据,就是长连接,也就是说,长连接可以多次收发数据
对比长短 连接,两者区别如下
建立连接、关闭连接的耗时
短连接每次请求、响应都需要建立连接,关闭连接。而长连接至需要第一次连接,之后的请求、响应都可以直接传输。相对来说建立连接,关闭连接也是耗时的,长连接效率更高
主动发送请求不同
短连接一般是客户端主动向服务器发送请求,而长连接可以是客户端主动发送请求,也可以是服务端主动发
两者的使用场景不同
短连接适用于客户端请求频率不高的场景,入浏览网页等。长连接适用于客户端与服务端通信频繁的场景,如聊天室、实时游戏等
基于TCP实现回显服务器
服务器代码
创建ServerSocket 对象指定端口号
TCP套接字双方要先建立连接
使用Thread处理多个客户端的情况
每次一个Socket使用完后一定要关闭
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class TcpEchoServer {
private ServerSocket listenSocket;
public TcpEchoServer(int port) throws IOException {
this.listenSocket = new ServerSocket(port);
}
private void start() throws IOException {
System.out.println("服务器启动成功");
while (true) {
// TCP套接字先要建立连接
Socket socket = this.listenSocket.accept();
//用Thread来处理多个客户端的情况
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
connectionProcess(socket);
} catch (IOException e) {
e.printStackTrace();
}
}
});
thread.start();
}
}
private void connectionProcess(Socket socket) throws IOException {
try (InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream()) {
Scanner sc = new Scanner(inputStream);
PrintWriter printWriter = new PrintWriter(outputStream);
String log = String.format("[%s:%d] 客户端上线",socket.getInetAddress(),socket.getPort());
System.out.println(log);
while (true) {
// 1.读取请求并解析
if (!sc.hasNext()) {
log = String.format("[%s:%d] 客户端下线",socket.getInetAddress(),socket.getPort());
System.out.println(log);
break;
}
String request = sc.nextLine();
// 2.根据请求计算响应
String response = process(request);
// 3.把响应发给客户端
printWriter.println(response);
//加上flush刷新缓冲区
printWriter.flush();
// 4.打印日志
log = String.format("[%s:%d] request: %s response: %s",socket.getInetAddress(),socket.getPort(),
request,response);
System.out.println(log);
}
sc.close();
printWriter.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
//使用后关闭,防止内存泄露
socket.close();
}
}
/**
* 回显服务器直接返回请求
* @param request
* @return
*/
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer server = new TcpEchoServer(9090);
server.start();
}
}
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class TcpEchoClient {
private Socket clientSocket;
private String serverIp;
private int serverPort;
public TcpEchoClient(int serverPort,String serverIp) throws IOException {
this.clientSocket = new Socket(serverIp,serverPort);
this.serverPort = serverPort;
this.serverIp = serverIp;
}
public void start() {
System.out.println("客户端启动成功");
Scanner sc = new Scanner(System.in);
try (InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()) {
Scanner responseSc = new Scanner(inputStream);
PrintWriter printWriter = new PrintWriter(outputStream);
while (true) {
// 1.从键盘输入请求
System.out.print("-> ");
String request = sc.nextLine();
if ("exit".equals(request)) {
break;
}
// 2.发送请求给服务器
printWriter.println(request);
//加上flush刷新缓冲区
printWriter.flush();
// 3.从服务器获取响应
String response = responseSc.nextLine();
// 4.打印日志
String log = String.format("[%s:%d] request: %s response: %s",this.serverIp,this.serverPort
,request,response);
System.out.println(log);
}
responseSc.close();
printWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient client = new TcpEchoClient(9090,"127.0.0.1");
client.start();
}
}