当前位置: 首页 > news >正文

【Java】网络编程(Socket)

网络编程

Socket

我们开发的网络应用程序位于应用层,TCP和UDP属于传输层协议,在应用层如何使用传输层的服务呢?在应用层和传输层之间,则使用套接字Socket来进行分离

套接字就像是传输层为应用层开的一个小口,应用程序通过这个小口向远程发送数据,或者接收远程发来的数据;而这个小口以内,也就是数据进入这个口之后,或者数据从这个口出来之前,是不知道也不需要知道的,也不会关心它如何传输,这属于网络其他层次工作

Socket实际是传输层供给应用层的编程接口。Socket就是应用层与传输层之间的桥梁。使用Socket变成可以开发客户机和服务器的应用程序,可以在本地网络上通信,也可以通过Internet在全球范围内通信

Java网络编程中的常用类

Java为了跨平台,在网络应用通信时是不允许直接调用操作系统接口的,而是由java.net包来提供网络功能。

InetAddress的使用

作用:封装计算机的IP地址和DNS(没有端口信息)

特点:

这个类没有构造方法,如果想要得到对象,只能通过静态方法:getLocalHost() 、 getByName() 、 getALLByName() 、 getAddress() 、 getHostName()

获取本机信息

获取本机信息需要使用getLocalHost()方法创建InetAddress对象,这个对象包含了本机的IP地址,计算机名等信息

public class InetTest {public static void main(String[] args) {try {InetAddress localHost = InetAddress.getLocalHost();String hostAddress = localHost.getHostAddress();String hostName = localHost.getHostName();System.out.println(hostAddress);System.out.println(hostName);} catch (UnknownHostException e) {throw new RuntimeException(e);}}
}

根据域名获取计算机的信息

根据域名获取计算机信息时需要使用getByName(“域名”)方法创建InetAddress对象

public class InetTest2 {public static void main(String[] args) {try {InetAddress inetAddress = InetAddress.getByName("www.baidu.com");System.out.println(inetAddress.getHostAddress());System.out.println(inetAddress.getHostName());} catch (UnknownHostException e) {throw new RuntimeException(e);}}
}

根据IP地址获取计算机的信息

根据IP地址获取计算机的信息时需要使用getByName(“IP”)方法创建InetAddress对象

public class InetTest3 {public static void main(String[] args) {try {InetAddress inetAddress = InetAddress.getByName("110.242.68.4");System.out.println(inetAddress.getHostName());System.out.println(inetAddress.getHostAddress());} catch (UnknownHostException e) {throw new RuntimeException(e);}}
}
InetSocketAddress的使用

作用:包含IP和端口信息,常用于Socket通信。此类实现IP套接字地址(IP地址+端口号),不依赖任何协议

InetSocketAddress相比较InetAddress多了一个端口号,端口的作用:一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等,这些服务完全可以通过1个IP地址来实现

public class InetSocketTest {public static void main(String[] args) {InetSocketAddress inetSocketAddress = new InetSocketAddress("www.baidu.com",80);System.out.println(inetSocketAddress.getAddress().getHostAddress());System.out.println(inetSocketAddress.getHostName());}
}
URL的使用

IP地址标识了Internet上唯一的计算机,而URL则标识了这些计算机上的资源。URL代表一个资源定位符,它是指向互联网“资源”的指针。资源可以是简单的文件或目录,也可以是对更为复杂的对象的引用,例如对数据库或者搜索引擎的查询

为了方便程序员编程,JDK中提供了URL类,该类的全名是java.net.URL,有了这样一个类,就可以使用它的各种方法来对URL对象进行分割、合并等处理

public class UrlTest {public static void main(String[] args) {try {URL url = new URL("http://www.edu2act.cn/task/list/finished/");System.out.println("获取当前协议的默认端口:" + url.getDefaultPort());System.out.println("访问资源:" + url.getFile());System.out.println("主机名" + url.getHost());System.out.println("访问资源的路径:" + url.getPath());System.out.println("协议" + url.getProtocol());System.out.println("参数部分" + url.getQuery());} catch (MalformedURLException e) {throw new RuntimeException(e);}}
}

通过URL实现最简单的网络爬虫

public class UrlTest2{public static void main(String[] args)throws Exception {URL url = new URL("http://dbms.wangding.co/design/ch04-database-design-1/");try (BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()))) {StringBuilder sb = new StringBuilder();String temp;while ((temp = br.readLine()) != null) {sb.append(temp);}System.out.println(sb);} catch (Exception e) {e.printStackTrace();}}
}
TCP通信的实现和项目案例
TCP通信实现原理

