1. 什么是网络编程?
网络编程,指网络上的主机,通过不同的进程,以编程的方式实现网络通信(或称为网络数据传输)。
这里只是在开发学习的时候使用的是,不同的进程来代表不同的主机来模拟网络通信,网络变成的目的就在于实现不同主机间的数据资源交互,具体地就是A来获取网络资源,B能够对A的请求做出响应来提供网络资源。
网络编程中的基本概念:
发送端和接收端
发送端:数据的发送方进程,称为发送端。发送端主机即网络通信中的源主机。
接收端:数据的接收方进程,称为接收端。接收端主机即网络通信中的目的主机。
收发端:发送端和接收端两端,也简称为收发端。
注意:发送端和接收端只是相对的,只是一次网络数据传输产生数据流向后的概念。
请求和响应
请求和响应就对应着两次网络传输,第一次就是A向B发送一个请求,此时发出请求数据;第二次是B给A发送一个响应,发送的是响应数据。
服务端和客户端
服务端和客户端很好理解,提供服务的一方称之为服务端,反之,获取服务的一方就称之为客户端。
最常见的场景就是,客户端给指定的用户提供服务,服务端是提供用户服务的程序,流程大致如下:
- 客户端先发送请求到服务端
- 服务端根据请求数据,执行相应的业务处理
- 服务端返回响应:发送业务处理结果
- 客户端根据响应数据,展示处理结果(展示获取的资源,或提示保存资源的处理结果)
2. Socket套接字
概念:Socket套接字,是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。基
于Socket套接字的网络程序开发就是网络编程。
分类
类型 | 特点 |
---|---|
流套接字,使用传输层TCP协议,TCP,即Transmission Control Protocol(传输控制协议),传输层协议。 | 有连接、可靠传输、面向字节流、有缓冲区、大小不限 |
数据报套接字,使用传输层UDP协议 ,UDP,即User Datagram Protocol(用户数据报协议),传输层协议。 | 无连接、不可靠传输、面向数数据报、有接收缓冲区、无发送缓冲区、大小受限(<64k) |
3. UDP数据报套接字编程
使用UDP数据报套接字编程必须先了解两个重要的API的使用,它们分别是 DatagramSocket API 和 DatagramPacket API
DatagramSocket API
DatagramSocket 是UDP Socket,用于发送和接收UDP数据报,他的构造方法如下:
方法签名 | 方法说明 |
---|---|
DatagramSocket() | 创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口(一般用于客户端) |
DatagramSocket(int port) | 创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用于服务端) |
客户端的进程在发送请求的时候,端口号一般来说就是我们的操作系统为我们分配的,给我们分配一个当前空闲的端口号,而服务器的端口号必须是我们指定的而且不能随机发生变化,就像我们打客服电话一样,客服电话一般都是固定的供我们发起“请求”,然后会根据我们的请求,给我们一些相应的响应。
DatagramSocket 主要方法:
方法签名 | 方法说明 |
---|---|
void receive(DatagramPacket p) | 从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待) |
void send(DatagramPacketp) | 从此套接字发送数据报包(不会阻塞等待,直接发送) |
void close()) | 关闭此数据报套接字 |
DatagramPacket API
DatagramPacket是UDP Socket发送和接收的数据报,他的构造方法如下:
方法签名 | 方法说明 |
---|---|
DatagramPacket(byte[] buf, int length) | 构造一个DatagramPacket以用来接收数据报,接收的数据保存在字节数组(第一个参数buf)中,接收指定长度(第二个参数length) |
DatagramPacket(byte[] buf, int offset, int length,SocketAddress address | 构造一个DatagramPacket以用来发送数据报,发送的数据为字节数组(第一个参数buf)中,从0到指定长度(第二个参数length)。address指定目的主机的IP和端口号 |
DatagramPacket的主要方法:
方法签名 | 方法说明 |
---|---|
InetAddress getAddress() | 从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取接收端主机IP地址 |
int getPort() | 从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接收端主机端口号 |
byte[] getData() | 获取数据报中的数据 |
构造UDP发送的数据报时,需要传入 SocketAddress ,该对象可以使用 InetSocketAddress 来创建。
InetSocketAddress API
InetSocketAddress ( SocketAddress 的子类 )构造方法:
方法签名 | 方法说明 |
---|---|
InetSocketAddress(InetAddress addr, int port) | 创建一个Socket地址,包含IP地址和端口号 |
4. 基于套接字编程写一个UDP服务器
服务器端代码:
public class UdpEchoServer {
//网络编程本质上就是操作网卡。但是网卡是硬件 不方便操作,在操作系统中,使用一种特殊的叫“socket”这样的文件来抽象表示网卡
//因此网络通信 现有一个socket对象
private DatagramSocket socket = null;
//作为服务器来说 要有一个固定的端口号 如果是操作系统来分配的话客户端就不知道端口号是多少了 也就无法通信
public UdpEchoServer(int port) throws SocketException {
socket = new DatagramSocket(port);
}
public void start() throws IOException {
System.out.println("==========服务器启动==========");
//服务器不止给一个客户端提供服务 真要有客户端发送来请求就必须响应 这里使用循环
while (true){
// 1. 读取用户发送来的数据
//receive方法的参数是一个输出型参数 需要先构造好空白的DatagramPacket 对象 交给receive来填充
DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
socket.receive(requestPacket);
//此时的DatagramPacket是一个特殊的对象,将包含数据的部分拿出来 构造成一个字符串
String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
//2. 根据请求计算响应
String response = process(request);
//3. 把响应写回到客户端
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());
socket.send(responsePacket);
//4. 打印一下 当前这次请求响应的处理中间结果
System.out.printf("[%s:%s] req: %s; resp : %s\n",requestPacket.getAddress().toString(),requestPacket.getPort(),request,response);
}
}
public String process(String request){
return request;
}
public static void main(String[] args) throws IOException {
//指定端口号(1024-65535)
UdpEchoServer server = new UdpEchoServer(9090);
server.start();
}
}
客户端代码:
public class UdpEchoClient {
private DatagramSocket socket = null;
//服务器的ip和端口
private String serverIp = null;
private int serverPort = 0;
//一次通信需要有两个ip 两个端口号 :客户端的和服务器的
//需要将服务器的IP和端口号告诉客户端才可以成功通信
public UdpEchoClient(String serverIp, int serverPort) throws SocketException {
socket = new DatagramSocket();
this.serverIp = serverIp;
this.serverPort = serverPort;
}
public void start() throws IOException {
System.out.println("客户端启动...");
Scanner scanner = new Scanner(System.in);
while (true) {
//1.读取要发送的数据
System.out.println("> ");
String request = scanner.next();
if(request.equals("exit")){
System.out.println("Bye!");
break;
}
//2.构造成UDP请求,并发送
// InetAddress.getByName(serverIp) --> IP需要填写的是32位整数形式,此时的ip是string 需要用这个方法转换
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(serverIp),serverPort);
socket.send(requestPacket);
//3.读取udp响应 并 解析
DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
socket.receive(responsePacket);
//4.把解析出来的结果显示出来
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();
}
}
通过下面设置可以启动多个客户端的实例:
查看结果:
圈起来的分别是客户端的ip和端口,以及请求信息和返回的响应。