目录
引例
在学习网络编程之前,我们先看这样一个例子。一般处于我们这个年龄段的同学,大都经历过写信/寄信的经历,在写信的时候,我们一般都需要明确发送的地址、联系人信息以及所在地区的邮编,邮递员可以根据这上面的信息找到接收信件的人,接收人在阅读过信件的内容后可以以同样的方式回信,这样就使得身处异地的朋友间可以进行通信。
在这种情况下,身处异地的朋友如果要进行通信,必须同时满足以下条件
1.接收人的所在地区及详细的住址
2.需要有快递员接收并发送
慢慢的,随着科技的发展,我们有手机通信、QQ聊天和微信聊天等多种通信方式,采用这些方式我们可以更便捷的进行通信,但是无论是那种方式,通信过程中总是需要这样两个元素:地址及传输过程。
在网络通信中,这两个元素可以使用更专业的名词来代替:
1、IP地址和端口号
IP地址可以帮助我们找到通信的接收方,端口号则可以帮助我们找到计算机中的具体应用,如QQ、微信等
2、传输协议
协议是为了帮助我们更好的进行传输,如TCP、UDP、FTP、SMTP和HTTP协议等
IP地址是互联网协议地址,它是一种统一的地址格式,互联网中的每一台主机都会有一个逻辑地址。在计算机网络中,localhost(意为“本地主机”,指“这台计算机”)是给回的一个标准主机名,相对应的IP地址为127.0.0.1
通过IP地址,我们就可以连接到指定的计算机了,但是如果要访问目标计算机中的某个应用程序,还需要知道端口号。在计算中,不同的应用程序就是通过端口号来进行区分的。
在网络编程中,可以使用InetAddress进行主机名解析和反向解析,即给定确定的主机名,返回确定的IP地址;给定IP地址,返回主机名
localhost解析为127.0.0.1
127.0.0.1反向解析为localhost
NOTE:不同协议的端口是可以重复的,如UDP和TCP
端口的查询操作
netstat -ano #查看所有的端口 netstat -ano | findstr "XXX" # 查看指定的端口IP地址及端口号解决了通信过程中的地址问题,但是在计算机中,我们还要解决如何通信问题,所谓通信就是计算机间如何交流,而通信协议就是将计算机双方遵循的一种规则和约定(如同普通话、英语),它可以通过通信信道将处于不同地理位置的设备连接起来,能够实现信息的交换和资源共享。
在计算机网络中,常用的协议就是TCP/IP,它是协议簇,由多个子协议组成了,如我们常见的TCP、IP、UDP、ARP等,我们主要讲解网络编程中常用的TCP、UDP和IP
TCPTCP协议是一种传输协议,面向连接、可靠的、基于字节流的传输层通信协议
UDPUDP是一种无连接的传输协议,无需建立连接就可以发送数据包
IPIP协议整个TCP/IP协议族的核心,对上可载送传输层各种协议的信息,例如TCP、UDP等;对下可将IP信息包放到链路层,通过以太网等各种技术来传送。
TCP和UDP对比
TCP可以类比于打电话,它具有以下特点
在数据传输前,需要建立连接(三次握手),所以连接稳定可靠有客户端、服务端的概念,客户端发送,服务端接收传输完成后,会释放连接(四次挥手)UDP可以类比于发短信,它具有以下特点
数据传输前,不需要建立连接,所以不可靠,不稳定客户端和服务端没有明确界限,客户端和服务端都可以进行收/发不需要建立连接,所以速度较快TCP网络编程需要以下Java类
InetAddress:表示IP协议的地址,可以解析IP地址和主机名
Socket:实现客户端的套接字,建立连接,套接字就是两台机器间通讯的端点
ServerSocket:实现服务端的套接字
客户端
1.拿到服务端的地址及端口,InetAddress
2.建立Socket连接
3.发送消息
public class TcpClient { public static void main(String[] args) throws IOException { Socket socket=null; OutputStream os=null; try { //1、得到服务端的地址,端口号 InetAddress inetAddress=InetAddress.getByName("127.0.0.1"); int port=9898; //2、建立Socket连接 socket=new Socket(inetAddress,port); //3、发送消息 os=socket.getOutputStream(); os.write("hello,Simon".getBytes()); }catch (Exception e){ e.printStackTrace(); }finally { //关闭资源 os.close(); socket.close(); } } }服务端
1.用ServerSocket设置自己的端口号
2.等待连接,Socket
2.1:可以等待一次连接,接收到消息后关闭
2.2:等待多次连接,用while(true)把等待连接的方法包裹起来,重复接收消息
3、获取发送端的消息
public class TcpServer { public static void main(String[] args) throws IOException { ServerSocket serverSocket=null; Socket socket=null; InputStream is=null; ByteArrayOutputStream baos=null; try { //1、设置自己的端口号 serverSocket=new ServerSocket(9898); //2、等待客户端的连接 socket=serverSocket.accept(); //3、读取客户端的消息 is=socket.getInputStream(); baos=new ByteArrayOutputStream(); byte[] buffer=new byte[1024]; int len; while ((len=is.read(buffer))!=-1){ baos.write(buffer,0,len); } System.out.print(baos.toString()); }catch (Exception e){ e.printStackTrace(); }finally { baos.close(); is.close(); socket.close(); serverSocket.close(); } } }客户端
1.创建一个连接
2.创建一个字节输出流用于通信
创建一个文件输入流用于接收文件,然后将接收后的文件给字节输出流用于通信
3.输出结束后,调用shutdownInput方法通知服务端已经发送完毕
4.关闭连接资源
public class TcpFileClient { public static void main(String[] args) throws Exception { //1、创建连接 Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 9987); //2、定义一个输出流用于通信 OutputStream os = socket.getOutputStream(); FileInputStream fis = new FileInputStream(new File("simon.png")); byte[] buffer = new byte[1024]; int len; while ((len = fis.read(buffer)) != -1) { os.write(buffer, 0, len); } //3、通知服务器,我已经结束了 socket.shutdownOutput(); //确定服务器接收完毕,才可以断开连接,这一段代码主要是接收服务端发出的结束信息 InputStream inputStream= socket.getInputStream(); byte[] buffer2=new byte[1024]; ByteArrayOutputStream baos=new ByteArrayOutputStream(); int len2; while ((len2=inputStream.read(buffer2))!=-1){ baos.write(buffer2,0,len2); } System.out.print(baos.toString()); //关闭连接 baos.close(); inputStream.close(); os.close(); fis.close(); socket.close(); } }服务端
1.创建一个连接用于等待客户端的接入
2.创建一个输入流
创建一个文件输出流,将输入流输出
3.关闭资源
public class TcpFileServerDemo02 { public static void main(String[] args) throws Exception { //设置端口号 ServerSocket serverSocket=new ServerSocket(9987); //等待连接 Socket socket=serverSocket.accept(); //获取输入流 InputStream is=socket.getInputStream(); //文件输出 FileOutputStream fos=new FileOutputStream(new File("snow.png")); byte[] buffer=new byte[1024]; int len; while ((len=is.read(buffer))!=-1){ fos.write(buffer,0,len); } //通知客户端接收完毕 OutputStream os=socket.getOutputStream(); os.write("我已经接收结束,你可以断开了".getBytes()); //关闭资源 fos.close(); is.close(); socket.close(); serverSocket.close(); } }UDP不需要连接,但是需要知道对方的地址和端口号,主要用到了以下Java类
DatagramPacket:表示数据包,用于实现无连接分组传送服务
DatagramSocket:表示用于发送和接收数据报的套接字
UDP是不存在客户端和服务端的概念,但是为了编程模拟方便,我们假定存在客户端和服务端
客户端
建立连接创建数据包发送数据包 public class UdpClientDemo1 { public static void main(String[] args) throws Exception { //1、建立连接(这个端口号是客户端的) DatagramSocket datagramSocket=new DatagramSocket(9888); //2、建立数据包 String msg="hello,Simon"; InetAddress inetAddress=InetAddress.getByName("127.0.0.1"); //数据,数据的起始位置,要发送的地址与端口号 DatagramPacket datagramPacket=new DatagramPacket(msg.getBytes(),0,msg.getBytes().length,inetAddress,9887); //3、发送数据包 datagramSocket.send(datagramPacket); //4、关闭数据流 datagramSocket.close(); } }服务端
建立连接接收数据 public class UdpServerDemo2 { public static void main(String[] args) throws Exception{ //建立连接,开放的端口 DatagramSocket datagramSocket=new DatagramSocket(9887); //接收数据包 byte[] buffer=new byte[1024]; DatagramPacket datagramPacket=new DatagramPacket(buffer,0,buffer.length); datagramSocket.receive(datagramPacket); //打印数据包信息 System.out.println(datagramPacket.getAddress()); System.out.println(datagramPacket.getPort()); System.out.println(new String(datagramPacket.getData())); //关闭连接 datagramSocket.close(); }使用while(true)方法将客户端中的发送数据和接收数据包裹起来,只要当他们满足一定的条件时(如输入的字符串为"bye"),退出即可。这样就可以达到循环发送和接收消息。
客户端
public class UdpSend { public static void main(String[] args) throws Exception { //1、建立连接 DatagramSocket datagramSocket=new DatagramSocket(9888); //2、创建数据包,从键盘输入 InetAddress inetAddress=InetAddress.getByName("127.0.0.1"); int port=9887; BufferedReader bufferedReader=new BufferedReader(new InputStreamReader(System.in)); while (true){ String s=bufferedReader.readLine(); DatagramPacket datagramPacket=new DatagramPacket(s.getBytes(),0,s.getBytes().length,inetAddress,9887); //3、发送数据 datagramSocket.send(datagramPacket); if(s.equals("bye")){ break; } } //4、关闭数据 datagramSocket.close(); } }服务端
public class UdpReceive{ public static void main(String[] args) throws Exception { //1、建立连接 DatagramSocket datagramSocket=new DatagramSocket(9887); while (true){ //2、接收数据包 byte[] buffer=new byte[1024]; DatagramPacket datagramPacket=new DatagramPacket(buffer,0,buffer.length); datagramSocket.receive(datagramPacket); //3、断开连接 byte[] data=datagramPacket.getData(); String receiveData=new String(data,0,data.length); System.out.println(receiveData); if(receiveData.equals("bye")){ break; } } datagramSocket.close(); } }使用UDP实现聊天,则客户端和服务端(其实不存在客户端和服务端的概念)既需要接收信息也需要发送消息,这就需要多线程的支持了。
我们首先构造两个接收类和发送的线程类,然后构造俩个用户类进行通信。
发送类
public class TalkSend implements Runnable { DatagramSocket datagramSocket = null; BufferedReader bufferedReader = null; private int fromPort; private int toPort; private String toIp; public TalkSend(int fromPort,int toPort,String toIp){ this.fromPort=fromPort; this.toPort=toPort; this.toIp=toIp; //1、建立连接 try { datagramSocket = new DatagramSocket(fromPort); //2、创建数据包,从键盘输入 bufferedReader = new BufferedReader(new InputStreamReader(System.in)); } catch (SocketException e) { e.printStackTrace(); } } @Override public void run() { while (true) { try { String s = bufferedReader.readLine(); DatagramPacket datagramPacket = new DatagramPacket(s.getBytes(), 0, s.getBytes().length, new InetSocketAddress(toIp,toPort)); //3、发送数据 datagramSocket.send(datagramPacket); if (s.equals("bye")) { break; } } catch (IOException e) { e.printStackTrace(); } } //4、关闭数据 datagramSocket.close(); } }接收类
public class TalkReceive implements Runnable{ DatagramSocket datagramSocket=null; private int port; private String msgFrom; public TalkReceive(int port,String msgFrom) { this.port=port; this.msgFrom=msgFrom; //1、建立连接 try { datagramSocket=new DatagramSocket(port); } catch (Exception e) { e.printStackTrace(); } } @Override public void run() { while (true){ try { //2、接收数据包 byte[] buffer=new byte[1024]; DatagramPacket datagramPacket=new DatagramPacket(buffer,0,buffer.length); datagramSocket.receive(datagramPacket); //3、断开连接 byte[] data=datagramPacket.getData(); String receiveData=new String(data,0,data.length); System.out.println(msgFrom+": "+receiveData); if(receiveData.equals("bye")){ break; } } catch (Exception e) { e.printStackTrace(); } } datagramSocket.close(); } }用户类1
public class TalkStudent { public static void main(String[] args) { new Thread(new TalkSend(7777,9999,"localhost")).start(); new Thread(new TalkReceive(8888,"小包")).start(); } }用户类2
public class TalkTeacher { public static void main(String[] args){ new Thread(new TalkSend(5555,8888,"localhost")).start(); new Thread(new TalkReceive(9999,"小郎")).start(); } }关注公众号:10分钟编程,让我们每天进步一点点
