udp
DatagramSocket
用于接收和发送udp数据报
构造方法:
- DatagramSocket():创建一个UDP数据报套接字的Socket,绑定到本地上 一个随机可用端口上,一般用于客户端
- DatagramSocket(int port):创建一个UDP数据报套接字的Socket,绑定到指定端口上,一般适用于服务器端
DatagramSocket方法:
- void receive(DatagramPacket p):接收套接字数据报p,如果此时还没有发送,便会阻塞
- void send(DatagramPacket p):发送套接字数据报p,不会阻塞,直接发送
- void close():关闭此数据报套接字
DatagramPacket:
udp发送和接收的数据报形式
构造方法:
- DatagramPacket(byte[] buf,int length):构造一个DatagramPacket对象来接收发送来到数据报,将数据内容放在第一个字节数组中,第二个参数是指定接收内容的长度。(用于接收时)
- DatagramPacket(byte[] buf,int offset,int length,SocketAddress address):接收发来的数据报,并将内容放进buf数组中,选中需要截取内容的索引[offset,length)。address:指定目的主机的ip和端口号。(用于发送时)
DatagramPacket方法:
- getSocketAddress():获取发送端的ip地址。
- getPort():获取发送端的端口
- byte[] getData():获取数据报中的内容
udp回显服务器实现:
服务器端:
import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException; import java.nio.charset.StandardCharsets; public class udpServer { //服务器端 private DatagramSocket socket = null; public udpServer(int port) throws SocketException { socket = new DatagramSocket(port); } public void start() throws IOException { //System.out.println(socket.getPort()); //returns the port to which the socket is connected 未连接套接字时 返回-1 //如果想得到服务器端的端口 要使用socket.getLocalPort() System.out.println("服务器启动"); //构建一个DatagramPacket来接收客户端发来的消息 while(true){ //创建一个DatagramPacket填充对象,用来接收从客户端发送来的数据报 DatagramPacket reqPacket = new DatagramPacket(new byte[4096],4096); //如果没有数据报发送,receive发送会阻塞等待 socket.receive(reqPacket); //转换成我们看得懂的字符串形式 ----- 客户端发送来的请求 String request = new String(reqPacket.getData(),0,reqPacket.getLength()); //服务器端做出响应 String res = RESPONSE(request); //构建一个DatagramPacket将响应后的结果发送回客户端 //注意需要有客户端socketAddress DatagramPacket respPacket = new DatagramPacket(res.getBytes(StandardCharsets.UTF_8),0,res.getBytes().length, reqPacket.getSocketAddress()); //发送数据报 socket.send(respPacket); System.out.println("服务器端口:"+socket.getLocalPort()+": 客户端[ip:"+respPacket.getAddress()+" 端口:"+reqPacket.getPort() +"] req:"+request+" res:"+res); } } public String RESPONSE(String req){ return req; } public static void main(String[] args) throws IOException { udpServer server = new udpServer(8080); server.start(); } }
值得注意的是:DatagramSocket的getPort/getAddress方法,返回的都是连接方的端口和ip,并不是本地的。而DatagreamPacket的getPort/getAddress方法,返回的都是本地的端口和ip地址。如果你还没有连接成功时,打印socket.getPort(),虽然这时候你已经给出了指定端口,但这个方法的返回值是连接端的,还没连接时返回-1
客户端
import java.io.IOException; import java.net.*; import java.nio.charset.StandardCharsets; import java.util.Scanner; public class udpClient { //目的端ip private String severIp; //目的端端口 private int severPort; private DatagramSocket socket; public udpClient(String severIp,int severPort) throws SocketException { socket = new DatagramSocket(); this.severIp = severIp; this.severPort = severPort; } public void start() throws IOException { Scanner sc = new Scanner(System.in); System.out.println("客户端启动"); while(true){ System.out.print(">>"); String request = sc.nextLine(); if(request.equals("exit")){ System.out.println("退出"); socket.close(); return; } //给出消息发出的目的端ip和端口 (服务器ip端口) DatagramPacket reqPacket = new DatagramPacket(request.getBytes(StandardCharsets.UTF_8),request.getBytes().length ,InetAddress.getByName(severIp),severPort); //发送 socket.send(reqPacket); //接收响应 DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096); socket.receive(responsePacket); //转换成字符串形式 String res = new String(responsePacket.getData(),0, responsePacket.getLength()); //打印响应 System.out.println(res); } } public static void main(String[] args) throws IOException { udpClient client = new udpClient("127.0.0.1",8080); client.start(); } }
注意:当传服务器端的ip地址时,此时我们这里给的是一个字符串,但参数接收的是一个32位整数形式,所以我们需使用InetAddress.getName(String hostage)做一下转换
tcp
SeverSocket
服务器端Socket API
构造方法:
- SeverSccket(int port):创建一个服务器端流套接字Socket,并绑定到指定端口
SeverSocket的方法:
- Socket accept():监听指定端口(创建时绑定的端口),如果有客户端连接后,返回一个服务端Socket对象,否则阻塞等待
- void close():关闭套接字
Socket
客户端Socket,或服务器端的accept方法收到有客户端连接后返回的服务端Socket
无论是客户端还是服务器端,都是连接建立后,保存对端的信息
构造方法:
Socket(String severIp,int severPort):创建一个客户端Socket,与对应的主机端口建立连接
Socket的方法:
- InetAddress():返回套接字所连接的地址
- InputStream getInputStream():
- OutputStream getOutStream():
ps:tcp是面向字节流传输的
tcp回显服务器实现
服务器端
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; public class tcpSever { private ServerSocket socket; public tcpSever(int port) throws IOException { socket = new ServerSocket(port); } public void start() throws IOException { System.out.println("服务器启动"); //与服务器建立连接 返回一个服务器端Socket Socket clickClient = socket.accept(); processConnection(clickClient); } public void processConnection(Socket clientSocket){ System.out.println("客户端上线[ip:"+clientSocket.getInetAddress().toString()+" port:"+clientSocket.getPort()+"]"); try(InputStream inputStream = clientSocket.getInputStream(); OutputStream outputStream = clientSocket.getOutputStream()) { while(true){ //读取结果 //输入流 会等待客户端输入东西 阻塞在这里 (通过Scanner的方法都会造成阻塞) Scanner sc = new Scanner(inputStream); if(!sc.hasNext()){ //没有数据了 断开连接 System.out.println("客户端下线[ip:"+clientSocket.getInetAddress()+" port:"+clientSocket.getPort()); socket.close(); break; } //遇到换行/空白结束 String req = sc.next(); //服务器响应 String resp = process(req); //OutputStream没有写String的方法 //1.将其转换成字节数组 //2.使用PrintWriter-----打印流-----字符打印流/字节打印流 PrintWriter printWriter = new PrintWriter(outputStream); printWriter.println(resp); printWriter.flush(); System.out.println("客户端:[req:"+req+" resp:"+resp+" ip:"+clientSocket.getInetAddress()+" port:"+clientSocket.getPort()+"]"); } } catch (IOException e) { e.printStackTrace(); } } public String process(String req){ return req; } public static void main(String[] args) throws IOException { tcpSever sever = new tcpSever(8080); sever.start(); } }
客户端
import java.io.IOException; import java.net.*; import java.nio.charset.StandardCharsets; import java.util.Scanner; public class udpClient { //目的端ip private String severIp; //目的端端口 private int severPort; private DatagramSocket socket; public udpClient(String severIp,int severPort) throws SocketException { socket = new DatagramSocket(); this.severIp = severIp; this.severPort = severPort; } public void start() throws IOException { Scanner sc = new Scanner(System.in); System.out.println("客户端启动"); while(true){ System.out.print(">>"); String request = sc.nextLine(); if(request.equals("exit")){ System.out.println("退出"); socket.close(); return; } //给出消息发出的目的端ip和端口 (服务器ip端口) DatagramPacket reqPacket = new DatagramPacket(request.getBytes(StandardCharsets.UTF_8),request.getBytes().length ,InetAddress.getByName(severIp),severPort); //发送 socket.send(reqPacket); //接收响应 DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096); socket.receive(responsePacket); //转换成字符串形式 String res = new String(responsePacket.getData(),0, responsePacket.getLength()); //打印响应 System.out.println(res); } } public static void main(String[] args) throws IOException { udpClient client = new udpClient("127.0.0.1",8080); client.start(); } }
注意:
- 在tcp的Socket中,ip地址可以直接传字符串类型的,不需要像udpSocket那样转成32位整数形式
- 我们读通过next()读,写通过println写。这是设计好了的。使换行符变成我们的消息隔断符,使我们知道每一段消息的头和尾,解决粘包问题
- 在运行tcp回显服务器时,必须先开服务器端,再开客户端,因为如果先开客户端的话,此时没有服务器与之相连,会抛异常------对应的即为tcp的是有连接的