一.什么是网络编程
用户在浏览器中,打开在线视频网站,如优酷看视频,实质是通过网络,获取到网络上的一个视频资源。
与本地打开视频文件类似,只是视频文件这个资源的来源是网络。所谓网络资源就是网络中获取数据。而所有的网络资源,都是通过网络编程来进行数据传输的。网络编程,指网络上的主机,通过不同的进程,以编程的方式实现网络通信(或称为网络数据传输)。
当然,我们只要满足进程不同就行;所以即便是同一个主机,只要是不同进程,基于网络来传输数据,也属于网络编程。
发送端和接收端:
在一次网络数据传输时:发送端:数据的发送方进程,称为发送端。发送端主机即网络通信中的源主机。
接收端:数据的接收方进程,称为接收端。接收端主机即网络通信中的目的主机。
收发端:发送端和接收端两端,也简称为收发端。
注意:发送端和接收端只是相对的,只是一次网络数据传输产生数据流向后的概念。
请求和响应:
一般来说,获取一个网络资源,涉及到两次网络数据传输。第一次:请求数据的发送。第二次:响应数据的发送。
客户端和服务端:
服务端:在常见的网络数据传输场景下,把提供服务的一方进程,称为服务端,可以提供对外服务。
客户端:获取服务的一方进程,称为客户端。对于服务来说,一般是提供:
客户端获取服务资源和客户端保存资源在服务端。
常见的客户端服务端模型:
最常见的场景,客户端是指给用户使用的程序,服务端是提供用户服务的程序:1. 客户端先发送请求到服务端。2. 服务端根据请求数据,执行相应的业务处理。3. 服务端返回响应:发送业务处理结果。4. 客户端根据响应数据,展示处理结果(展示获取的资源,或提示保存资源的处理结果)
二.UDP和Tcp的特点
这里列举的UDP和Tcp只是很简单的一些特点,后面会详细谈论Tcp和UDP。
udp:无连接,不可靠,全双工,面向数据报。
tcp:有连接,可靠,全双工,面向字节流。
有连接和无连接:JDBC会先创建一个DataSource,再通过DataSource建立Connect连接。比如打电话时对方接通才算建立连接成功。
可靠和不可靠:A尽可能的给B传输信息,传输 失败时,A能感知到。可靠和不可靠都有优缺点,可靠传输效率低,不可靠传输效率高,具体用哪一个要看场景。
字节流和数据报:Tcp和文件操作类似,都是以字节传输的,像流一样。
UDP面向数据报,读写的单位是一个UDP数据报(后面介绍)。
全双工半双工:全双工,一个通道可以双向通信。半双工,一个通道只能单向通信。
三.UDP实现客户端服务器
2个核心类DatagramSocket和DatagramPacket
DatagramSocket
DatagramSocket是一个socket对象,操作系统使用文件这样的概念来管理一些软硬件资源。表示网卡的这类文件被称为socket文件,网卡也是操作系统使用文件来控制的。java的socket对象就对应着socket对象。
第一个构造方法用于客户端,第二个构造方法指定了端口号用于服务器。
为什么服务器要指定一个特定的端口号,而客户端要一个随机的端口号。
比如我要去学校食堂的2号窗口吃饭,学校就是服务器,我就是客户端。为了让学生每次都能找到,我窗口的位置必须是固定的,使用一个固定的端口号。而学生每次吃饭坐的位置不同,上次做的位置可能这次来被别人占用了,所以需要随机分配一个空闲的座位,也就是分配一个随机的端口号。
第一个方法是接收数据报,第二个发送数据报,第三个关闭。前2个方法都传入了DatagramPacket 类型的方法参数,那么DatagramPacke是什么?
DatagramPacke
DatagramPacke是一个UDP数据报,代表了系统中设定的UDP数据报的二进制形式。
DatagramPacket作为UDP数据报,必然要能够承载一些数据,通过手动指定byte[]作为数据的存储空间
实现Udp服务器
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
class UdpSever{
public DatagramSocket socket =null;
public UdpSever(int port) throws SocketException {
this.socket=new DatagramSocket(port);
}
public void start() throws IOException {
System.out.println("客户端启动");
while (true){
DatagramPacket datagramPacket = new DatagramPacket(new byte[1024],1024);
socket.receive(datagramPacket);
String str = new String(datagramPacket.getData(),0,datagramPacket.getLength());
String requsrt = process(str);
DatagramPacket datagramPacket1 = new DatagramPacket(requsrt.getBytes(),requsrt.getBytes().length,datagramPacket.getSocketAddress());
socket.send(datagramPacket1);
System.out.print("["+datagramPacket.getAddress().toString()+" ");
System.out.print(datagramPacket.getPort()+"]:");
System.out.print("req:"+str+" ");
System.out.println("reqs:"+requsrt);
}
}
public String process(String str){
return str;
}
}
public class Test3 {
public static void main(String[] args) throws IOException {
UdpSever udpSever = new UdpSever(9090);
udpSever.start();
}
}
创建DatagramPacket对象,传入字节数组,DatagramPacket的返回值是一个字节数组,第一句代码运行结束此时并没有把客户端发来的信息写入。socket.reveive才是把客户端发来的信息真正的写入创建的字节数组中。
因为写入字节数组的内容二进制形式,所以构造字符串把接收到的二进制内容转换为字符串。同时计算出响应。
UDP客户端
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
class Udpclint{
public DatagramSocket socket=null;
public String SeverIP;
public int SeverPort;
public Udpclint(String SeverIP,int SeverPort) throws SocketException {
this.SeverIP=SeverIP;
this.SeverPort=SeverPort;
this.socket=new DatagramSocket();
}
public void start() throws IOException {
System.out.println("客户端启动:");
while(true){
System.out.print("----->");
Scanner scanner = new Scanner(System.in);
String str = scanner.next();
DatagramPacket datagramPacket = new DatagramPacket(str.getBytes(),str.getBytes().length, InetAddress.getByName(SeverIP),SeverPort);
socket.send(datagramPacket);
DatagramPacket datagramPacket1 = new DatagramPacket(new byte[1024],1024);
socket.receive(datagramPacket1);
String requst = new String(datagramPacket1.getData(),0,datagramPacket1.getLength());
System.out.println(requst);
}
}
}
public class Test4 {
public static void main(String[] args) throws IOException {
Udpclint udpclint = new Udpclint("127.0.0.1",9090);
udpclint.start();
}
}
客户端和服务器相互搭配运行效果
根据客户端和服务器代码实现翻译功能
分析:字典功能需要客户端输入中文意思,服务器显示英文单词。只需要客户端在计算响应的时候,实现翻译处理,所以让自己实现的翻译类继承服务器类,重写计算响应的方法(process),使其具备翻译功能
import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
class GrammerSever extends UdpSever{
Map<String,String> map = new HashMap<>();
public GrammerSever(int port) throws SocketException {
super(port);
map.put("猫","cat");
map.put("狗","dog");
map.put("鱼","fish");
}
@Override
public String process(String str) {
return map.get(str);
}
}
public class Test5 {
public static void main(String[] args) throws IOException {
GrammerSever grammerSever = new GrammerSever(9090);
grammerSever.start();
}
}
Tcp实现
Tcp分量要比Udp更重,用的更多的协议,,Tcp主要有2个类,SeverSocket和socket。给服务器用seversocket,socket既可以客户端使用也可以服务器使用。
Tcp服务器
mport 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.ExecutorService;
import java.util.concurrent.Executors;
class TcpSever{
public ServerSocket socket = null;
public ExecutorService executorService = Executors.newCachedThreadPool();
public TcpSever(int port) throws IOException {
this.socket=new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动:");
while(true){
Socket socket1 = socket.accept();
executorService.submit(new Runnable() {
@Override
public void run() {
try {
process(socket1);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
}
}
public void process(Socket socket1) throws IOException {
System.out.printf("[%s:%d] 客户端上线!\n", socket1.getInetAddress().toString(), socket1.getPort());
try(InputStream inputStream = socket1.getInputStream();
OutputStream outputStream = socket1.getOutputStream()) {
while (true){
Scanner scanner =new Scanner(inputStream);
if(!scanner.hasNext()){
System.out.printf("[%s:%d] 客户端下线!\n", socket1.getInetAddress().toString(), socket1.getPort());
break;
}
String requst = scanner.next();
String requse = process1(requst);
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(requse);
printWriter.flush();
System.out.printf("[%s:%d] req: %s, resp: %s\n", socket1.getInetAddress().toString(), socket1.getPort(),
requse, requst);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
finally {
socket1.close();
}
}
public String process1(String str){
return str;
}
}
public class Test {
public static void main(String[] args) throws IOException {
TcpSever tcpSever = new TcpSever(22);
tcpSever.start();
}
}
Tcp首先要处理连接,内核的连接就想一个待办事项,这些待办事项在一个队列的数据结构中,应用程序就需要一个一个完成这些任务,要完成这些任务就得先取任务。
accept是把内核中已经建立好的连接拿到应用程序中,但是这里的返回值并非是一个connect这样的对象,而只是一个socket对象,这个socket对象就像一个耳麦一样可以说话也可以听到对方的声音。
Tcp客户端
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
class Tcpclint{
public Socket socket =null;
public Tcpclint(String IP,int port) throws IOException {
this.socket=new Socket(IP,port);
}
public void start(){
System.out.println("客户端启动:");
try(InputStream inputStream = socket.getInputStream();
OutputStream outputStream =socket.getOutputStream()) {
while (true) {
System.out.print("-------->");
Scanner scanner = new Scanner(System.in);
String str = scanner.next();
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(str);
printWriter.flush();
Scanner scanner1 = new Scanner(inputStream);
String str1 = scanner1.next();
System.out.println(str1);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public class Test2 {
public static void main(String[] args) throws IOException {
Tcpclint tcpclint = new Tcpclint("127.0.0.1",22);
tcpclint.start();
}
}
Tcp实现单词翻译功能
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
class Grammer extends TcpSever{
Map<String,String> map = new HashMap<>();
public Grammer(int port) throws IOException {
super(port);
map.put("猫","cat");
map.put("狗","dog");
map.put("鱼","fish");
}
@Override
public String process1(String str) {
return map.get(str);
}
}
public class Test7 {
public static void main(String[] args) throws IOException {
Grammer grammer =new Grammer(9090);
grammer.start();
}
}