前边我们提到TCP协议是面向的连接的,在通信时客户端与服务器端必须建立连接。在网络通讯中,第一次主动发起通讯的程序被称作客户端(Client)程序,简称客户端,而在第一次通讯中等待连接的程序被称作服务器端(Server)程序,简称服务器。一旦通讯建立,则客户端和服务器端完全一样,没有本质的区别。

请求-相应 模式

  • Socket类:发送TCP信息
  • ServerSocket类:创建服务器

套接字Socket是一种进程间的数据交换机制。这些进程既可以在同一机器上,也可以在通过网络连接的不同机器上。换句话说,套接字起到通信端点的作用。单个套接字是一个端点,而一对套接字则构成一个双向通信信道,使非关联进程可以在本地或通过网络进行数据交换。一旦建立套接字连接,数据即可在相同或不同的系统中双向或单向发送,直到其中一个端点关闭连接。套接字与主机地址和端口地址相关联。主机地址就是客户端或服务器程序所在的主机的IP地址。端口地址是指客户端或服务器程序使用的主机的通信端口。

在客户端和服务器中,分别创建独立的Socket,并通过Socket的属性,将两个Socket进行连接,这样,客户端和服务器通过套接字所建立的连接使用输入输出流进行通信。

TCP/IP套接字是最可靠的双向流协议,使用TCP/IP可以发送任意数量的数据。

实际上,套接字只是计算机上已编号的端口。如果发送方和接收方计算机确定好端口,他们就可以通信了。

客户端与服务器端的通信关系图:

在这里插入图片描述

TCP/IP通信连接的简单过程

位于A计算机上的TCP/IP软件向B计算机发送包含端口号的消息,B计算机的TCP/IP软件接收该消息,并进行检查,查看是否有它知道的程序正在该端口上接收消息。如果有,他就将该消息交给这个程序。

通过Socket的编程顺序

1、创建服务器ServerSocket,在创建时,定义ServerSocket的监听端口(在这个端口接收客户端发来的消息)
2、ServerSocket调用accept()方法,使之处于阻塞状态。
3、创建客户端Socket,并设置服务器的IP及端口。
4、客户端发出连接请求,建立连接。
5、分别取得服务器和客户端Socket的InputStream和OutputStream。
6、利用Socket和ServerSocket进行数据传输。
7、 关闭流及Socket。

TCP通信入门案例

创建服务端

public class BasicSocketServer {public static void main(String[] args) throws IOException {System.out.println("服务器已启动,等待监听....");ServerSocket serverSocket = null;try {serverSocket = new ServerSocket(8888);Socket socket = serverSocket.accept();//连接成功后会得到与客户端对应的Socket对象,并解除线程阻塞InputStream in = socket.getInputStream();BufferedReader br = new BufferedReader(new InputStreamReader(in));System.out.println(br.readLine());OutputStream out = socket.getOutputStream();} catch (IOException e) {throw new RuntimeException(e);}finally {serverSocket.close();}}
}

创建客户端

public class BasicSocketClient {public static void main(String[] args) throws IOException {Socket socket = null;PrintWriter pw = null;try {socket = new Socket("127.0.0.1",8888);OutputStream out = socket.getOutputStream();pw = new PrintWriter(out);pw.write("服务端,你好!");pw.flush();} catch (IOException e) {throw new RuntimeException(e);}finally {pw.close();socket.close();}}
}
TCP单向通信

单向通信是指通信双方中,一方固定为发送端,一方固定为接收端

创建服务端

public class OneWaySocketServer {public static void main(String[] args) {System.out.println("服务器启动,开始监听");try(ServerSocket serverSocket = new ServerSocket(8888);) {Socket socket = serverSocket.accept();BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));PrintWriter pw = new PrintWriter(socket.getOutputStream());System.out.println("连接成功");while (true){String str = br.readLine();System.out.println("客户端说" + str);if ("exit".equals(str)){break;}pw.println(str);pw.flush();}} catch (IOException e) {System.out.println("服务器启动失败");throw new RuntimeException(e);}}
}

创建客户端

