赞
踩
网络编程,指网络上的主机,通过不同的进程,以编程的方式实现网络通信(或称为网络数据传输)
当然,我们只要满足进程不同就行;所以即便是同一个主机,只要是不同进程,基于网络来传输数据,也属于网络编程。
发送端:数据的发送方进程,称为发送端。发送端主机即网络通信中的源主机。
接收端:数据的接收方进程,称为接收端。接收端主机即网络通信中的目的主机。
Socket套接字,是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。基于Socket套接字的网络程序开发就是网络编程
系统提供的socket api有两种:
UDP
- 无连接
- 不可靠传输
- 面向数据报
- 有接收缓冲区,无发送缓冲区
- 大小受限:一次最多传输64k
- 全双工
TCP
- 有连接
- 可靠传输
- 面向字节流
- 有接收缓冲区,也有发送缓冲区
- 大小不限
- 全双工
DatagramSocket
是UDP Socket,用于发送和接收UDP数据报
构造方法 | 作用 |
---|---|
DatagramSocket() | 创建一个UDP数据报套接字的Socket, 绑定到任意一个随机端口号(一般用于客户端,自动分配端口) |
DatagramSocket(int port) | 创建一个UDP数据报套接字的Socket, 绑定到本机指定端口(一般用于服务器) |
方法 | 作用 |
---|---|
void receive(DatagramPacket p) | 从此套接字接收数据 (如果没有接收到数据报, 进行阻塞等待) |
void send(DatagramPacket p) | 从此套接字发送数据包 (不会阻塞等待,直接发送) |
void close() | 关闭此数据报套接字 |
DatagramPacket
是UDP Socket发送和接收的数据报。
构造方法 | 作用 |
---|---|
DatagramPacket(byte[] buf, int length) | 构造一个DatagramPacket用来接收数据报,接收的数据保存在字节数组里, 接受指定长度(一般用于读取信息) |
DatagramPacket(byte[] buf, int offset,int length, SocketAddress address) | 构造一个DatagramPacket用来发送数据报,发送的数据为字节数组,从0到指定长度,address用来指定目的主机的IP和端口号(服务器可以直接获取IP和端口号,客户端需要自行指定) |
关键方法 | 作用 |
---|---|
InetAddress getAddress() | 从接受的数据报中,获取发送端IP地址,或从发送的数据报中,获取接收端主机IP地址 |
int getPort() | 从接收的数据报中,获取发送端主机的端口号,或从发送的数据报中,获取接收端的端口号 |
SocketAddress getSocketAddress() | 从接收的数据报中,获取发送端主机SocketAddress,或从发送的数据报中,获取接收端的SocketAddress(IP地址+端口号) |
byte[] getData() | 获取数据报的数据 |
1.创建一个
DatagramSocket
对象,指定端口号,客户端通过这个端口号发送消息2.通过
DatagramPacket
对象获取到客户端发送的消息,并且使用receive()填充3.处理获取到的消息,先使用new String()把字节转换成字符
4.服务器接收到消息后,使用
DatagramPacket
对象封装给客户端反馈信息,反馈的消息必须是字节形式,再使用send()方法发送反馈消息
public class Server { //准备好socket实例,准备传输 private DatagramSocket socket = null; //构造时指定服务器的端口号 public Server(int port) throws SocketException { this.socket = new DatagramSocket(port); } public void start() throws IOException { System.out.println("服务器启动!"); //服务器要给多个客户端提供服务 while (true) { //1. 读取客户端发过来的信息 DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096); //receive内部会对参数对象进行填充数据,填充的数据来源于网卡 socket.receive(requestPacket); //解析收到的数据包成String String request = new String(requestPacket.getData(), 0, requestPacket.getLength()); //2. 根据请求计算响应 String response = process(request); //处理响应内容: 字符 => 字节 byte[] bytes = response.getBytes(); //3. 把响应写回客户端 //requestPacket.getSocketAddress()通过数据报获取到ip+端口号 DatagramPacket responsePacket = new DatagramPacket(bytes,bytes.length,requestPacket.getSocketAddress()); socket.send(responsePacket); //输出一下发送日志 System.out.printf("日志信息: [%s:%d] 接收到的消息: %s, 反馈消息: %s \n",requestPacket.getAddress().toString(),requestPacket.getPort(),request, response); } } //处理接收到的数据 public String process(String request) { System.out.println("客户端发送的消息:"+request); String response = "服务器已接收到信息"; return response; } public static void main(String[] args) throws IOException { Server server = new Server(9090); server.start(); } }
1.创建一个DatagramSocket对象,同时指定服务器ip和端口号
2.从控制台获取到要发送的信息,转成字节,封装到DatagramPacket对象,使用send()发送到指定的ip和端口号
3.通过
DatagramPacket
对象获取到服务器反馈的消息,并且使用receive()填充4.展示服务器反馈的信息
public class Client { private DatagramSocket socket; //需要指定服务器的ip和端口号 private String serverIp ; private int serverPort ; public Client(String serverIp, int serverPort) throws SocketException { this.socket = new DatagramSocket(); this.serverIp = serverIp; this.serverPort = serverPort; } public void start() throws IOException { System.out.println("客户端启动!"); Scanner scanner = new Scanner(System.in); while (true) { // 1. 从控制台读取要发送的数据 System.out.print("> "); String request = scanner.next(); if (request.equals("exit")) { System.out.println("客户端退出"); break; } // 2. 构造UDP请求,并发送 //上面的IP地址是一个字符串,需要使用InetAddress.getByName来转换成一个整数. DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length, InetAddress.getByName(serverIp), serverPort); socket.send(requestPacket); //3. 读取从服务器返回的响应,并解析 DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096); socket.receive(responsePacket); String response = new String(responsePacket.getData(), 0, responsePacket.getLength()); // 4. 把解析好的结果显示出来 System.out.println("服务器反馈的消息:"+response); } } public static void main(String[] args) throws IOException { //ip:本地ip //端口:服务器端口号 Client client = new Client("127.0.0.1", 9090); client.start(); } }
效果:
服务器中的process方法就是用来处理客户端接收到的消息,那么我们就可以再写一个类,继承服务器这个类并且重写process这个方法,实现我们想要得效果。
模拟实现英文单词翻译器(Server和Client使用前面所写的)
服务器:
public class UdpServer extends Server { private Map<String, String> dict = new HashMap<>(); public UdpServer(int port) throws SocketException { super(port); dict.put("dog", "小狗"); dict.put("cat", "小猫"); dict.put("student", "学生"); dict.put("teacher", "教师"); } @Override public String process(String request) { return dict.getOrDefault(request, "该单词没有查到"); } public static void main(String[] args) throws IOException { Server udpServer = new UdpServer(9090); udpServer.start(); } }
效果:
ServerSocket
是创建TCP服务端Socket的API
构造方法 | 解释 |
---|---|
ServerSocket(int port) | 创建一个服务端流套接字Socket,并绑定到指定端口 |
方法 | 解释 |
---|---|
Socket accept() | 开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket对象,并基于该Socket建立与客户端的连接,否则阻塞等待 |
void close() | 关闭此套接字 |
Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。
不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。
构造方法 | 解释 |
---|---|
Socket(String host, int port) | 创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接 |
方法 | 解释 |
---|---|
InetAddress getInetAddress() | 返回套接字所连接的地址 |
InputStream getInputStream() | 返回此套接字的输入流 |
OutputStream getOutputStream() | 返回此套接字的输出流 |
TCP中的长短连接
TCP发送数据时,需要先建立连接,什么时候关闭连接就决定是短连接还是长连接:
短连接:每次接收到数据并返回响应后,都关闭连接,即是短连接。也就是说,短连接只能一次收发数据。
长连接:不关闭连接,一直保持连接状态,双方不停的收发数据,即是长连接。也就是说,长连接可以多次收发数据。
1.创建一个ServerSocket对象,并指定端口号,用来和客户端建立连接
2.当有客户端连接到服务器的时候,通过accept()方法建立与客户端的连接
3.使用inputStream、scanner接收服务器传过来的信息
4.使用outputStream、printWriter处理反馈信息
5.特别注意:与客户端建立连接产生的clientSocket一定要关闭,不然可能造成资源泄露
public class TcpEchoServer { //服务器专用的socket,用来和客户端建立连接 private ServerSocket serverSocket = null; public TcpEchoServer(int port) throws IOException { serverSocket = new ServerSocket(port); } //启动服务器 public void start() throws IOException { //创建线程池 ExecutorService threadPool = Executors.newCachedThreadPool(); System.out.println("启动服务器!"); while (true) { //和客户端建立连接 Socket clientSocket = serverSocket.accept(); //和客户端进行交互, //如果直接调用processConnection,会导致accept不及时 //1.使用多线程解决问题 Thread t = new Thread(() -> { processConnection(clientSocket); }); t.start(); //2.使用线程池解决问题 /* threadPool.submit(() -> { processConnection(clientSocket); });*/ } } //处理客户端连接 private void processConnection(Socket clientSocket) { System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort()); //基于clientSocket对象和客户端进行通信 try (InputStream inputStream = clientSocket.getInputStream(); OutputStream outputStream = clientSocket.getOutputStream()) { //Scanner、PrintWriter都是字符流,避免了直接使用字节流需要我们去判断那个是结束标记 Scanner scanner = new Scanner(inputStream); PrintWriter printWriter = new PrintWriter(outputStream); //客户端可能有多个请求,所以使用循环来处理 while (true) { // 1. 读取请求 if (!scanner.hasNext()) { //hasNext()方法判断输入是否还有下一个输入项,若有,返回true,反之false. //当客户端那边关闭了连接,输入源也就结束了,没有了下一个数据,说明读完了,此时hasNext()就为false了 System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort()); break; } //next 是一直读取到换行符/空格/其他空白符结束,但是最终返回结果里不包含空白符. String request = scanner.next(); // 2. 根据请求构造响应 String response = process(request); // 3. 返回响应的结果,写入网卡 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) { e.printStackTrace(); } finally { try { //释放资源 clientSocket.close(); } catch (IOException e) { throw new RuntimeException(e); } } } public String process(String request) { System.out.println("客户端发送的消息:" + request); String response = "服务器已接收到信息"; return response; } public static void main(String[] args) throws IOException { TcpEchoServer server = new TcpEchoServer(9090); server.start(); } }
1.创建Socket对象,指定服务器的ip和端口号,建立连接
2.使用OutputStream、PrintWriter向服务器传输信息
3.使用InputStream、respScanner接收服务器反馈的信息
public class TcpEchoClient { private Socket socket = null; public TcpEchoClient(String serverIp, int serverPort) throws IOException { // 与服务器建立TCP连接 socket = new Socket(serverIp, serverPort); } public void start() { System.out.println("客户端启动!"); Scanner scanner = new Scanner(System.in); try (InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream()) { Scanner respScanner = new Scanner(inputStream); PrintWriter printWriter = new PrintWriter(outputStream); while (true) { // 1.从键盘上读取用户输入的内容 System.out.print("> "); String request = scanner.next(); if (request.equals("exit")) { System.out.println("客户端退出"); break; } // 2. 构造请求并发送给服务器 printWriter.println(request); printWriter.flush(); // 3. 读取服务器的响应 String response = respScanner.next(); // 4. 把响应内容显示到屏幕上 System.out.printf("req: %s; resp: %s \n", request, response); } } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) throws IOException { TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9090); client.start(); } }
效果:创建一个服务器,两个客户端演示
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。