Socket套接字
目录
1.Socket套接字
1.1数据报套接字
1.2流套接字
1.3原始套接字
2.UDP数据报套接字编程
2.1API介绍
2.2实现简易回显服务器
3.TCP流套接字编程
3.1API介绍
3.2实现简易回显服务器
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的Socketprotected 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*/@Overridepublic 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 {//创建一个客户端socketprivate Socket socket;//描述服务器的IP和端口号private String severIp;private int severPort;/*** 构造方法* @param severIp 服务器IP* @param severPort 服务器端口号*/public TCPEchoClient(String severIp,int severPort) throws IOException {//初始化客户端socketsocket = 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();}
}
结果展示:
客户端发送请求:
服务器回应: