一.什么是网络编程
网络编程,指网络上的主机,通过不同的进程,以编程的方式实现网络通信(或称为网络数据传输)。
二.为什么要实现网络编程
我们通过网络编程可以在网络中获取资源,实质是通过网络,获取到我们所需要的资源。
三.如何进行网络编程
针对网络编程,操作系统提供了一套专门实现网络编程的API,称为Socket套接字
我们的程序在应用层,操作系统工作在传输层,socket套接字就是传输层提供给应用层的API,传输层中最知名的协议就是TCP和UDP。
四.Socket关键字
Socket关键字针对传输协议分为三类:
①TCP传输协议②UDP传输协议③原始套接字
Tcp传输协议和UDP传输协议的区别:
有连接:相当于打电话,得先接通,才能交互数据
无连接:相当于发微信,不需要接通,直接就能发数据
可靠传输:传输过程中,发送方道知接收方有没有收到数据
不可靠传输:传输过程中,发送方不知道接收方有没有收到数据
面向字节流:以字节为单位进行传输
面向数据报:以数据报为单位进行传输(一个数据报都会明确大小)一次发送/接收必须是一个完整的数据报
全双工:一条链路,双向通信
半双工:一条链路,单向通信
大小限制:打电话可长可短,但是发短信有字数限制。
4.1UDP中的API
主要涉及两个类DatagramSocket(针对服务器上启动的网络服务,对应到服务器上用来接收用户请求的进程)和DatagramPacket(针对通信报文)
在网络编程中收发数据的过程主要是通过网卡来实现的,接收数据是对网卡的读取,发送数据时对网卡的写入。
构造方法:
收发和关闭方法:
notes:receive()方法会在没有客服端访问时进入阻塞状态,send()方法则是直接发送
测试要求:
根据请求返回响应:响应数据与请求数据一致
服务端代码:
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;
/**
* @author tongchen
* @create 2023-02-17 23:30
*/
public class UDPEchoServer {
//创建socket变量
private DatagramSocket socket;
//通过构造方法指定端口号
public UDPEchoServer(int port) throws SocketException {
//检查端口号的合法性
if(port<0||port>65535){
throw new BindException("端口建议在合法范围内");
}
//创建socket
this.socket=new DatagramSocket(port);
}
//启动服务
public void start() throws IOException {
//标识运行
System.out.println("服务端开始启用.......");
while(true){
//使用datagrampacket进行数据的接收
DatagramPacket datagramPacket = new DatagramPacket(new byte[1024], 1024);
//使用socket接收数据
socket.receive(datagramPacket);
//解析接收到的数据
String request=new String(datagramPacket.getData(), 0, datagramPacket.getData().length, "utf-8");
//根据数据获取响应数据
String response=pocess(request);
//对响应数据进行封装
DatagramPacket responsePacket=new DatagramPacket(response.getBytes(StandardCharsets.UTF_8),
response.getBytes().length,datagramPacket.getSocketAddress());
//发送响应数据
socket.send(responsePacket);
System.out.printf("[%s:%d] request = %s, response = %s\n", datagramPacket.getAddress().toString(),
datagramPacket.getPort(), request, response);
}
}
private String pocess(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.nio.charset.StandardCharsets;
import java.util.Scanner;
/**
* @author tongchen
* @create 2023-02-18 11:00
*/
public class UDPchoClient {
//定义服务器的地址和端口号
private String address;
private int port;
private DatagramSocket socket;
//初始化端口号和目标地址
public UDPchoClient(String address, int port) throws SocketException {
this.address = address;
this.port = port;
this.socket=new DatagramSocket();
}
//启动程序
public void start() throws IOException {
System.out.println("客户端启动......");
//输入请求数据
while(true){
System.out.println("请输入请求数据");
Scanner scanner=new Scanner(System.in);
String request=scanner.nextLine();
//与服务端建立连接
InetSocketAddress inetSocketAddress = new InetSocketAddress(address, port);
//进行数据的封装
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(StandardCharsets.UTF_8), request.getBytes().length, inetSocketAddress);
//发送数据
socket.send(requestPacket);
//创建接收packet
DatagramPacket responsePacket = new DatagramPacket(new byte[1024], 1024);
//接收数据
socket.receive(responsePacket);
//解析数据
String response=new String(responsePacket.getData(), 0, responsePacket.getLength(), "utf-8");
//打印解析到的数据
System.out.printf("request=%s,response=%s\n",request,response);
}
}
public static void main(String[] args) throws IOException {
//创建客户端
UDPchoClient udPchoClient = new UDPchoClient("127.0.0.1", 9090);
//启动客户端
udPchoClient.start();
测试结果分析
测试要求2:请求英文单词,返回其中文翻译
服务端
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
/**
* @author tongchen
* @create 2023-02-18 11:36
*/
public class UDPdicServer {
//创建字典
private HashMapdic;
//创建socket
private DatagramSocket socket;
//通过构造方法进行对所需内容的初始化
public UDPdicServer(int port) throws SocketException {
//创建新的hashmap
this.dic=new HashMap<>();
dic.put("tiger", "老虎");
dic.put("cat", "小猫");
dic.put("dog", "小狗");
dic.put("pig", "小猪");
//创建新的socket
socket=new DatagramSocket(port);
}
//创建启动方法
public void start() throws IOException {
System.out.println("服务端启动.......");
//创建循环
while(true){
//创建接收请求的packet
DatagramPacket requestPacket=new DatagramPacket(new byte[1024],1024);
//接收数据
socket.receive(requestPacket);
//对数据进行解析
String request=new String(requestPacket.getData(), 0, requestPacket.getLength(), "utf-8");
//根据请求获取响应
String response=pocess(request);
//封装数据
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(StandardCharsets.UTF_8), response.getBytes().length, requestPacket.getSocketAddress());
//发送数据
socket.send(responsePacket);
//打印结果
System.out.printf("[%s:%d] request = %s, response = %s\n", requestPacket.getAddress().toString(),
requestPacket.getPort(), request, response);
}
}
private String pocess(String request) {
return dic.getOrDefault(request, "查无此词");
}
public static void main(String[] args) throws IOException {
//创建服务端
UDPdicServer udPdicServer = new UDPdicServer(9091);
udPdicServer.start();
}
}
用户端:
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;
/**
* @author tongchen
* @create 2023-02-18 11:00
*/
public class UDPchoClient {
//定义服务器的地址和端口号
private String address;
private int port;
private DatagramSocket socket;
//初始化端口号和目标地址
public UDPchoClient(String address, int port) throws SocketException {
this.address = address;
this.port = port;
this.socket=new DatagramSocket();
}
//启动程序
public void start() throws IOException {
System.out.println("客户端启动......");
//输入请求数据
while(true){
System.out.println("请输入请求数据");
Scanner scanner=new Scanner(System.in);
String request=scanner.nextLine();
//与服务端建立连接
InetSocketAddress inetSocketAddress = new InetSocketAddress(address, port);
//进行数据的封装
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(StandardCharsets.UTF_8), request.getBytes().length, inetSocketAddress);
//发送数据
socket.send(requestPacket);
//创建接收packet
DatagramPacket responsePacket = new DatagramPacket(new byte[1024], 1024);
//接收数据
socket.receive(responsePacket);
//解析数据
String response=new String(responsePacket.getData(), 0, responsePacket.getLength(), "utf-8");
//打印解析到的数据
System.out.printf("request=%s,response=%s\n",request,response);
}
}
public static void main(String[] args) throws IOException {
//创建客户端
UDPchoClient udPchoClient = new UDPchoClient("127.0.0.1", 9091);
//启动客户端
udPchoClient.start();
}
}
结果分析:
4.2实现TCP协议的类
ServerSocket API
ServerSocket 是创建TCP服务端Socket的API。
ServerSocket 构造方法:
Socket API
Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端
Socket。
不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。
Socket 构造方法:
我们通过实例对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;
/**
* @author tongchen
* @create 2023-02-20 10:50
*/
public class TcpEchoService {
//通过创建构造方法指定端口号获取socket连接
private ServerSocket socket;
public TcpEchoService(int port) throws IOException {
//判断端口的有效性
if(port<1025||port>65535){
throw new BindException("端口建议在1025和65535之间.......");
}
//通过指定端口号创建socket服务
this.socket=new ServerSocket(port);
}
//获取客户端连接
public void start() throws IOException {
System.out.println("服务端端获取连接成功......");
//不断循环接收用户的请求
while(true){
//获取与客户端的连接
Socket clientSocket = socket.accept();
//处理用户请求
pocessRequset(clientSocket);
}
}
private void pocessRequset(Socket clientSocket) throws IOException {
//打印客户端信息
System.out.printf("%s|%d,客服端连接成功......",clientSocket.getInetAddress().toString(),clientSocket.getPort());
//通过clientSocket中获取输入和输出数据
try(InputStream inputStream=clientSocket.getInputStream(); OutputStream outputStream=clientSocket.getOutputStream()){
//循环从输入中获取数据
while (true){
Scanner scan=new Scanner(inputStream);
//判断是否存在
if(!scan.hasNextLine()){
System.out.printf("%s|%d,客服端退出成功......",clientSocket.getInetAddress().toString(),clientSocket.getPort());
break;
}
//获取数据并获得返回数据
String request=scan.nextLine();
String response=pocess(request);
//发送数据
PrintWriter printWriter = new PrintWriter(outputStream);
//注意输入输出格式的一致性(用户和客户端)
printWriter.println(response);
//强制刷新缓存区
printWriter.flush();
//打印信息
System.out.printf("[%s:%d] request = %s, response = %s\n", clientSocket.getInetAddress().toString(),
clientSocket.getPort(), request, response);
}
}catch (Exception e){
e.printStackTrace();
}finally {
//关闭资源
clientSocket.close();
}
}
private String pocess(String request) {
return request;
}
//初始化服务器
public static void main(String[] args) throws IOException {
TcpEchoService tcpEchoService = new TcpEchoService(9090);
tcpEchoService.start();
}
}
用户端:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
/**
* @author tongchen
* @create 2023-02-20 11:17
*/
public class TcpEchoClient {
//创建socket
private Socket socket;
//构造方法创建socket
public TcpEchoClient(String address,int port) throws IOException {
this.socket=new Socket(address, port);
}
//连接
public void start(){
System.out.printf("客户端启动成功....");
try(InputStream inputStream=socket.getInputStream(); OutputStream outputStream=socket.getOutputStream()){
//循环输入
while(true){
System.out.println("->");
Scanner scan=new Scanner (System.in);
String request=scan.nextLine();
//验证request的有效性
if(request==null||request.equals("")){
System.out.println("输入的请求不能为空");
continue;
}
//将request输入到socket
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(request);
//强制刷新
printWriter.flush();
//在socket中获取请求
Scanner scanner = new Scanner(inputStream);
String response = scanner.nextLine();
System.out.printf("request = %s, response = %s\n", request, response);
}
}catch (Exception e){
e.printStackTrace();
}finally {
}
//循环输入
}
//验证‘=
public static void main(String[] args) throws IOException {
TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1", 9090);
tcpEchoClient.start();
}
}
一个客户端时可以正常运行,建立两个客户端时却出现了下面的情况:
我们去分析情况,是因为只有一个线程,客户端没有退出,服务端卡在第二个while循环中出不来
解决措施也很简单,开启多线程即可,为了线程频繁的开启和销毁所消耗的系统资源,我们使用线程池进行优化:
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;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author tongchen
* @create 2023-02-20 12:18
*/
public class ThreadPoolTest {
//通过创建构造方法指定端口号获取socket连接
private ServerSocket socket;
public ThreadPoolTest(int port) throws IOException {
//判断端口的有效性
if(port<1025||port>65535){
throw new BindException("端口建议在1025和65535之间.......");
}
//通过指定端口号创建socket服务
this.socket=new ServerSocket(port);
}
//获取客户端连接
public void start() throws IOException {
System.out.println("服务端端获取连接成功......");
//不断循环接收用户的请求
//创建线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 10, 1, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10));
while(true){
//获取与客户端的连接
Socket clientSocket = socket.accept();
threadPoolExecutor.submit(()->{
try {
//处理用户请求
pocessRequset(clientSocket);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
}
private void pocessRequset(Socket clientSocket) throws IOException {
//打印客户端信息
System.out.printf("%s|%d,客服端连接成功......",clientSocket.getInetAddress().toString(),clientSocket.getPort());
//通过clientSocket中获取输入和输出数据
try(InputStream inputStream=clientSocket.getInputStream(); OutputStream outputStream=clientSocket.getOutputStream()){
//循环从输入中获取数据
while (true){
Scanner scan=new Scanner(inputStream);
//判断是否存在
if(!scan.hasNextLine()){
System.out.printf("%s|%d,客服端退出成功......",clientSocket.getInetAddress().toString(),clientSocket.getPort());
break;
}
//获取数据并获得返回数据
String request=scan.nextLine();
String response=pocess(request);
//发送数据
PrintWriter printWriter = new PrintWriter(outputStream);
//注意输入输出格式的一致性(用户和客户端)
printWriter.println(response);
//强制刷新缓存区
printWriter.flush();
//打印信息
System.out.printf("[%s:%d] request = %s, response = %s\n", clientSocket.getInetAddress().toString(),
clientSocket.getPort(), request, response);
}
}catch (Exception e){
e.printStackTrace();
}finally {
//关闭资源
clientSocket.close();
}
}
private String pocess(String request) {
return request;
}
//初始化服务器
public static void main(String[] args) throws IOException {
ThreadPoolTest tcpEchoService = new ThreadPoolTest(9090);
tcpEchoService.start();
}
}
第二个任务:实现简单对话:
import javax.sound.sampled.Port;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author tongchen
* @create 2023-02-20 15:01
*/
public class TcpMsgServer {
//创建socket
private ServerSocket socket;
public TcpMsgServer(int port) throws IOException {
this.socket=new ServerSocket(port);
}
//开启工作
public void start() throws IOException {
System.out.println("用户端开启成功......");
//循环接收用户请求
//创建线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 10, 1, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10));
while(true){
//接收用户请求
Socket clientSocket = socket.accept();
//处理用户请求(使用多线程)
threadPoolExecutor.submit(()->{
try {
pocessRequset(clientSocket) ;
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
}
private void pocessRequset(Socket clientSocket) throws IOException {
System.out.printf("%s|%d,客服端连接成功......\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
//获取两个流
try(InputStream inputStream=clientSocket.getInputStream(); OutputStream outputStream=clientSocket.getOutputStream()){
//多次获取用户请求
while(true){
//获取用户输入
Scanner scan=new Scanner(inputStream);
if(!scan.hasNext()){
System.out.printf("%s|%d,客服端退出成功......",clientSocket.getInetAddress().toString(),clientSocket.getPort());
break;
}
String request= scan.nextLine();
//根据请求获取响应
String response=pocess(request);
//通过print发回
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(response);
//打印效果
printWriter.flush();
System.out.printf("[%s:%d] request = %s, response = %s\n", clientSocket.getInetAddress().toString(),
clientSocket.getPort(), request, response);
}
}catch (Exception e){
e.printStackTrace();
}finally {
clientSocket.close();
}
}
private String pocess(String request) {
System.out.println("请求为"+request);
System.out.println("请输入你的响应->");
Scanner scanner=new Scanner(System.in);
String response=scanner.nextLine();
return response;
}
//开启
public static void main(String[] args) throws IOException {
TcpMsgServer msgServer = new TcpMsgServer(9090);
msgServer.start();
}
}