目录
1.Socket套接字
Socket套接字,是由操作系统提供用于网络通讯的API,是基于TCP/IP协议实现的,主要作用于传输层,因此程序员只需制定好应用层的协议,再调用此API,即可实现网络通讯。
socket套接字分为三类
1.1数据报套接字
该套接字使用的传输层协议是UDP,即
1.无连接
2.不可靠传输
3.面向数据报
4.传送数据限制大小
1.2流套接字
该套接字使用的传输层协议是TCP,即
1.有连接
2.可靠传输
3.面向字节流
4.传输数据不限大小
1.3原始套接字
原始套接字⽤于⾃定义传输层协议,⽤于读写内核没有处理的IP协议数据。
2.UDP数据报套接字编程
2.1API介绍
DatagramSocket是UDP的Socket,用于发送和接收数据报,通过不同的构造方法,可以指定作为客户端还是服务端,客户端一般是不需要指定端口号,由操作系统分配,而服务端的端口号则需程序员指定,有了固定的端口号,方便为客户端服务。
方法名 | 方法说明 |
public DatagramSocket() |
创建一个UDP套接字的Socket,绑定到本机随机端口,由操作系统决定,一般用作客户端 |
public DatagramSocket(int port) |
创建一个UDP套接字的Socket,绑定到本机指定端口,一般用作服务端 |
public void send(DatagramPacket p) |
此套接字发送数据报 |
public synchronized void receive(DatagramPacket p) |
此套接字等待接收数据报,没有接收到会阻塞等待 |
public void close() |
关闭套接字 |
DatagramPacket是用于UDP中封装数据报的。
方法名 | 方法说明 |
public DatagramPacket(byte buf[], int length) |
创建一个DatagramPacket用来接收数据报,数据保存在字节数组中,大小为length。 |
public DatagramPacket(byte buf[], int offset, int length, SocketAddress address) |
创建一个DatagramPacket用来发送数据报,发送的数据在buf数组中,从offset位置开始,发送length个长度的数据到指定的主机中。 |
public synchronized InetAddress getAddress() |
从接收的数据报中,获取发送方的主机IP地址,或者从发送的数据报中,获取接收端主机的IP地址 |
public synchronized int getPort() |
从接收的数据报中,获取发送方的主机端口号,或者从发送的数据报中,获取接收端主机的端口号 |
public synchronized byte[] getData() |
获取数据报中数据 |
构造UDP发送的数据报时,需要传⼊ SocketAddress ,该对象可以使⽤ InetSocketAddress
来创建。
构造方法 | 说明 |
InetSocketAddress(InetAddress addr,int por) | 创建一个Socket地址,主机IP和端口号 |
2.2实现简易回显服务器
实现一个简易回显服务器,客户端输入什么,服务端回应什么。
package UDP;
import java.io.IOException;
import java.net.BindException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.nio.charset.StandardCharsets;
/**
* 基于UDP的回显服务器
*/
public class UDPEchoServer {
//创建一个DatagramSocket用来发送和接收数据包
private DatagramSocket socket;
//构造函数中指定服务器的端口号
public UDPEchoServer(int port) throws SocketException {
//对端口号进行校验
if (port < 1024 || port > 65535) {
throw new BindException("端口号必须在1025 ~ 65535之间");
}
//实例化后服务器已启动
this.socket = new DatagramSocket(port);
}
/**
* 处理用户请求
* @throws IOException
*/
public void start() throws IOException {
//循环处理提供服务
System.out.println("服务器已经启动");
while (true) {
//创建一个DatagramPacket接收请求
DatagramPacket requestPacket = new DatagramPacket(new byte[1024], 1024);
//等待接收用户发来的请求
socket.receive(requestPacket);
//解析用户发来的请求
String request = new String(requestPacket.getData(), 0,requestPacket.getLength(),"UTF-8");
//处理用户的请求,并做出响应
String response = process(request);
//利用DatagramPacket封装响应
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(StandardCharsets.UTF_8), 0,
response.getBytes().length, requestPacket.getSocketAddress());
//发送响应
socket.send(responsePacket);
//打印日志
System.out.printf("[%s,%d] repuest: %s,response: %s\n", responsePacket.getAddress().toString(),
responsePacket.getPort(), request, response);
}
}
/**
* 计算响应
* @param request
* @return
*/
protected String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
UDPEchoServer udpEchoServer = new UDPEchoServer(8888);
udpEchoServer.start();
}
}
package UDP;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
/**
* 客户端
*/
public class UDPEchoClient {
//创建一个DatagramSocket来收发数据报
private DatagramSocket socket;
//声明服务器的IP和端口号
private String severIP;
private int severPort;
public UDPEchoClient(String severIP,int severPort) throws SocketException {
//让操作系统为客户端指定一个随机端口号,并指定需要连接的客户端的IP和端口号
socket = new DatagramSocket();
this.severIP = severIP;
this.severPort = severPort;
}
public void start() throws IOException {
System.out.println("客户端已启动");
while (true) {
//1.接收用户发来的请求
System.out.println("请输入请求");
Scanner scanner = new Scanner(System.in);
String request = scanner.nextLine();
//校验请求
if(request.isEmpty() || request == null){
System.out.println("请求为空,请重新输入");
continue;
}
//2.使用DatagramPacket封装请求
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(StandardCharsets.UTF_8),0
,request.getBytes().length,new InetSocketAddress(severIP,severPort));
//3.发送请求
socket.send(requestPacket);
//4.使用DatagramPacket等待并接收服务端发来的响应
DatagramPacket responsePacket = new DatagramPacket(new byte[1024],1024);
socket.receive(responsePacket);
//5.解析并处理响应
String response = new String(responsePacket.getData(),0,responsePacket.getLength(),"UTF-8");
//6.打印结果
System.out.printf("request: %s, response: %s\n",request,response);
}
}
public static void main(String[] args) throws IOException {
UDPEchoClient udpEchoClient = new UDPEchoClient("127.0.0.1",8888);
udpEchoClient.start();
}
}
客户端发送请求:
服务器结果显示
3.TCP流套接字编程
3.1API介绍
SeverSocket是创建服务端socket的API
方法名 | 方法说明 |
public ServerSocket(int port) throws IOException |
创建服务端Socket,并为服务端指定端口号port |
public Socket accept() throws IOException |
服务端开始监听,有客户端连接后,返回一个客户端Socket,其中包含着双方通信的信息流。 |
public void close() throws IOException |
关闭套接字 |
Socket是客户端Socket
方法名 | 方法说明 |
public Socket(String host, int port) |
创建客户端Socket,并且指定服务端IP和端口号 |
public InetAddress getInetAddress() |
返回此套接字所连接的地址 |
public InputStream getInputStream() throws IOException |
获取此套接字的输入流 |
public OutputStream getOutputStream() throws IOException |
获取此套接字的输出流 |
3.2实现简易回显服务器
实现一个简易回显服务器。
下面展示一个无法连接多个客户端的服务器实现,由于是单线程,只能同时连接一个客户端。
package TCP;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
/**
* 基于TCP实现的简易回显服务器
*/
public class TCPEchoSever {
//创建一个TCP的Socket
protected ServerSocket socket;
/**
*构造方法
* @param port 服务端端口号
*/
public TCPEchoSever(int port) throws IOException {
//校验端口号
if(port <1035 || port > 65535){
throw new BindException("端口号只能在1035 ~ 65535之间");
}
//初始化socket,启动服务器
socket = new ServerSocket(port);
}
/**
* 监听客户端
* @throws IOException
*/
public void start() throws IOException {
//循环监听处理客户端的请求
while (true) {
//开始监听
Socket clientSocket = socket.accept();
//处理请求
processConnection(clientSocket);
}
}
/**
* 连接客户端处理业务
* @param clientSocket 客户端socket
*/
protected void processConnection(Socket clientSocket) {
//校验客户端socket对象
if(clientSocket == null){
throw new RuntimeException("clientSocket为空,无法操作");
}
System.out.printf("[%s,%d] 客户端已经上线\n",clientSocket.getInetAddress(),
clientSocket.getPort());
//1.创建输入输出流来获取请求和计算响应
try(InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()) {
//2.接收用户请求,利用Scanner简化读取
Scanner scanner = new Scanner(inputStream);
//按照每次读取一行的协议
while(scanner.hasNextLine()){
//从网卡中获取请求
String request = scanner.nextLine();
//3.解析请求并计算响应
String response = process(request);
//4.将响应利用PrintWriter简化写入网卡中
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(response);
//将内核缓存区内容强制刷新进网卡
printWriter.flush();
//5.打印日志
System.out.printf("[%s,%d],request: %s, response: %s\n",
clientSocket.getInetAddress(),clientSocket.getPort(),request,response);
}
System.out.printf("[%s:%d],客户端已经下线\n",clientSocket.getInetAddress(),clientSocket.getPort());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 计算响应
* @param request 客户请求
* @return 响应
*/
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TCPEchoSever tcpEchoSever = new TCPEchoSever(9999);
tcpEchoSever.start();
}
}
思考过后,可以用多个线程处理多个客户端,并且采用线程池的方法,防止同一时间内客户端请求数量过大,服务器配置不够。
package TCP;
import java.io.IOException;
import java.net.Socket;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class TCPThreadPoolSever extends TCPEchoSever{
/**
* 构造方法
*
* @param port 服务端端口号
*/
public TCPThreadPoolSever(int port) throws IOException {
super(port);
}
/**
* 利用线程池,根据主机核显数配置
* @throws IOException
*/
@Override
public void start() throws IOException {
//利用线程池,防止客户端数量过大冲破服务器
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,5,
1,TimeUnit.SECONDS,new LinkedBlockingQueue<>(1));
while (true) {
//开始监听
Socket clientSocket = socket.accept();
threadPoolExecutor.submit(()->{
processConnection(clientSocket);
});
}
}
public static void main(String[] args) throws IOException {
TCPThreadPoolSever tcpThreadPoolSever = new TCPThreadPoolSever(9999);
tcpThreadPoolSever.start();
}
}
package TCP;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class TCPEchoClient {
//创建一个客户端socket
private Socket socket;
//描述服务器的IP和端口号
private String severIp;
private int severPort;
/**
* 构造方法
* @param severIp 服务器IP
* @param severPort 服务器端口号
*/
public TCPEchoClient(String severIp,int severPort) throws IOException {
//初始化客户端socket
socket = new Socket(severIp,severPort);
}
/**
* 启动客户端
*/
public void start(){
System.out.println("客户端已启动");
//1.获取输入输出流
try (InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()) {
//2.获取用户请求
while (true) {
System.out.println("->");
Scanner scanner = new Scanner(System.in);
String request =scanner.nextLine();
//3.校验请求
if(request.isEmpty()||request == null){
System.out.println("请求不能为空,请重新输入");
continue;
}
//4.将请求写入网卡发送出去
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(request);
printWriter.flush();
//5.等待接收响应
Scanner responseScanner = new Scanner(inputStream);
//6.解析响应数据
String response = responseScanner.nextLine();
//7.打印日志
System.out.printf("request: %s,response:%s\n",request,response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws IOException {
TCPEchoClient tcpEchoClient = new TCPEchoClient("192.168.0.106",9999);
tcpEchoClient.start();
}
}
结果展示:
客户端发送请求:
服务器回应: