认识socket
UDP和TCP
认识UDPAPI有关方法
基于UDP实现回显服务器
Socket套接字,是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。基于Socket套接字的网络程序开发就是网络编程
通俗一点来说也就是我们编写网络程序,主要写的是应用层代码,要发送数据的时候,需要上层协议调用下层协议,应用层调用传输层,传输层给应用层提供一组API,就叫做socketAPI
Java给我们提供了两组socketAPI,一个是基于UDP的API,一个是基于TCP的API,这俩有很大差别,我们来对比一下
UDP特点:
无连接,不可靠传输,面向数据报,全双工
TCP特点:
有连接,可靠传输,面向字节流,全双工
下面来具体解释一下什么是连接
这个问题比较抽象了,理解为通信双方,各自记录了对方的信息,就像结婚,通过一个结婚证,男方知道女方是老婆,女方知道男方是老公(相当于做了个记录,知道彼此是另一半)
无连接就是不需要刻意保存对方的相关信息
有连接就是需要刻意保存对端的信息
不可靠传输就是我的消息发了就发了,我不关心结果,爱咋咋地
可靠传输就是我的消息发出去,我尽可能发过去,关注结果
面向数据报就是在数据传输的时候,以一个UDP数据报为基本单位
面向字节流就是数据传输的时候以字节为基本单位,读写灵活
全双工就是一条路径,双向通信
举个例子,这个马路是双向通行的,那么就是全双工的
与之对应的就是半双工,就是只允许一方通行,就是单向通信
UDP的方法
下面我们来认识一下UDP API的方法
1.DatagramSocket API
此类表示用于发送和接收数据报数据包的套接字。
DatagramSocket()
构造数据报套接字并将其绑定到本地主机上的任何可用端口。(一般来说用于客户端)
DatagramSocket(int port)
构造数据报套接字并将其绑定到本地主机上的指定端口。 (一般来说用于服务器)
void close()
关闭此数据报套接字 (socket也是文件,用完也需要关闭,不然会资源泄漏)
void receive(DatagramPacket p)
从此套接字接收数据报包。
void send(DatagramPacket p)
从此套接字发送数据报包。
DatagramPacket API
该类表示数据报包。
数据报包用于实现无连接分组传送服务。 仅基于该数据包中包含的信息,每个消息从一台机器路由到另一台机器。 从一台机器发送到另一台机器的多个分组可能会有不同的路由,并且可能以任何顺序到达。 包传送不能保证
DatagramPacket(byte[] buf, int length)
构造一个 DatagramPacket用于接收长度的数据包 length 。
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
构造用于发送长度的分组的数据报包 length指定主机上到指定的端口号
DatagramPacket(byte[] buf, int offset, int length)
构造一个 DatagramPacket用于接收长度的分组 length ,指定偏移到缓冲器中
InetAddress getAddress()
返回该数据报发送或接收数据报的计算机的IP地址
int getPort()
返回发送数据报的远程主机上的端口号,或从中接收数据报的端口号
byte[] getData()
返回数据缓冲区
基于UDP实现回显程序
//服务器
package network; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException; public class UdpEchoServer { //先定义一个scoket对象 //通过网络通信, 必须要使用 socket 对象. private DatagramSocket socket=null; //绑定一个端口,不一定能成功 //如果某个端口已经被别的进程占用了.这里的绑定操作就会出错 //同一个主机上,一个端口,同一时刻,只能被一个进程绑定 public UdpEchoServer(int port) throws SocketException { //构造socket的同时,指定要关联/绑定的端口 socket=new DatagramSocket(port); } //启动服务端的主逻辑 public void start() throws IOException { System.out.println("服务器启动"); while(true){ //每次循环,每次要做三件事情 //1.读取请求并解析 // 构造空饭盒 DatagramPacket requestPacket=new DatagramPacket(new byte[4096],4096); //食堂大妈给饭盒盛饭(饭从网卡上来的) socket.receive(requestPacket); //为了方便处理这个请求,将数据包转为String String request=new String(requestPacket.getData(),0,requestPacket.getLength()); //2.根据请求计算响应(因为这个是个回显器,所以不用计算响应) String response=process(request); //3.把响应写回到客户端 //根据response字符串,构造一个DatagramPacket //和requestPacket不同,此处构造响应的时候,需要指定这个包发给谁 DatagramPacket responsePacket=new DatagramPacket(response.getBytes(),response.getBytes().length, // requestPacket 是从客户端这里收来的. getSocketAddress 就会得到客户端的 ip 和 端口 requestPacket.getSocketAddress()); socket.send(responsePacket); 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 udpEchoServer=new UdpEchoServer(9090); udpEchoServer.start(); } }
//客户端
package network; 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 { private Socket socket=null; public TCPEchoClient(String serverIp,int port) throws IOException { //这个操作相当于让客户端和服务器建立tcp连接 //这里的连接连上了,服务器的accept就会返回 socket=new Socket(serverIp,port); } public void start(){ Scanner scanner=new Scanner(System.in); try(InputStream inputStream=socket.getInputStream(); OutputStream outputStream=socket.getOutputStream()){ PrintWriter printWriter=new PrintWriter(outputStream); Scanner scannerFromSocket=new Scanner(inputStream); while(true){ //1.从键盘上读取用户输入的内容 System.out.print("->"); String request=scanner.next(); //2.把读取的内容构造成请求,发送给服务器 //注意,这里的发送,是带有换行的 printWriter.println(request); //3.从服务器读取响应 String response=scannerFromSocket.next(); //4.把响应结果显示到控制台上 System.out.printf("req: %S; resp: %s\n",request,response ); } } catch (IOException e) { throw new RuntimeException(e); } } public static void main(String[] args) throws IOException { TCPEchoClient tcpEchoClient=new TCPEchoClient("127.0.0.1",9090); tcpEchoClient.start(); } }
现在来分析一下
来分析一下执行思路
1.服务器先启动,执行到receive进行阻塞
2.客户端运行之后,从控制台读取数据,并进行send
//走到这一步,客户端和服务器一起往下执行,所以会有两个3和两个4
3.客户端这边,send之后,继续往下走,走到receive读取响应,会阻塞等待
3.服务器这边,就从receive返回,读到数据(客户端发来的),往下走到process生成响应,到send,打印日志
4.客户端这边收到服务器send回来的数据后就会解除阻塞,执行下面的打印操作
4.服务器这边,进行下一轮循环,再次阻塞在receive那里,等到客户端发来的请求
5.客户端继续进入下一轮循环,阻塞在Scanner.next这里.等待用户输入数据
以上的流程是所有客户端和服务器交互的流程,比较复杂,需要理解!
socket可以实现跨主机通信!
这个点我们以后再说
今天的讲解到此结束,我们下期再见