public class OneWaySocketClient {public static void main(String[] args) {try(Socket socket = new Socket("127.0.0.1",8888)) {Scanner scanner = new Scanner(System.in);PrintWriter pw = new PrintWriter(socket.getOutputStream());BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));while (true){String s = scanner.nextLine();pw.println(s);pw.flush();if ("exit".equals(s)){break;}String serverInput = br.readLine();System.out.println("服务器返回的" + serverInput);}}catch (Exception e){e.printStackTrace();}}
}
TCP双向通信

双向通信是指通信双方中,任何一方都可为发送端,任何一方都可为接收端

服务端

public class TwoWaySocketServer {public static void main(String[] args) {System.out.println("服务器启动,监听8888端口");try(ServerSocket serverSocket = new ServerSocket(8888);) {Socket socket = serverSocket.accept();//创建键盘输入对象Scanner scanner = new Scanner(System.in);BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));PrintWriter pw = new PrintWriter(socket.getOutputStream());while (true){String str = br.readLine();System.out.println("客户端说:" + str);String keyInput = scanner.nextLine();pw.println(keyInput);pw.flush();}}catch (Exception e){e.printStackTrace();}}
}

客户端

public class TwoWaySocketClient {public static void main(String[] args) {try(Socket socket = new Socket("127.0.0.1",8888)) {Scanner scanner = new Scanner(System.in);BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));PrintWriter pw = new PrintWriter(socket.getOutputStream());while (true){String keyInput = scanner.nextLine();pw.println(keyInput);pw.flush();String str = br.readLine();System.out.println("服务端说:" + str);}}catch (Exception e){e.printStackTrace();}}
}
创建点对点的聊天应用

创建服务端

主线程

public class ChatSocketServer {public static void main(String[] args) {try(ServerSocket serverSocket = new ServerSocket(8888)) {System.out.println("服务端启动,等待连接");Socket socket = serverSocket.accept();new Thread(new SendThread(socket)).start();new Thread(new ReceiveThread(socket)).start();}catch (Exception e){e.printStackTrace();}}
}

接收消息线程

public class ReceiveThread implements Runnable{private Socket socket;public ReceiveThread(Socket socket){this.socket = socket;}@Overridepublic void run() {this.receiveMsg();}private void receiveMsg(){try(BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));){while (true){String msg = br.readLine();System.out.println("客户端说:" + msg);}}catch (Exception e){e.printStackTrace();}}
}

发送消息线程

public class SendThread implements Runnable{private Socket socket;public SendThread(Socket socket){this.socket = socket;}@Overridepublic void run() {this.sendMsg();}private void sendMsg(){try(Scanner scanner = new Scanner(System.in);PrintWriter pw = new PrintWriter(socket.getOutputStream());){while (true){String msg = scanner.nextLine();pw.println(msg);pw.flush();}}catch (Exception e){e.printStackTrace();}}
}

创建客户端

主线程

public class ChatSocketClient {public static void main(String[] args) {try {Socket socket = new Socket("127.0.0.1",8888);System.out.println("连接成功");new Thread(new ClientSendThread(socket)).start();new Thread(new ClientReceiveThread(socket)).start();}catch (Exception e){e.printStackTrace();}}
}

接收消息线程被

public class ClientReceiveThread implements Runnable{private Socket socket;public ClientReceiveThread(Socket socket){this.socket = socket;}@Overridepublic void run() {receiveMsg();}private void receiveMsg() {try(BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {while (true){String msg = br.readLine();System.out.println("服务端说:" + msg);}} catch (IOException e) {e.printStackTrace();}}
}

发送消息线程

public class ClientSendThread implements Runnable{private Socket socket;public ClientSendThread(Socket socket){this.socket = socket;}@Overridepublic void run() {this.sendMsg();}private void sendMsg() {try(Scanner scanner = new Scanner(System.in);PrintWriter pw = new PrintWriter(socket.getOutputStream())){while (true){String msg = scanner.nextLine();pw.println(msg);pw.flush();}}catch (Exception e){e.printStackTrace();}}
}
优化点对点的聊天应用
public class GoodTCP {public static void main(String[] args) throws IOException {ServerSocket serverSocket = null;Socket socket = null;try {Scanner scanner = new Scanner(System.in);System.out.println("请输入:server,<port> 获取 <ip>,<port>");String str = scanner.nextLine();String[] arr = str.split(",");if("server".equals(arr[0])){System.out.println("TCP Server Listen at" + arr[1] + "......");serverSocket = new ServerSocket(Integer.parseInt(arr[1]));socket = serverSocket.accept();new Receive(socket);}else {socket = new Socket(arr[0],Integer.parseInt(arr[1]));System.out.println("连接成功");}new Thread(new Send(socket, scanner)).start();new Thread(new Receive(socket)).start();}catch (Exception e){e.printStackTrace();}finally {if(serverSocket != null){serverSocket.close();}}}
}
public class Send implements Runnable{private Socket socket;private Scanner scanner;public Send(Socket socket, Scanner scanner) {this.socket = socket;this.scanner = scanner;}@Overridepublic void run() {this.sendMsg();}private void sendMsg(){try(PrintWriter pw = new PrintWriter(socket.getOutputStream());){while (true){String msg = scanner.nextLine();pw.println(msg);pw.flush();}}catch (Exception e){e.printStackTrace();}}
}
public class Receive implements Runnable{private Socket socket;public Receive(Socket socket){this.socket = socket;}@Overridepublic void run() {sendMsg();}private void sendMsg(){try(BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));){while (true){String msg = br.readLine();System.out.println("客户端说:" + msg);}}catch (Exception e){e.printStackTrace();}}
}
一对多应用

