> 技术文档 > 【Java】网络编程(Socket)_java socket编程

【Java】网络编程(Socket)_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; } @Override public 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; } @Override public 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; } @Override public 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; } @Override public 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, 获取 ,\"); 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; } @Override public 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; } @Override public 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; } @Override public 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; } @Override public 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(); } }}