网络编程(JavaEE)
前言:
熟悉了网络的基本概念之后,接下来就需要针对网络进行一系列的编程,其中可能涉及到新的一些编程操作,需要我们进一步探索!
网络编程套接字:
套接字其实是socket的翻译。
操作系统给应用程序(传输层给应用层)提供的API,起了个名字就叫socket。
接下来探究的就是操作系统提供的API,socket的相关内容:
socketAPI提供了两种:
一种是针对UDP协议提供的API,另一种是针对TCP协议提供的API。
UDP\TCP:
所谓的UDP和TCP针对传输层的两套协议,有什么不同的地方吗?
TCP:有链接,可靠传输、面向字节流、全双工。
UDP:无连接、不可靠传输、面向数据报、全双工。
1.有链接/无连接:通信双方如果保存了对端的信息就是有链接,如果有一端没有保存就是无连接。
2.可靠/不可靠传输:可靠是指接收方尽可能的接收到发送端的信息,不可靠是指发送方将信息发出后再不去干预。
TCP内置了一些机制确保可靠传输:
1、感知到对方是否接收到信息了。
2、重传机制,在对方没收到信息的时候进行重试。
3.面向字节流/数据报:
TCP面向字节流,此时传输数据就和文件流一个特点了。可以选择多传几次一次传n个字节。
UDP面向数据报,此时传输数据就是以数据报为单位,一次发送完整一个数据包,就必须完整接收一个数据包。
4.全双工/半双工:
全双工:一个通信链路可以发送数据,也可以接收数据(双向通信)
半双工:一个通信链路只可以发送/接收数据(单向通信)。
UDP的API:
直接操作网卡不太好操作,引入了socket对象,通过操作socket对象简介操作网卡。
系统的socket api在Java中又做了进一步的封装:DatagramSocket、DatagramPacket
对于UDP来说,由于发送和接收数据都是以数据报为单位,我们需要用到数据报类:DatagramPacket
echo Server:
通过UDP协议提供的API写一份网络编程的代码,其中涉及到客户端和服务器,在这里服务器不做任何复杂的业务,只是将客户端发来的内容显示出来就好,这样的服务器也被称为"回显服务器"。
public class UdpEchoSever {
private DatagramSocket socket;
//该异常是IOException的子类
public UdpEchoSever(int port) throws SocketException {
//此处在运行服务器的时候需要制定一个端口号
socket = new DatagramSocket(port);
}
//使用该方法启动服务器
public void start() throws IOException {
System.out.println("服务器启动!");
//服务器要持续不断地工作,这里使用while循环处理
while(true) {
//一个服务器要做的三件事情:
//1、读取请求并解析
//发送和接收数据数据报
DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
//如果网卡上没有数据,receive就会阻塞等待
socket.receive(requestPacket);
//上述接收到的是二进制的byte数组,此时转成字符串才好处理
String request = new String(requestPacket.getData(),0,requestPacket.getLength());
//2.根据请求计算出响应
String response = getProcess(request);
//3.把响应写回到客户端
//由于是无连接通信,DatagramSocket对象中不持有对方的IP和端口号,要想发送过去必须手动告诉客户端的IP和端口号
DatagramPacket datagramPacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
requestPacket.getSocketAddress());
socket.send(datagramPacket);
//此时记录日志方便观察程序执行效果
System.out.printf("[%s:%d] req:%s,resp:%s\n",requestPacket.getSocketAddress(),response);
}
}
private static String getProcess(String request) {
return request;
}
}
服务器写完之后,就开始写UDP相关的客户端代码:
echo Client:
public class UdpEchoClient {
private DatagramSocket socket = null;
private String severIp;
private int severPort;
//指定服务器的ip和服务器的端口
public UdpEchoClient(String ip,int port) throws SocketException {
this.severIp = ip;
this.severPort = port;
//此处让系统自动分配出一个空着的端口来
socket = new DatagramSocket();
}
//让客户端反复读从控制台读取内容,构造成UDP发送给服务器
//最终再显示在客户端的屏幕上
public void start() throws IOException {
Scanner scanner = new Scanner(System.in);
System.out.println("客户端启动!");
while(true) {
//1.从控制台读取用户输入的内容
System.out.print("->");
String request = scanner.next();
//2.构造请求对象并发送给服务器
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
InetAddress.getByName(severIp),severPort);
socket.send(requestPacket);
//3.读取出服务器的响应,并解析响应内容
DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
socket.receive(responsePacket);
String response = new String(responsePacket.getData(),0,responsePacket.getLength());
System.out.println(response);
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient udpEchoClient = new UdpEchoClient("127.0.0.1",9090);
udpEchoClient.start();
}
}
TCP的API:
注意:
此时的服务器的相关socket子类不再是DatagramSocket,而是SeverSocket。
此时的客户端的相关socket子类不再是DatagramSocket,而是Socket。
此时不再有类似DatagramPacket去描述一个数据报。
原因在于:
1、此时TCP发送和接收的是字节流
2、此时TCP是有链接通信,双方都需要保存对方的信息(IP+端口号)
echoServer:
public class TcpEchoServer {
ServerSocket socket = null;
public TcpEchoServer(int port) throws IOException {
socket = new ServerSocket(port);
}
public void start() throws IOException {
//接收信号
System.out.println("服务器启动!");
while(true) {
Socket clientSocket = socket.accept();
//处理连接
ProcessConnection(clientSocket);
}
}
private void ProcessConnection(Socket clientSocket) throws IOException {
System.out.printf("[%s,%d]: 客户端上线!\n",clientSocket.getInetAddress(),clientSocket.getPort());
//Stock内部包含两个字节流,拿到这两个字节流就可完成接下的读写操作
try(InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()) {
//读请求,解析请求,可能有多次请求
while(true) {
Scanner scanner = new Scanner(inputStream);
if(!scanner.hasNext()) {
//读取完毕客户端下线
System.out.printf("[%s,%d]: 客户端下线\n",clientSocket.getInetAddress(),clientSocket.getPort());
break;
}
String request = scanner.nextLine();
System.out.println(request);
//计算响应
String response = process(request);
//将计算后的响应写回socket
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(response);
//刷新缓冲区
printWriter.flush();
System.out.printf("[%s:%d] req:%s resp:%s",clientSocket.getInetAddress(),clientSocket.getPort()
,request,response);
}
}catch(IOException e) {
e.printStackTrace();
}finally {
clientSocket.close();
}
}
public String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);
tcpEchoServer.start();
}
}
echoClient:
public class TcpEnchoClient {
private Socket socket = null;
public TcpEnchoClient(String severIp,int serverPort) throws IOException {
//与服务器建立连接
socket = new Socket(severIp,serverPort);
}
public void start() {
System.out.println("客户端启动!");
Scanner scanner = new Scanner(System.in);
try(InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()) {
while(true) {
//获取请求
String request = scanner.nextLine();
//传入请求
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(request);
//清空缓冲区
printWriter.flush();
//获取回应,以字符串的方式读
Scanner scanner1 = new Scanner(inputStream);
String response = scanner1.nextLine();
System.out.println(response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws IOException {
TcpEnchoClient tcpEnchoClient = new TcpEnchoClient("127.0.0.1",9090);
tcpEnchoClient.start();
}
}
大致的流程和UDP是类似的,其中需要时注意的就是,这是有连接通信,通过Socket类中提供的InputStream和OutputStream进行读写操作!不再使用SocketPacket类进行读写操作。
当然以上的服务器的设计只会针对一个客户端提供服务,如果一个客户端没有结束进程,那么其他的客户端就会阻塞等待!直到上一个客户端进程结束。
为了确保能为多个客户端提供服务,接下来引入多线程:
多线程echoSever:
我们需要确保每一个客户端都能有服务器给予服务,可以使用多线程的方式解决上述问题:
1.普通多线程:
public void start() throws IOException {
//接收信号
System.out.println("服务器启动!");
while(true) {
Socket clientSocket = socket.accept();
Thread t = new Thread(()->{
//处理连接
try {
ProcessConnection(clientSocket);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
t.start();
}
}
2.引入线程池的多线程:
public void start() throws IOException {
//接收信号
System.out.println("服务器启动!");
//引入线程工场
ExecutorService service = Executors.newCachedThreadPool();
while(true) {
Socket clientSocket = socket.accept();
service.submit(new Runnable() {
@Override
public void run() {
try {
ProcessConnection(clientSocket);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
}
}
原文地址:https://blog.csdn.net/m0_75235246/article/details/144170995
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!