服务端应该将serverSocket.accept()放入while(true)循环中

一对多聊天服务器设计

难点在于解决线程同步

当没有消息发送时,发送线程处于等待状态,当接收线程接收到消息后,唤醒所有等待的发送线程

public class ChatRoomServer {public static String buf;public static void main(String[] args) {System.out.println("Chat Server Version 1.0");System.out.println("Listen at 8888......");try(ServerSocket serverSocket = new ServerSocket(8888)){while (true){Socket socket = serverSocket.accept();System.out.println("连接到" + socket.getInetAddress());new Thread(new ChatReceiveThread(socket)).start();new Thread(new ChatSendThread(socket)).start();}}catch (Exception e){e.printStackTrace();}}
}
public class ChatReceiveThread implements Runnable {private Socket socket;public ChatReceiveThread(Socket socket) {this.socket = socket;}@Overridepublic void run() {receiveMsg();}private void receiveMsg() {try(BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));){while (true){String msg = br.readLine();synchronized ("abc"){ChatRoomServer.buf = "[" + this.socket.getInetAddress() + "]" + msg;"abc".notifyAll();}}}catch (Exception e){e.printStackTrace();}}
}
public class ChatSendThread implements Runnable{Socket socket;public ChatSendThread(Socket socket) {this.socket = socket;}@Overridepublic void run() {sendMsg();}private void sendMsg() {try(PrintWriter pw = new PrintWriter(socket.getOutputStream());){while (true){synchronized ("abc"){//先让发送消息的线程处于等待状态"abc".wait();//将公共数据区的数据发送给客户端pw.println(ChatRoomServer.buf);pw.flush();}}}catch (Exception e){e.printStackTrace();}}
}
UDP通信实现原理

UDP协议与之前讲到的TCP协议不同,是面向无连接的,双方不需要建立连接便可通信。UDP通信所发送的数据需要进行封包操作(使用DatagramPacket类),然后才能接收或发送(使用DatagramSocket类)。

DatagramPacket:数据容器(封包)的作用

此类表示数据报包。 数据报包用来实现封包的功能。

常用方法

方法名使用说明
DatagramPacket(byte[] buf, int length)构造数据报包,用来接收长度为 length 的数据包
DatagramPacket(byte[] buf, int length, InetAddress address, int port)构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号
getAddress()获取发送或接收方计算机的IP地址,此数据报将要发往该机器或者是从该机器接收到的
getData()获取发送或接收的数据
setData(byte[] buf)设置发送的数据

DatagramSocket:用于发送或接收数据报包

当服务器要向客户端发送数据时,需要在服务器端产生一个DatagramSocket对象,在客户端产生一个DatagramSocket对象。服务器端的DatagramSocket将DatagramPacket发送到网络上,然后被客户端的DatagramSocket接收。

DatagramSocket有两种常用的构造函数。一种是无需任何参数的,常用于客户端;另一种需要指定端口,常用于服务器端。如下所示:

