ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

网络编程套接字(socket)

网络编程套接字(socket) 套接字什么是socket所谓的套接字也就是socket这个socket和我们编写网络程序又有什么关系呢socket是传输层给应用层提供的API的集合socket就相当于是程序使用网络的接口而网卡是把数据发出去的硬件socket就相当于是网卡的一个遥控器我们网络编程主要涉及的就是传输层以及应用层的交互我们先决定传输层的哪一个协议是UDP还是TCP也就是上文所介绍的两种后续的各种层是操作系统根据我们代码中的任务来选择的发送数据的过程Java 程序↓Java Socket 对象↓文件描述符 fd↓进程的文件描述符表↓操作系统内核中的 Socket 对象↓TCP / IP 协议栈↓网卡↓网络为什么会有文件描述符表明明我们是讲socket为什么会有一个文件描述符表呢原因就是在Linux中很多东西都被抽象成了“可以像文件一样读写的资源”所以他们统一通过文件描述符表fd来管理但是我们学习的并不是linux操作系统啊为什么会有什么个说法因为在基本上所有的操作系统中都有这么一个概念都会让程序拿到一个编号然后通过这个编号来找到底层系统中的操作以及方法只不过在linux里面叫做文件描述符表所以我们可以统一叫做这个来理解还有就是为什么我们不能直接通过Java代码来操作一切因为网络、文件、进程这些资源都属于操作系统的重要资源不能让普通程序随便直接改所以需要通过一个像文件描述符表的东西进行调度调度到底层的操作系统的socketsocket的组成Socket网络通信接口大概念│├── TCP│ ├── Socket│ └── ServerSocket│└── UDP├── DatagramSocket└── DatagramPacketUDPDatagramSocket 通信工具DatagramPacket 要传输的数据包DatagramSocket构造方法作用常见场景DatagramSocket()创建一个 UDP Socket端口由系统自动分配客户端常用DatagramSocket(int port)创建 UDP Socket并绑定指定端口服务器常用DatagramSocket(int port, InetAddress laddr)绑定指定端口和本机 IP一台机器有多个网卡/IP 时使用方法作用send(DatagramPacket p)发送 UDP 数据包receive(DatagramPacket p)接收 UDP 数据并保存到p中close()关闭 Socket释放网络资源getLocalPort()获取当前 Socket 绑定的本地端口getLocalAddress()获取当前 Socket 绑定的本地 IPisClosed()判断 Socket 是否已经关闭DatagramPacket1接收数据new DatagramPacket(byte[] buf, int length)参数作用buf用来存放收到的数据length最多允许接收多少字节2发送数据new DatagramPacket(byte[] buf,int length,InetAddress address,int port)参数作用buf要发送的数据length要发送多少字节address对方的 IP 地址port对方的端口号回显服务器例子注意一为什么服务器要指定端口而客户端不需要因为一台服务器是用来服务很多的客户端的指定端口是让客户端知道并且只传入到那一个端口里面去但是客户端有很多就得系统随机并且有些同学很容易将一个概念混淆就是觉得客户端的端口和服务器的端口得一致才能对接上实际是不是的这里也会涉及前文讲到的五原组我们只要明确了这五个就行不需要一致不是说需要五元组吗那为什么服务器里面好像少了一个自己的ip因为系统会根据客户端的数据进行匹配比如客户端发送目标 IP 192.168.1.10目标端口 9090协议 UDP到我们电脑之后系统看到的信息就是目的 IP192.168.1.10目的端口9090协议UDP假设你服务器中写到new DatagramSocket(9090);也就相当于是监听所有本机 IP 的 UDP 9090然后进行匹配最终就可以得出服务器自己的ip所以说五元组往往是客户端和服务器两边的信息共同组成的注意二局域网 ≈ 一个小区IP ≈ 楼号 / 房屋地址端口 ≈ 房间号所以为什么会出现端口号冲突也就是我们现在写的程序是在自己的电脑上面所以ip都是127.0.0.1所以如果服务器和客户端的端口号也一致的情况下就会产生端口号冲突也就是走到了一个小区的一个楼层中的一个房间那我们电脑上有那么多的应用程序为什么不会造成冲突呢因为系统会给客户端的端口自动分配所以我们程序里面的客户端的代码也没有指定端口号简单介绍一下电脑里的ip不同 IP 的来源不一样127.0.0.1操作系统按照网络协议标准预留的本机固定使用。192.168.x.x这种局域网 IP一般是你家路由器通过 DHCP 自动分配给电脑。公网 IP通常由运营商分配给你的路由器/网络出口。VPN 的 IP由 VPN 软件或 VPN 服务器分配。所以我们的应用程序的ip都是通过操作系统自己分配的比如你点开来一个网站系统会识别这个请求是从WiFi网卡发出去的所以会分配192开头的那个具体代码package network; //服务器 import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.nio.charset.StandardCharsets; public class UdpEchoServer { //首先创建一个socket对象 DatagramSocket serverSocketnull; //通过构造方法来完整 //创建一个服务器对象需要指定端口 public UdpEchoServer(int port) throws Exception{ serverSocketnew DatagramSocket(port); } //开始方法 public void start() throws IOException { System.out.println(服务器启动了); while (true) { //先得创建一个datagrampacket对象这个对象是传输数据的基本单位就相当于你赶海一人拿一个桶是一样的 DatagramPacket requestPacket new DatagramPacket(new byte[1024], 1024); //接收请求 serverSocket.receive(requestPacket); //将请求转为字符串 String request new String(requestPacket.getData(), 0, requestPacket.getLength()); //根据请求计算响应 String response process(request); //将响应封装到datagrampacket对象中,并且返回 DatagramPacket responsePacket new DatagramPacket(response.getBytes(), response.getBytes().length, requestPacket.getSocketAddress()); //requestPacket.getSocketAddress()不仅拿到了ip也拿到了端口号 serverSocket.send(responsePacket); //打印日志 System.out.printf(客户端:%s 请求:%s 响应:%s\n, requestPacket.getSocketAddress(), request, response); } } private String process(String request) { //因为回显所以就原路返回就行 return request; } public static void main(String[] args) throws Exception { UdpEchoServer udpEchoServernew UdpEchoServer(9090); udpEchoServer.start(); } }package network; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; import java.util.Scanner; //客户端 public class UdpEchoClient { //也是先创建一个socket对象 DatagramSocket clientSocketnull; //需要给定服务器的ip以及端口 private String serverIp; private int serverPort; public UdpEchoClient(String serverIp,int serverPort) throws SocketException { clientSocketnew DatagramSocket(); this.serverIpserverIp; this.serverPortserverPort; } public void start() throws Exception{ while(true){ Scanner scannernew Scanner(System.in); System.out.println(请输入要发送的内容); String requestscanner.nextLine(); if (request.equals(exit)){ break; } DatagramPacket requestPacketnew DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(serverIp),serverPort); clientSocket.send(requestPacket); DatagramPacket responsePacketnew DatagramPacket(new byte[1024],1024); clientSocket.receive(responsePacket); //将服务器返回的数据打印出来 String response new String(responsePacket.getData(), 0, responsePacket.getLength()); // 4. 显示响应. System.out.println(response); } } public static void main(String[] args) throws Exception { UdpEchoClient udpEchoClientnew UdpEchoClient(127.0.0.1,9090); udpEchoClient.start(); } }注意三别人要访问我的服务器本机测试用 127.0.0.1想让互联网中的别人访问通常需要“公网 IP 开放端口”云服务器就是最常见的实现方式。127.0.0.1表示当前这台电脑自己。查字典案例我们刚刚通过一个回显的案例来初步了解了一下UDP的构造以及相关socket的api那我们现在再用一个查字典的案例来进一步了解一下注意一服务器以及客户端的主要流程服务器大体在while循环里面第一步读取请求并解析因为我们这里是需要返回一个字符串所以转为字符串格式第二步根据请求计算相应这里以回显为例子所以只需要原路返回就行第三步把相应返回给客户端客户端大体第一步在控制台读取输入第二步构造请求发送给服务器第三步接收服务器发来的响应第四步根据返回来的响应进行显示结果所以我们可以总结一下我们写服务器以及客户端只需要跟着上面的步骤在发送和接收的时候来书写对的DatagramPacket对象使用正确的构造方法注意二为什么可以这么写因为其实现在市面的大部分背单词软件都是这么写的我们也可以上网复制中英文对应关系来使我们的回答更加全面具体代码package network; import java.io.IOException; import java.net.DatagramSocket; import java.util.HashMap; import java.util.Map; public class UdpDictServer extends UdpEchoServer{ //因为我们是一个查字典所以我们用到map来根据key来查找values MapString,String mapnew HashMapString, String(); //重写方法 public UdpDictServer(int port) throws Exception { super(port); //在这里传入对应的中英文关系 map.put(apple,苹果); map.put(banana,香蕉); map.put(cat,猫); map.put(dog,狗); } Override public String process(String request) { //request 是客户端传来的查询内容是 keygetOrDefault() 返回的是这个 key 对应的 value。 return map.getOrDefault(request,查无此词); } public static void main(String[] args) throws Exception { UdpDictServer server new UdpDictServer(9090); server.start(); } }因为这里的客户端和前面的回显服务器一样都是发送包装成String然后发送回去所以我们还是用的之前的客户端TCPServerSocket和Socket客户端 服务器Socket││ 请求连接├──────────────────────────→ ServerSocket│ ││ accept()│ ││ ↓│ Socket│ │└──────────── TCP连接 ────────────┘整体的关系差不多就是这样serversocket负责将将请求引进家门处理客户端的连接所以只会在服务器中写到而socket像是专门负责业务的专员在客户端以及服务器里都会用到回显服务器例子TCP的短长连接短连接就是连接一次通信完就断需要多次进行连接和断开会影响效率但是不会一直占着服务器资源长连接连接一次可以通信很多次之后关闭就像我们程序中写到的while循环不用老是创建和关闭所以效率会高但是会长时间占着资源可能一直占着一个线程注意一accept建立连接因为在我们没有写多线程的时候就只有一个socket服务员也就是只能一个线程在进行服务后面的就得等着所以我们通过多线程的形式可以实现多客户端注意二flush在我们用printwriter这个内置了缓冲区进行写入的时候会先将数据写进缓冲区里数据满了之后才会将数据传输出去所以我们得写flush写入一些数据就将他们传输过去TCP和UDP的有无连接TCP的Socket socket serverSocket.accept()这个方法会进行连接也就是会拿到这个socket的信息并且记录下来所以后续就会根据这个信息给特定的socket进行交互但是udp不会记录对方socket信息注意三具体代码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 { Socket socketnull; TcpEchoClient(String serverName,int serverPort) throws IOException { socketnew Socket(serverName,serverPort); } public void start() throws IOException { System.out.println(client start!); //依旧是先创建字节流的输入输出来处理数据 try (InputStream inputStream socket.getInputStream(); OutputStream outputStream socket.getOutputStream()) { Scanner scanner new Scanner(System.in); Scanner scannerNetwork new Scanner(inputStream); PrintWriter printWriter new PrintWriter(outputStream); while (true) { //先读取数据 System.out.println(client:请输入数据); String request scanner.next(); //再将读取的数据传给服务器 printWriter.println(request); printWriter.flush(); //最后读取服务器的相应 if (!scannerNetwork.hasNext()) { break; } String response scannerNetwork.next(); //最后打印出来 System.out.println(server: response); } }catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) throws IOException { TcpEchoClient client new TcpEchoClient(127.0.0.1, 9090); client.start(); } }package network; 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; public class TcpEchoServer { //先创建一个ServerSocket对象 ServerSocket serverSocket null; public TcpEchoServer(int port) throws Exception { serverSocket new ServerSocket(port); } //开始方法 public void start() throws IOException { //先建立连接然后由Socket进行处理 //注意一 ExecutorService executorService Executors.newCachedThreadPool(); while (true) { Socket socket serverSocket.accept(); executorService.submit(() - { try { processConnection(socket); } catch (IOException e) { throw new RuntimeException(e); } }); } } private void processConnection(Socket socket) throws IOException { System.out.printf([%s:%d] 客户端上线!\n, socket.getInetAddress(), socket.getPort()); //tcp是面向字节流的 try (InputStream inputStream socket.getInputStream(); OutputStream outputStream socket.getOutputStream()) { //因为tcp是面向字节流的所以我们定义了InputStream和OutputStream但是我们输入是一个字符串所以我们会 //继续用到scanner来读和PrintWriter来写并且PrintWriter可以将我们输入的字符串转为字节传输出去 Scanner scanner new Scanner(inputStream); PrintWriter printWriter new PrintWriter(outputStream); while (true) { if (!scanner.hasNext()) { // 客户端关闭了连接 System.out.printf([%s:%d] 客户端下线!\n, socket.getInetAddress(), socket.getPort()); // 假设这里有一系列的逻辑呢?? break; } //先接收数据 String request scanner.next(); //再根据请求计算相应 String response process(request); //最后发送相应给客户端 printWriter.println(response); //注意二 printWriter.flush(); System.out.printf([%s:%d] 客户端请求: %s\n, socket.getInetAddress(), socket.getPort(), request); } } catch (Exception e) { e.printStackTrace(); } finally { socket.close(); } } public String process(String request) { return request; } public static void main(String[] args) throws Exception { TcpEchoServer tcpEchoServer new TcpEchoServer(9090); tcpEchoServer.start(); } }查字典案例package network; import java.util.HashMap; import java.util.Map; public class TcpDictServer extends TcpEchoServer{ MapString,String mapnew HashMapString, String(); public TcpDictServer(int port) throws Exception { super(port); map.put(apple,苹果); map.put(banana,香蕉); map.put(cat,猫); map.put(dog,狗); } Override public String process(String request) { //request 是客户端传来的查询内容是 keygetOrDefault() 返回的是这个 key 对应的 value。 return map.getOrDefault(request,查无此词); } public static void main(String[] args) throws Exception { TcpDictServer server new TcpDictServer(9090); server.start(); } }
返回列表