文章目录
- 网络编程
- 一、什么是网络编程
- 1.TCP和UDP的区别
- 二、UDP数据报套接字编程
- DatagramSocket
- DatagramPacket
- 回显服务器(echo server)
网络编程
一、什么是网络编程
- 通过网络,让两个主机之间能够进行通信。基于通信来完成一定的功能。
进行网络编程的时候,需要操作系统提供一组API,通过这些API来完成。这些API可以认为的应用层和传输层之间交互的路径。这些API称为Socket API。通过一套Socket API 可以完成不同主机、不同系统之间的网络通信。
传输层提供的网络协议主要有两个:TCP、UDP。这两个协议的特性差异很大,会导致使用这两种协议进行网络编程,会存在一定的差别。系统就分别提供了两套API。
1.TCP和UDP的区别
1.TCP是有连接的,UDP是无连接的。
连接是抽象的概念:此处的连接本质上,就是建立连接的双方,各自保存对方的信息。两台计算机建立连接,就是双方彼此保存了对方的关键信息。TCP要想通信,就需要先建立连接(保存对方信息)。存完之后才能通信。
如果A想和B建立连接,但是B拒绝了,通信就无法完成。
UDP想要通信,自己不会去保存对方信息。直接发送数据,不需要征求对方同意。(程序员调用UDP的Socket API会传东对方信息)
2.TCP是可靠传输的,UDP是不可靠传输的。
在网络上进行通信,A给B发送消息。并不能保证100%送达。所以这里可靠传输的概念是:A给B发消息。A可以感知到,消息有没有到达B。就可以在发送失败时,采取一定的措施(尝试重传等)。但同时可靠传输的代价就是机制更复杂以及降低传输效率。
TCP就内置了可靠传输机制。
3.TCP是面向字节流的,UDP是面向数据报的。
TCP也是和文件操作一样,以字节为单位来进行传输。UDP则是按照数据报为单位进行传输。 UDP数据报有严格的格式。
网络通信数据的基本单位:1.数据报(Datagram)2.数据包(Packet)3.数据帧(Frame)4.数据段(Segment)
4.TCP和UDP都是全双工的。
一个信道,允许双向通信,就是全双工。一个信道,只能单向通信,就是半双工。
在代码中使用一个Socket对象,就可以发送数据也能接受数据。
二、UDP数据报套接字编程
Socket是操作系统中的概念,本质上是一种特殊的文件。就相当于把“网卡”这个设备抽象成了文件。后续往Socket文件中写数据,就相当于通过网卡发送数据。从Socket文件中读数据,就相当于通过网卡接收数据。把网络通信和文件操作进行统一。
DatagramSocket
在Java中,使用DatagramSocket这个类来表示系统内部的Socket文件。
DatagramSocket是UDP Socket,用来发送和接收UDP数据报
public UdpEchoServer(int port) throws SocketException {//指定一个端口号
socket = new DatagramSocket(port);//创建的socket对象绑定这个指定的端口。
}
1.这里的send发送 和receive接收方法,传进的参数类型都是DatagramPacket数据报。
2.receive方法中,参数同样是一个“输出型参数”。
DatagramPacket
-
DatagramPacket这个类,表示一个UDP数据报。
构造方法:
1.只指定字节数组缓冲区
DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
//用来承载从网卡中读到的数据。收到数据的时候需要搞一个内存空间来保存这个数据
//DatagramPacket内部不能自行分配内存空间,需要程序员手动创建空间,交给DatagramPacket处理
socket.receive(requestPacket);
2.指定字节数组缓冲区,同时制定一个InetAdress对象(包含了IP和端口号)
DatagramPacket responsePacket = new DatagramPacket(
response.getBytes(), //指定的数据
response.getBytes().length, //数据的长度
requestPacket.getSocketAddress());//发送来的地址就是要发送的地址。
public synchronized SocketAddress getSocketAddress() {
return new InetSocketAddress(getAddress(), getPort());
}
3.指定字节数组缓冲区,指定IP + 端口号。
DatagramPacket requestPacket = new DatagramPacket(
request.getBytes(), request.getBytes().length,
InetAddress.getByName(serverIp), serverPort);
UDP是面向数据报的,每次进行传输,都要以UDP数据报为基本单位。在DatagramSocket的方法中,接收和发送传入的参数就是DatagramPacket数据报类型。
回显服务器(echo server)
- 写一个简单的客户端、服务器通信程序。单纯调用Socket API。从控制台上输入一个请求发送给服务器,服务器收到字符串后,原封不动返回给客户端并显示出来。
服务器和客户端都需要创建Socket对象。但是 服务器的socket一般要显示的指定一个端口号。而客户端的socket一般不能显示指定。(不显示指定,系统会自动分配一个随机的端口)
服务器上有哪些程序,都使用哪些端口,都是程序员可控的。写代码时就可以指定空闲的端口,给当前的服务器使用。相比之下,客户端不可控。交给系统分配一个空闲的端口给客户端。所以服务器需要手动指定端口。客户端要交个系统来分配一个空闲端口。
UdpEchoServer
1.读取请求并解析
2.根据请求计算响应(一个服务器最核心的步骤)
3.把响应写回客户端
4.打印一个日志,把这次数据交互的详情打印出来
public class UdpEchoServer {
//1.先出创建DatagramSocket对象 :后续操作网卡的基础
private DatagramSocket socket = null;
public UdpEchoServer(int port) throws SocketException {//指定一个端口号
socket = new DatagramSocket(port);//创建的socket对象绑定这个指定的端口。
}
/**
* 通过这个方法启动服务器
*/
public void start() throws IOException {
System.out.println("服务器启动!");
//在服务器程序中,经常出现while true的代码
while (true) {
//1.读取请求并解析
DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
//用来承载从网卡中读到的数据。收到数据的时候需要搞一个内存空间来保存这个数据
//DatagramPacket内部不能自行分配内存空间,需要程序员手动创建空间,交给DatagramPacket处理
socket.receive(requestPacket);
//读取数据,并填充进DatagramPacket,如果没有接收到数据报,receive方法会阻塞等待
//此时是以二进制的形式存到DatagramPacket中,需要把二进制转换成字符串
String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
//获取到字符数组,取[0,getLength]区间内的字节,构造成String. getLength不是4096,是实际的收到的数据长度
//2.根据请求计算响应(一个服务器最核心的步骤)
//由于此处是回显服务器,请求是啥样,响应就是啥样。
String response = process(request);
//3.把响应写回客户端
//创建一个响应对象,DatagramPacket,往对象里构造刚才的数据,再通过send进行返回。
DatagramPacket responsePacket = new DatagramPacket(
response.getBytes(), //指定的数据
response.getBytes().length, //数据的长度
requestPacket.getSocketAddress());//发送来的地址就是要发送的地址。
//需要指定数据的内容,也要指定数据报要发给谁。
socket.send(responsePacket);
//4.打印一个日志,把这次数据交互的详情打印出来
System.out.printf("[%s:%d] 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 {
UdpEchoServer server = new UdpEchoServer(9090);
server.start();
}
- 需注意的是,这里并没有写close.因为socket是文件描述符表的一个表项,文件描述符表在PCB上(跟随进程)。
- socket在整个程序运行的过程中都需要使用,不能提前关闭。当不在再需要时,意味着程序就要结束了,进程结束,文件描述符表就会销毁。随着销毁,被系统自动回收
public class UdpEchoClient {
private DatagramSocket socket = null;
private String serverIp = "";
private int serverPort = 0;
public UdpEchoClient(String ip, int port) throws SocketException {
//客户端的socket对象的端口,由系统自动分配
socket = new DatagramSocket();
//由于UDP不会持有对端的信息,需要在应用程序里,把对端的情况记录下来。
serverIp = ip;
serverPort = port;
}
public void start() throws IOException {
System.out.println("客户端启动!");
Scanner scanner = new Scanner(System.in);
while (true) {
//1.从控制台读取数据,作为请求
System.out.print("->");
String request = scanner.next();
//2.把请求内容构造成DatagramPacket对象,再发给服务器
DatagramPacket requestPacket = new DatagramPacket(
request.getBytes(), request.getBytes().length,
InetAddress.getByName(serverIp), serverPort);//把转换字符串ip
socket.send(requestPacket);
//3.尝试读取服务器返回的响应
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 client = new UdpEchoClient("127.0.0.1",9090);
client.start();
}
1.服务器先启动,服务器启动之后,就会进入循环,执行到receive这里并进行阻塞
2.客户端开始启动,也会先进入while循环,执行到scanner.next,进行阻塞。当用户输入完成后,next就会返回,从而构造请求数据并进行发送给服务器。
3.服务器从receive中返回,进一步执行解析请求为字符串,执行process操作,执行send操作。
与此同时,客户端继续往下执行,执行到receive等待服务器的响应,进行阻塞。
4.客户端收到从服务器返回的数据后,就会从receive中返回,执行打印
5.服务器完成一次循环后,有执行到receive进行阻塞。客户端完成一次循环后,又执行到scanner.next进入阻塞,直到用户进行输入。
点击移步博客主页,欢迎光临~