  • DatagramSocket() :构造数据报套接字并将其绑定到本地主机上任何可用的端口。
  • DatagramSocket(int port) :创建数据报套接字并将其绑定到本地主机上的指定端口。

常用方法

方法名使用说明
send(DatagramPacket p)从此套接字发送数据报包
receive(DatagramPacket p)从此套接字接收数据报包
close()关闭此数据报套接字

UDP通信编程基本步骤:

1、创建客户端的DatagramSocket,创建时,定义客户端的监听端口。
2、创建服务器端的DatagramSocket,创建时,定义服务器端的监听端口。
3、在服务器端定义DatagramPacket对象,封装待发送的数据包。
4、客户端将数据报包发送出去。
5、服务器端接收数据报包。

UDP通信入门案例

服务端

public class UDPServer {public static void main(String[] args) {//创建服务端接收数据的DatagramSocket对象try(DatagramSocket datagramSocket = new DatagramSocket(9999)){//创建数据缓冲区byte[] b = new byte[1024];//创建数据报包对象DatagramPacket datagramPacket = new DatagramPacket(b,b.length);//等待接收客户端所发送的数据datagramSocket.receive(datagramPacket);//取出数据String str = new String(datagramPacket.getData(),0,datagramPacket.getLength());System.out.println(str);}catch (Exception e){e.printStackTrace();}}
}

客户端

public class UDPClient {public static void main(String[] args) {try(DatagramSocket datagramSocket = new DatagramSocket(8888);) {byte[] bytes = "Triticale".getBytes();DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length,new InetSocketAddress("127.0.0.1",9999));datagramSocket.send(datagramPacket);}catch (Exception e){e.printStackTrace();}}
}
基本数据类型通信

服务端

try(DataInputStream dis = new DataInputStream(new ByteArrayInputStream(datagramPacket.getData()));){System.out.println(dis.readLong());
}

客户端

long n = 2000l;
try(ByteArrayOutputStream bos = new ByteArrayOutputStream();DataOutputStream dos = new DataOutputStream(bos);){dos.writeLong(n);byte[] arr = bos.toByteArray();DatagramPacket datagramPacket1 = new DatagramPacket(arr,arr.length,new InetSocketAddress("127.0.0.1",9999));
}
传递自定义数据类型

创建服务端

public class ObjectTypeUDPServer {public static void main(String[] args) {try(DatagramSocket datagramSocket = new DatagramSocket(9999)){byte[] bytes = new byte[1024];DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length);datagramSocket.receive(datagramPacket);try(ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(datagramPacket.getData()))){Person person = (Person) ois.readObject();System.out.println(person.toString());}}catch (Exception e){e.printStackTrace();}}
}

创建客户端

public class ObjectTypeUDPClient {public static void main(String[] args) {try(DatagramSocket datagramSocket = new DatagramSocket(8888);ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(bos)){Person person = new Person();person.setName("张三");person.setAge(18);oos.writeObject(person);byte[] byteArray = bos.toByteArray();DatagramPacket datagramPacket = new DatagramPacket(byteArray,byteArray.length,new InetSocketAddress("127.0.0.1",9999));datagramSocket.send(datagramPacket);}catch (Exception e){e.printStackTrace();}}
}

相关文章:

  • Mac上安装Mysql的详细步骤及配置
  • git-gui界面汉化
  • android 权限配置
  • Visual Studio 2022 跨网络远程调试
  • JSP笔记
  • 《类和对象(下)》
  • Android NDK 高版本交叉编译:为何无需配置 FLAGS 和 INCLUDES
  • Cursor 编辑器 的 高级使用技巧与创意玩法
  • Flask Docker Demo 项目指南
  • 二次封装 el-dialog 组件:打造更灵活的对话框解决方案
  • 六、Hive 分桶
  • Spark处理过程-转换算子
  • 运行Spark程序-在Spark-shell——RDD
  • 第四章 部件篇之按钮矩阵部件
  • 前端如何应对精确数字运算?用BigNumber.js解决JavaScript原生Number类型在处理大数或高精度计算时的局限性
  • JVM Optimization Learning(七)-GC
  • JVM——方法内联之去虚化
  • 哈希表:数据世界的超级索引
  • 【速通RAG实战:进阶】10.RAG 进化论:Advanced与Modular架构解锁智能问答新维度
  • Kafka 如何保证消息顺序性
  • 市场监管总局等五部门约谈外卖平台企业
  • 反制美国钢铝关税!印度拟对美国部分商品征收关税
  • 吉林:消纳绿电,“氢”装上阵
  • 行知读书会|换一个角度看见社会
  • 石家庄推动城市能级与民生福祉并进
  • IPO周报|本周A股暂无新股网上申购,年内最低价股周二上市