网站建设 事业单位 安全,专业东莞网站制作公司,温州网络学堂,湖北企业建站系统信息网络编程 Socket套接字概念分类Java数据报套接字通信模型一次发送和接受UDP数据报提供多个客户端的请求处理及响应 Java流套接字通信模型Socket编程注意事项 UDP数据报套接字编程DatagramSocket API构造方法普通方法 DatagramPacket API构造方法普通方法 InetSocketAddress API… 网络编程 Socket套接字概念分类Java数据报套接字通信模型一次发送和接受UDP数据报提供多个客户端的请求处理及响应 Java流套接字通信模型Socket编程注意事项 UDP数据报套接字编程DatagramSocket API构造方法普通方法 DatagramPacket API构造方法普通方法 InetSocketAddress API实现回显UDP客户端和服务器 TCP流套接字编程ServerSocket API构造方法普通方法 Socket API构造方法普通方法TCP中的长短连接 实现回显TCP客户端和服务器 Socket套接字
概念
Socket套接字是由系统提供用于网络通信的技术是基于TCP/IP协议的网络通信的基本操作单元。基 于Socket套接字的网络程序开发就是网络编程
分类
流套接字:使用传输层TCP协议 特点 有连接,可靠传输,面向字节流,有接收缓冲区也有发送缓冲区,大小不限 对于字节流来说可以简单的理解为传输数据是基于IO流流式数据的特征就是在IO流没有关闭的情 况下是无边界的数据可以多次发送也可以分开多次接收。
数据报套接字:使用传输层UDP协议 UDP即User Datagram Protocol用户数据报协议传输层协议。 特点 无连接,不可靠传输,面向数据报,有接收缓冲区,无发送缓冲区,大小受限,一次最多64k 对于数据报来说可以简单的理解为传输数据是一块一块的发送一块数据假如100个字节必须一 次发送接收也必须一次接收100个字节而不能分100次每次接收1个字节。
Java数据报套接字通信模型
对于UDP协议来说具有无连接面向数据报的特征即每次都是没有建立连接并且一次发送全部数 据报一次接收全部的数据报。
java中使用UDP协议通信主要基于 DatagramSocket 类来创建数据报套接字并使用 DatagramPacket 作为发送或接收的UDP数据报。
一次发送和接受UDP数据报
一次UDP数据报的发送区分发送端和接收端 发送端 第一步先创建DatagramSocket ,然后构造出要发送的内容,放在DatagramPacket数据报中(包含发送的数据,和ip地址,端口号等信息) 第二步发送数据包给接受端,通过socket.send()方法来发送给接收端 第三步接收端创建DatagramSocket,通过DatagramPacket构造出一个存放数据报的空间,通过socket.receive()方法来接受一个UDP数据报, 最后接收端获取到了发送端发送的信息 以上只是一次发送端的UDP数据报发送及接收端的数据报接收并没有返回的数据。也就是只有请求没有响应。对于一个服务端来说重要的是提供多个客户端的请求处理及响应
提供多个客户端的请求处理及响应
对于请求响应模型,客户端和服务器同时担任接收端和发送端的任务 客户端先给服务器发送请求,服务器接收到请求后执行业务逻辑,构造响应再次发送给客户端,客户端再接收响应
Java流套接字通信模型 Socket编程注意事项
客户端和服务端开发时经常是基于一个主机开启两个进程作为客户端和服务端但真实的场 景一般都是不同主机。注意目的IP和目的端口号标识了一次数据传输时要发送数据的终点主机和进程Socket编程我们是使用流套接字和数据报套接字基于传输层的TCP或UDP协议但应用层协议 也需要考虑这块我们在后续来说明如何设计应用层协议
UDP数据报套接字编程
DatagramSocket API
DatagramSocket 是UDP Socket用于发送和接收UDP数据报。
构造方法
方法名方法说明DatagramSocket()创建一个UDP数据报套接字的Socket绑定到本机任意一个随机端口一般用于客户端DatagramSocket(intport)创建一个UDP数据报套接字的Socket绑定到本机指定的端口一般用于服务端
普通方法
方法名方法说明voidreceive(DatagramPacket p)从此套接字接收数据报如果没有接收到数据报该方法会阻塞等待void send(DatagramPacketp)从此套接字发送数据报包不会阻塞等待直接发送void close()关闭此数据报套接字
DatagramPacket API
DatagramPacket是UDP Socket发送和接收的数据报。
构造方法
方法方法说明DatagramPacket(byte[] buf, int length)构造一个DatagramPacket以用来接收数据报接收的数据保存在字节数组第一个参数buf中接收指定长度第二个参数length)DatagramPacket(byte[]buf, int offset, int length,SocketAddress address)构造一个DatagramPacket以用来发送数据报发送的数据为字节数组第一个参数buf中从0到指定长度第二个参数length。address指定目的主机的IP和端口号
普通方法
方法名方法说明InetAddress getAddress()从接收的数据报中获取发送端主机IP地址或从发送的数据报中获取接收端主机IP地址int getPort()从接收的数据报中获取发送端主机的端口号或从发送的数据报中获取接收端主机端口号byte[] getData()获取数据报中的数据
构造UDP发送的数据报时需要传入 SocketAddress 该对象可以使用 InetSocketAddress 来创建。
InetSocketAddress API
InetSocketAddress SocketAddress 的子类 构造方法
InetSocketAddress(InetAddress addr, int port) 创建一个Socket地址包含IP地址和端口号
实现回显UDP客户端和服务器
客户端
import java.io.IOException;
import java.net.*;
import java.util.Scanner;public class UdpEchoClient {private DatagramSocket socket null;private String serverIp;private int serverPort;public UdpEchoClient(String ip, int port) throws SocketException {serverIp ip;serverPort port;//客户端端口号让系统自动分配socket new DatagramSocket();}//让客户端反复从控制台读取用户输入的数据,把输入的数据构造成UDP请求,发送给服务器//在读取服务器返回的响应,打印到控制台public void start() throws IOException {Scanner scanner new Scanner(System.in);System.out.println(客户端启动);while (true){//1.从控制台获取用户输入的数据System.out.print(--);String request scanner.next();//构造出请求对象,发送给服务器/*** 第一个参数是数据转换为字节数组* 第二个参数是字节数组的长度* 第三个参数是ip地址* 第四个参数是端口号*/DatagramPacket requestPacket new DatagramPacket(request.getBytes(),request.getBytes().length,InetAddress.getByName(serverIp),serverPort);//发送给服务器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.println(response);}}public static void main(String[] args) throws IOException {UdpEchoClient udpEchoClient new UdpEchoClient(127.0.0.1,9090);udpEchoClient.start();}
}
服务器
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;/*** UDP回显服务器*/
public class UdpEchoServer {private DatagramSocket socket null;public UdpEchoServer(int port) throws SocketException {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 request new String(requestPacket.getData(),0,requestPacket.getLength());//2.根据请求,做出响应String response process(request);//这里是回显服务器,就返回客户端发来的请求DatagramPacket responsePocket new DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getAddress(),requestPacket.getPort());socket.send(responsePocket);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();}
}服务器能够进行一对多的响应
TCP流套接字编程
ServerSocket API
ServerSocket 是创建TCP服务端Socket的API
构造方法 ServerSocket(int port) 创建一个服务端流套接字Socket并绑定到指定端口 普通方法
方法名方法说明Socket accept()开始监听指定端口创建时绑定的端口有客户端连接后返回一个服务端Socket 对象并基于该Socket建立与客户端的连接否则阻塞等待void close()关闭此套接字
Socket API
Socket 是客户端Socket或服务端中接收到客户端建立连接accept方法的请求后返回的服务端Socket。
不管是客户端还是服务端Socket都是双方建立连接以后保存的对端信息及用来与对方收发数据的。
构造方法 Socket(String host, int port) 创建一个客户端流套接字Socket并与对应IP的主机上对应端口的进程建立连接 普通方法
方法名方法说明InetAddress getInetAddress()返回套接字所连接的地址InputStream getInputStream()返回此套接字的输入流OutputStream getOutputStream()返回此套接字的输出流
TCP中的长短连接
TCP发送数据时需要先建立连接什么时候关闭连接就决定是短连接还是长连接 短连接每次接收到数据并返回响应后都关闭连接即是短连接。也就是说短连接只能一次收发数据。 长连接不关闭连接一直保持连接状态双方不停的收发数据即是长连接。也就是说长连接可以多次收发数据。 对比以上长短连接两者区别如下
建立连接、关闭连接的耗时短连接每次请求、响应都需要建立连接关闭连接而长连接只需要第一次建立连接之后的请求、响应都可以直接传输。相对来说建立连接关闭连接也是要耗时的长连接效率更高。主动发送请求不同短连接一般是客户端主动向服务端发送请求而长连接可以是客户端主动发送请求也可以是服务端主动发。两者的使用场景有不同短连接适用于客户端请求频率不高的场景如浏览网页等。长连接适用于客户端与服务端通信频繁的场景如聊天室实时游戏等。
扩展了解 基于BIO同步阻塞IO的长连接会一直占用系统资源。对于并发要求很高的服务端系统来说这样的消耗是不能承受的。 由于每个连接都需要不停的阻塞等待接收数据所以每个连接都会在一个线程中运行。 一次阻塞等待对应着一次请求、响应不停处理也就是长连接的特性一直不关闭连接不停的处理请求。 实际应用时服务端一般是基于NIO即同步非阻塞IO来实现长连接性能可以极大的提升。
实现回显TCP客户端和服务器
服务器
import javafx.scene.layout.Priority;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 {private ServerSocket serverSocket null;//线程数目不固定的线程池private ExecutorService service Executors.newCachedThreadPool();public TcpEchoServer(int port) throws IOException {//绑定端口号serverSocket new ServerSocket(port);}//启动服务器public void start() throws IOException {System.out.println(服务器启动);while (true){Socket clientSocket serverSocket.accept();//创建一个新的线程来完成任务//主线程用来接收客户端的连接//这里使用线程池,每有一个客户端请求,就使用一个线程service.submit(new Runnable() {Overridepublic void run() {processConnection(clientSocket);}});}}private void processConnection(Socket clientSocket) {System.out.printf([%s:%d] 客户端上线\n, clientSocket.getInetAddress().toString(), clientSocket.getPort());//socket对象内部包含了两个字节流对象,可以把这两个字节流对象获取到,完成读写操作try (InputStream inputStream clientSocket.getInputStream();OutputStream outputStream clientSocket.getOutputStream()) {while (true) {//1.读取请求并解析//为了读取方便,使用scanner将流对象封装Scanner scanner new Scanner(inputStream);if (!scanner.hasNext()) {//读取结束,客户端下线System.out.printf([%s:%d] 客户端下线!\n, clientSocket.getInetAddress().toString(), clientSocket.getPort());break;}// 这个代码暗含一个约定, 客户端发过来的请求, 得是文本数据, 同时, 还得带有空白符作为分割. (比如换行这种)String request scanner.next();//2.根据请求计算响应String response process(request);//3.将响应发送给客户端//使用PrintWriter封装OutputStreamPrintWriter printWriter new PrintWriter(outputStream);printWriter.println(response);printWriter.flush();System.out.printf([%s:%d] req: %s, resp: %s\n, clientSocket.getInetAddress().toString(), clientSocket.getPort(),request, response);}} catch (IOException e) {throw new RuntimeException(e);}finally {//finally中加上close操作,保证socket被关闭try {clientSocket.close();} catch (IOException e) {throw new RuntimeException(e);}}}private String process(String resquest){return resquest;}public static void main(String[] args) throws IOException {TcpEchoServer tcpEchoServer new TcpEchoServer(9090);tcpEchoServer.start();}
}
客户端
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 serverPort) throws IOException {//new操作结束后,就和服务器建立了连接socket new Socket(serverIp,serverPort);}public void start(){System.out.println(客户端启动);Scanner scannerConsole new Scanner(System.in);try(InputStream inputStream socket.getInputStream();OutputStream outputStream socket.getOutputStream()){while (true){System.out.print(--);//1.控制台输入字符串String request scannerConsole.next();//2.发送给服务器PrintWriter printWriter new PrintWriter(outputStream);printWriter.println(request);printWriter.flush();//3.从服务器获取响应Scanner scanner new Scanner(inputStream);String response scanner.next();System.out.println(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();}
}