一 Socket 套接字
1.1 概念
Socket 套接字,是由系统提供用于网络通信的技术,是基于TCP/IP 协议的网络通信的基本操作单元。基于 Socket套接字的网络程序开发就是网络编程。
1.2 分类
流套接字:使用传输层 TCP 协议
TCP,即 Transmission Control Protol (传输控制协议),传输层协议。
特点:
• 有连接(通信双方需要刻意保留对方的信息):打电话就是有连接,要想打电话,得先有对方的号码。
• 可靠传输
• 面向字节流:以字节为传输的基本单位,读写非常灵活
• 由接收缓冲区,也有发送缓冲区
• 全双工:一条路径,双向通信。即两端可以同时收发消息。(半双工:一端发消息,另一端只能接收消息)。
• 大小不限
数据报套接字:使用传输层 UDP 协议
UDP,即 User Datagram Protocol (用户数据报协议),传输层协议。
特点:
• 无连接(通信双方不需要刻意保留对方的消息): 发短信,发送验证码时并不需要知道对方的号码,可以直接发送消息。
• 不可靠传输
• 面向数据报:以一个 UDP 数据报为基本单位。
• 有接收缓冲区
• 全双工
• 大小有限:一次最多传输64k
二. UDP 数据报套接字编程
2.1 DatagramSocket API
DatagramSocket 是UDP Socket, 用于发送和接收UDP数据报.
DatagramSocket 构造方法:
DatagramSocket 方法:
2.2 DatagramPacket API
DatagramPacket API 是UDP Socket 发送和接收的数据报.
DatagramPacket 构造方法:
DatagramPacket 方法:
2.3 UDP 回显代码示例:
UDP 客户端
public class UDPEchoClient { private String SeverIP; private int SeverPort; private DatagramSocket socket=null; public UDPEchoClient(int SeverPort,String SeverIP) throws SocketException { socket=new DatagramSocket(); this.SeverPort=SeverPort; this.SeverIP=SeverIP; } public void start() throws IOException { Scanner scanner=new Scanner(System.in); while (true){ System.out.print("->"); String request=scanner.next(); DatagramPacket requestPacket=new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(SeverIP),SeverPort); socket.send(requestPacket); DatagramPacket responsePacket=new DatagramPacket(new byte[4096],4096); socket.receive(responsePacket); String response=new String(responsePacket.getData(),0,responsePacket.getLength()); System.out.printf("req: %s,resp: %s\n",request,response); } } public static void main(String[] args) throws IOException { // UDPEchoClient udpEchoClient=new UDPEchoClient(9090,"127.0.0.1"); UDPEchoClient udpEchoClient=new UDPEchoClient(9090,"114.115.149.19"); udpEchoClient.start(); } }
UDP 服务器端
public class UDPEchoSever { //定义一个socket 对象,通过网络通信,必须要使用socket 对象. private DatagramSocket socket=null; //绑定一个端口,不一定能成功 //如果某个端口已经被别的进程占用了,此时这里的绑定操作就会出错 //同一个主机上,一个端口,同一时刻,只能被一个进程绑定 public UDPEchoSever(int port) throws SocketException { socket=new DatagramSocket(port); } //启动服务器 public void start() throws IOException { System.out.println("服务器启动"); while (true) { //每次循环做三件事: //1.读取请求并解析,构造空饭盒(构造DatagramPacket对象获取数据报) DatagramPacket requestSocket=new DatagramPacket(new byte[4096],4096); //食堂大妈给饭盒里盛饭(将数据报中的内容全部放进上述构造的DatagramPacket对象中) socket.receive(requestSocket); //为了方便处理这个请求,把数据报转成String String request=new String(requestSocket.getData(),0, requestSocket.getLength()); //2.根据请求计算响应(此处省略了这个步骤,没有明确的业务逻辑,直接返回了原内容) String response=process(request); //3.把响应结果写回到客户端 // 格局response 字符串,构造一个DatagramPacket // 和请求 packet 不同,此处构造响应的时候,需要指定这个包发给谁 DatagramPacket responseSocket=new DatagramPacket(response.getBytes(),response.getBytes().length,requestSocket.getSocketAddress()); socket.send(responseSocket); System.out.printf("[%s:%d]:req: %s,resp: %s\n",requestSocket.getAddress().toString(),requestSocket.getPort(),request,response); } } //这个方法希望根据请求计算响应,如果后续写个别的服务器,不再回显了,而是有具体的业务了,就可以修改 process方法,根据需要重新构造响应. private String process(String request) { return request; } public static void main(String[] args) throws IOException { UDPEchoSever udpEchoSever=new UDPEchoSever(9090); udpEchoSever.start(); } }
简单的单词查询服务器
public class UdpDictServer extends UDPEchoSever{ private Map<String,String> map=new HashMap<>(); public UdpDictServer(int port) throws SocketException { super(port); map.put("小猫","cat"); map.put("小狗","dog"); map.put("小猪","pig"); } @Override public String process(String request) { return map.getOrDefault(request,"不包含要查找的单词!!"); } public static void main(String[] args) throws IOException { UdpDictServer udpDictServer=new UdpDictServer(9090); udpDictServer.start(); } }
三. TCP 流套接字
3.1 ServerSocket API
ServerSocket 是创建TCP服务端Socket 的API.
3.2 Socket API
Socket 是客户端Socket, 或服务端中接收到客户端建立连接(accept 方法)的请求后,返回的服务端Socket.不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,即用来与对方收发数据的.
3.3 TCP 回显代码:
服务器端:
package demo; 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; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * Created with IntelliJ IDEA. * Description: * User: zhao3 * Date: 2023-11-02 * Time: 11:07 */ public class TCPEchoServer { ServerSocket socket=null; public TCPEchoServer(int port) throws IOException { socket=new ServerSocket(port); } public void start() throws IOException { //无法同时处理多个客户的请求:如果直接调用,processConnection方法会影响这个循环的二次执行,导致 accept不及时. 法 /*while (true){ Socket clientSocket=socket.accept(); processConnection(clientSocket); }*/ //创建线程池,可以同时处理多个用户的请求 ExecutorService pool= Executors.newCachedThreadPool(); while (true){ Socket clientSocket=socket.accept(); pool.submit(new Runnable() { @Override public void run() { try { processConnection(clientSocket); } catch (IOException e) { throw new RuntimeException(e); } } }); } } private void processConnection(Socket clientSocket) throws IOException { try (InputStream inputStream=clientSocket.getInputStream(); OutputStream outputStream=clientSocket.getOutputStream()){ //套上一个套接字,将字节流改为字符流 PrintWriter printWriter=new PrintWriter(outputStream); Scanner scanner=new Scanner(inputStream); while (true){ //将字节流转换成字符流,每个请求用'\n'来 分割 if(!scanner.hasNext()){ //请求结束 break; } String request=scanner.next(); String response=process(request); printWriter.println(response); printWriter.flush(); System.out.printf("[%s,%d]:req:%s,res:%s\n",clientSocket.getInetAddress().toString(),clientSocket.getPort(),request,response); } } catch (IOException e) { throw new RuntimeException(e); } finally { clientSocket.close(); } } private String process(String request) { return request; } public static void main(String[] args) throws IOException { TCPEchoServer tcpEchoServer=new TCPEchoServer(9090); tcpEchoServer.start(); } }
客户端:
package demo; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.net.Socket; import java.util.Scanner; /** * Created with IntelliJ IDEA. * Description: * User: zhao3 * Date: 2023-11-02 * Time: 11:07 */ public class TCPEchoClient { Socket socket=null; public TCPEchoClient(String post, int port) throws IOException { socket=new Socket(post,port); } public void start() throws IOException { Scanner scanner=new Scanner(System.in); try (InputStream inputStream=socket.getInputStream(); OutputStream outputStream= socket.getOutputStream()){ Scanner scannerFromSocket=new Scanner(inputStream); PrintWriter printWriter=new PrintWriter(outputStream); while (true){ System.out.print("->"); String request=scanner.next(); printWriter.println(request); printWriter.flush(); String response= scannerFromSocket.next(); System.out.printf("req:%s,res:%s\n",request,response); } } catch (IOException e) { e.printStackTrace(); }finally { socket.close(); } } public static void main(String[] args) throws IOException { TCPEchoClient tcpEchoClient=new TCPEchoClient("127.0.0.1",9090); tcpEchoClient.start(); } }