文章分以下几个部分:
我们为什么需要网络编程?为了获取丰富的网络资源。 用户在浏览器中,打开在线视频网站,比如通过优酷去看视频,实则是通过网络,获取到网上一个视频资源。与本地打开视频文件类似,只是这个视频文件的资源的来源是网络。相比本地资源来说,网络提供了更为丰富的网络资源。 所谓的网络资源,其实就是在网络中可以获取到的各种数据资源。而所有的网络资源,都是通过网络编程来进行数据传输的。
什么是网络编程?网络编程,指网络上的主机,通过不同的进程,以编程的方式实现网络通信。
我们只需要满足进程不同就行;所以即便是同一个主机,只要是不同的进程,基于网络来进行数据传输,也属于网络编程。
在一次网络数据传输时:
发送端:数据的发送方进程,称为发送端。发送端主机即网络通信中的源主机。
接收端:数据的接收方进程,称为接收端。接收端主机即网络通信中的目的主机。
收发端:发送端和接收端。
发送端和接收端只是相对的,只是一次网络数据传输产生数据流向后的概念。
一般来说,获取一个网络资源,涉及到两次网络数据传输。
第一次:请求数据的发送;
第二次:响应数据的发送;
服务端:在常见的网络数据传输场景下,把提供服务的一方进程,称为服务端,可以对外提供服务。
客户端:获取服务的一方进程,称为客户端。
对于服务端来说,一般是提供:客户端获取服务资源;客户端保存资源在服务端。
常见的客户端服务端模型:最常见的场景:客户端是指给用户使用的程序,服务端是提供用户的服务的程序:
1.客户端先发送请求到服务端。
2.服务端根据请求计算响应。
3.服务端将响应返回给服务端。
4.客户端展示服务端返回的响应。
Socket套接字,是由操作系统系统的用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。基于Socket套接字的网络程序开发就是网络编程。
Socket套接字主要针对传输层协议划分为如下三类:
流套接字:使用传输层TCP协议。TCP:即Transmission Control Protocol(传输控制协议)。以下是TCP的特点:1.有连接。 2.可靠传输。 3.面向字节流。 4.有接收缓冲区,也有发送缓冲区。 5.大小不限。
对于字节流来说,可以简单的理解为:传输数据是基于IO流,流式数据的特征就是在IO流没有关闭的情况下,是无边界的数据,可以多次发送,也可以分开多次接收。
数据报套接字:使用传输层UDP协议。UDP:即User Datagram Protocol(用户数据报协议)。以下是UDP的特点:1.无连接。 2.不可靠传输。 3.面向数据报。 4.有接收缓冲区,无发送缓冲区。5.大小受限,一次最多传输64K。
对于数据报来说,可以简单的理解为,传输数据是一块一块的,发送一块数据假如100个字节,必须一次发送,接收也必须一次接收100个字节,而不能分100次,每次接收1个字节。
原始套接字:原始套接字用于自定义传输层协议,用于读写内核没有处理的IP协议数据。
对于UDP协议来说,具有无连接、面向数据报的特征,即每次都是没有建立连接,并且一次发送全部的数据报,一次接收全部的数据报。
java中使用UDP协议通信,主要基于DatagramSocket类来创建数据报套接字,并使用DatagramPacket类作为发送或接收的UDP数据报。
DatagramSocket是UDP Socket,用于发送和接收UDP数据报。
DatagramSocket 构造方法:
1.方法签名: DatagramSocket();方法说明:创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口(一般用于客户端)。
2.方法签名:DatagramSocket(int port);方法说明:创建一个UDP数据报套接字的Socket,绑定到本机指定的端口号(一般用于服务器)。
DatagramSocket 方法:
1.方法签名:void receive(DatagramPacket p);方法说明:从此套接字(参数)接收数据报。如果没有接收数据报,该方法会阻塞等待。
2.方法签名:void send(DatagramPacket p);方法说明:从此套接字(参数)发送数据报。该方法不会阻塞等待,直接发送。
3.方法签名:void close();方法说明:关闭此数据报套接字。
DatagramPacket 是UDP Socket发送和接收的数据报。
DatagramPacket 构造方法:
1.方法签名:DatagramPacket(byte[] buf,int length);方法说明:构造一个DatagramPacket以用来接收数据报,接收的数据保存在字节数组(第一个参数buf)中,接收指定长度(第二个参数length)。
2.方法签名:DatagramPacket(byte[] buf,int offset,int length,SocketAddress address);方法说明:构造一个DatagramPacket以用来发送数据报,发送的数据为字节数组(第一个参数buf)中,从0到指定长度(第二个参数length)。address指定目的主机的IP地址和端口号。
DatagramPacket 方法:
1.方法签名:InetAddress getAddress();方法说明:从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取接收端主机IP地址。
2.方法签名:int getPort();方法说明:从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接收端主机的端口号。
3.方法签名:byte[] getData();方法说明:获取数据报中的数据。
构造UDP发送的数据报时,需要传入SocketAddress,该对象可以使用InetSocketAddress类(SocketAddress的子类)来创建。
InetSocketAddress的构造方法:
1.方法签名:InetSocketAddress(InetAddress addr,int port);方法说明:创建一个Socket地址,包含IP地址和端口号。
我们以回显客户端-服务器为例来理解java数据报套接字通信过程:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
//UDP服务端:
public class UdpEchoServer {
private DatagramSocket socket=null;
public UdpEchoServer(int port) throws SocketException {
socket=new DatagramSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动!");
while(true){
//首先接收客户端的请求:
DatagramPacket requestPacket=new DatagramPacket(
new byte[4096],4096
);
socket.receive(requestPacket);
//然后将这个请求转换为字符串的形式:
String request=new String(requestPacket.getData(),0,
requestPacket.getLength());
//然后处理客户端发来的请求:
String response=process(request);
//然后将回应写回到客户端:
DatagramPacket responsePacket=new DatagramPacket(response.getBytes(),
response.getBytes().length,requestPacket.getSocketAddress());
socket.send(requestPacket);
System.out.printf("[%s:%d] request:%s; response:%s;",
requestPacket.getAddress().toString(),
requestPacket.getPort(),request,response);
}
}
public String process(String request){
return request;
}
public static void main(String[] args) throws IOException {
UdpEchoServer server=new UdpEchoServer(9090);
server.start();
}
}
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
//UDP客户端:
public class UdpEchoClient {
//该java文件实现一个客户端模式:
private DatagramSocket socket=null;
private String serverIP;
private int serverPort;
public UdpEchoClient(String serverIP,int serverPort) throws SocketException {
socket = new DatagramSocket();
this.serverIP=serverIP;
this.serverPort=serverPort;
}
public void start() throws IOException {
Scanner scanner=new Scanner(System.in);
while(true){
//首先读取用户的请求:
System.out.print("-> ");
String request=scanner.next();
//然后构造一个UDP请求发送给服务器:
DatagramPacket requestPacket=new DatagramPacket(
request.getBytes(),request.getBytes().length,
InetAddress.getByName(this.serverIP),this.serverPort
);
socket.send(requestPacket);
//然后从服务器读取响应并解析:
DatagramPacket responsePacket=new DatagramPacket(new byte[
4096],4096);
socket.receive(responsePacket);
//然后将收到的响应转换成字符串:
String response=new String(responsePacket.getData(),0,
requestPacket.getLength());
//然后将响应显示到屏幕上:
System.out.println(response);
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient client=new UdpEchoClient("127.0.0.1",9090);
client.start();
}
}
ServerSocket 是创建TCP服务端Socket的API。
ServerSocket 的构造方法:
1.方法签名:ServerSocket(int port);方法说明:创建一个服务端流套接字Socket,并绑定到指定端口。
ServerSocket 的方法:
1.方法签名:Socket accept();方法说明:开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket对象,并基于该Socket建立与客户端的连接,否则阻塞等待。
2.方法签名:void close();方法说明:关闭此套接字。
Socket 是客户端Socket,或服务器中接收到客户端建立连接(accept)的请求后,返回的服务端Socket。不管是客户端Socket还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。
Socket 的构造方法:
1.方法签名:Socket(String host,int port);方法说明:创建一个客户端流套接字Socket,并于对应IP上的主机,对应端口上的进程建立连接。
Socket 的方法:
1.方法签名:InetAddress getInetAddress();方法说明:返回此套接字所连接的地址;
2.方法签名:InputStream getInputStream();方法说明:返回此套接字的输入流;
3.方法签名:OutputStream gerOutputStream();方法说明:返回此套接字的输出流;
我们以回显客户端-服务器为例来理解java流套接字通信过程:
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.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TCPEchoServer {
//实现TCP版本的服务器:
private ServerSocket listenSocket=null;
public TCPEchoServer(int port) throws IOException {
listenSocket=new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动!");
ExecutorService service= Executors.newCachedThreadPool();
Socket clientSocket=listenSocket.accept();
//然后去处理链接:
// Thread t=new Thread(()->{
// processConnection(clientSocket);
// });
// t.start();
service.submit(new Runnable() {
@Override
public void run() {
processConnection(clientSocket);
}
});
}
private void processConnection(Socket clientSocket){
System.out.printf("[%s:%d] 客户端上线!\n",clientSocket.getInetAddress().toString(),
clientSocket.getPort());
try(OutputStream outputStream=clientSocket.getOutputStream();
InputStream inputStream=clientSocket.getInputStream()) {
while(true){
//首先读取请求并解析:
Scanner scanner=new Scanner(inputStream);
if(!scanner.hasNext()){
System.out.printf("[%s:%d] 客户端下线!\n",
clientSocket.getInetAddress().toString(),
clientSocket.getPort());
break;
}
String request=scanner.next();
//然后根据请求计算响应:
String response=process(request);
//将响应写回给客户端:
PrintWriter printWriter=new PrintWriter(outputStream);
printWriter.println(response);
printWriter.flush();
//打印一下日志:
System.out.printf("[%s:%d] req:%s res:%s\n",clientSocket.getInetAddress().toString(),
clientSocket.getPort(),request,response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private String process(String request){
return request;
}
public static void main(String[] args) throws IOException {
TCPEchoServer tcpEchoServer=new TCPEchoServer(9090);
tcpEchoServer.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;
public class TCPEchoClient {
//TCP版本的客户端:
private Socket socket=null;
public TCPEchoClient(String serverIp,int serverPort) throws IOException {
socket=new Socket(serverIp,serverPort);
}
public void start(){
Scanner scanner=new Scanner(System.in);
try(InputStream inputStream=socket.getInputStream();
OutputStream outputStream=socket.getOutputStream()){
System.out.printf("->");
String request=scanner.next();
//将请求发送给服务器:
PrintWriter printWriter=new PrintWriter(outputStream);
printWriter.println(request);
printWriter.flush();
//然后从服务器中读取响应:
Scanner responseScanner=new Scanner(inputStream);
String response=responseScanner.next();
System.out.println(response);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws IOException {
TCPEchoClient client=new TCPEchoClient("127.0.0.1",9090);
client.start();
}
}