网络编程主要有两个问题,1.如何定义一台或多台主机?2.找到主机后如何进行通信?
对问题1需要得到某台主机的IP地址和端口,不同进程有不同端口,用来区分软件,端口号规定范围:0~65535,在单个协议下(TCP/UDP),端口号是不能重复的
对问题2主要有TCP和UDP两种方式进行计算机间通信。TCP/IP 中有两个具有代表性的
传输层协议
,分别是 TCP 和 UDP。下面这篇博客对TCP,UDP特点,区别,应用场景有详细介绍:
解决问题1:学习InetAddress和InetSocketAddress两个类
InetAddress类
import java.net.InetAddress; import java.net.UnknownHostException; import java.sql.SQLOutput; public class TestInetAddress { public static void main(String[] args) throws UnknownHostException { //获取本地的一个InetAddress对象 InetAddress ia1=InetAddress.getLocalHost(); System.out.println(ia1); //通过IP获取一个InetAddress对象 InetAddress ia2=InetAddress.getByName("127.0.0.1"); System.out.println(ia2); //通过域名获取一个InetAddress对象 InetAddress ia3=InetAddress.getByName("www.baidu.com"); System.out.println(ia3); /**获取InetAddress类对象的三种方式: 1.通过getLocalhost获取本机InetAddress对象 2.通过getByName方法并传入IP地址 3.通过getByName方法并传入域名 */ //常用的两个方法 sout(ia1.getHostAddress()) //获取ia1的ip地址 sout(ia1.getHostName()) //获取ia1的主机名 } }
InetSocketAddress类
InetSocketAddress和InetAddress类的主要区别是有端口的设置
import java.net.InetSocketAddress; public class TestInetSocketAddress { public static void main(String[] args) { //通过localhost创建对象 InetSocketAddress isa1=new InetSocketAddress("localhost",9090); System.out.println(isa1); //通过IP创建对象 InetSocketAddress isa2=new InetSocketAddress("127.0.0.1",9090); System.out.println(isa2); System.out.println(isa1.getAddress());//获取isa1的IP System.out.println(isa1.getPort());//获取端口 System.out.println(isa1.getHostName());//获取isa1的主机名
}
}
解决问题2:方式一TCP通信
TCP通信只能实现一对一传信息,发送文件等,有明确的客户端和服务器,连接比udp更加稳定,但是效率比udp低,连接涉及三次握手,断开连接涉及四次挥手
三次握手:
客户端首先向服务器发送连接请求,这是第一次握手;服务器做出应答并进入等待状态,这是第二次握手;客户端发送信号给服务器,建立连接成功,这是第三次握手。
四次挥手:
断开时,客户端向服务器发断开请求,这是第一次挥手;服务器收到请求后让应用层要释放 TCP 链接。然后会发送 ACK 包,并进入等待,这是第二次挥手;服务器有未发送的信息会在这时再次发送,完毕后会向 A 发送连接释放请求,这是第三次挥手;客户端发送最后信号确认断开给服务端,断开成功,这是第四次挥手。
TCP应用1:客户端给服务器发送消息
万物皆对象,创建两个类,客户端类和服务器类
客户端:
import java.io.OutputStream; import java.net.InetAddress; import java.net.Socket; public class TcpClientDemo1 { public static void main(String[] args) throws Exception{ //1.要知道服务器的端口,ip InetAddress serverIP=InetAddress.getByName("localhost"); int port=9999; //2.要建立一个socket连接 Socket s=new Socket(serverIP,port); //3.输出流要传递的信息 OutputStream os=s.getOutputStream(); os.write("你好啊,服务器!".getBytes());//字符串是无法传递的,需要getBytes()变成字节流 //4.关闭资源 s.close(); os.close(); } }
服务器:
import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; public class TcpServerDemo1 { public static void main(String[] args) throws Exception { //1.首先服务器要有一个地址 ServerSocket serverSocket=null; serverSocket=new ServerSocket(9999); //2.等待客户端连接过来 Socket socket=null; socket=serverSocket.accept(); //3.读取客户端发来的信息 InputStream is=socket.getInputStream(); //4.将收到的字节流放入缓冲区 ByteArrayOutputStream baos=new ByteArrayOutputStream(); byte[] buffer=new byte[1024]; int len; while((len=is.read(buffer))!=-1){//读取is里的信息放入buffer baos.write(buffer,0,len);//将buffer里的信息写到baos } //5.输出接收的信息 System.out.println(baos.toString()); //6.关闭资源 socket.close(); is.close(); baos.close(); serverSocket.close(); } }
TCP应用2:客户端给服务器传文件
万物皆对象,创建两个类,客户端类和服务器类
客户端:
import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.Socket; public class TcpClientDemo2 { public static void main(String[] args) throws Exception { //1.要知道服务器的端口,ip InetAddress serverIP=InetAddress.getByName("localhost"); int port=9999; //2.要建立一个socket连接 Socket s=new Socket(serverIP,port); //3.读取要传给服务器的图片文件 FileInputStream fis=new FileInputStream(new File("src/go.jpg")); //4,将文件输入流放入缓冲区并通过os输出 //创建一个输出流 OutputStream os=s.getOutputStream(); byte[] buffer =new byte[1024];//缓冲区 int len; while ((len=fis.read(buffer))!=-1){//从哪读,固定代码 os.write(buffer,0,len);//信息都写入了os,os相当于载体 } s.close(); fis.close(); os.close(); } }
服务器:
import java.io.FileOutputStream; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; public class TcpServerDemo2 { public static void main(String[] args) throws Exception{ //1.首先服务器要有一个地址 ServerSocket serverSocket=null; serverSocket=new ServerSocket(9999); //2.等待客户端连接过来 Socket socket=null; socket=serverSocket.accept(); //3.读取客户端发来的信息 InputStream is=socket.getInputStream(); //4,将收到的is放入缓冲区,并写入文件输出流fos FileOutputStream fos=new FileOutputStream("receive1.jpg");//如果是输出信息用baos流,此题输出文件流用fos byte[] buffer=new byte[1024]; int len; while((len=is.read(buffer))!=-1){ fos.write(buffer,0,len); } //5.关闭资源 socket.close(); serverSocket.close(); is.close(); fos.close(); } }
左侧文件夹多出了receive1.jpg说明传输成功了
解决问题2:方式二UDP通信
UDP应用1:单方面发送消息
客户端
import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.Socket; public class UdpClientDemo1 { public static void main(String[] args) throws Exception{ //1.要知道对方的端口,ip InetAddress serverIP=InetAddress.getByName("localhost"); int port=9999; //2.要建立一个socket连接,这里与TCP用的不同类 DatagramSocket s=new DatagramSocket(); //3.建立包并打包好 String msg="你好啊,对面的朋友!";//信息 DatagramPacket packet=new DatagramPacket(msg.getBytes(),0,msg.getBytes().length,serverIP,port);//把信息打成包 //4.发送包 s.send(packet); //5.关闭资源 s.close(); } }
服务器端
import java.net.DatagramPacket; import java.net.DatagramSocket; public class UdpServerDemo1 { public static void main(String[] args) throws Exception{ //1.开放端口,不用ip了,因为这个例子是用本机测试,client会把包发给localhost DatagramSocket socket=new DatagramSocket(9999); //2.接受包,并输出包裹内容 byte[] buffer=new byte[1024];//将包放到缓冲区 DatagramPacket packet=new DatagramPacket(buffer,0,buffer.length);//这里需要新建一个packet空间来接收 socket.receive(packet);//阻塞接收 System.out.println(packet.getAddress().getHostName()); System.out.println(new String(packet.getData(),0, packet.getLength()));//输出包裹内容 //3.关闭资源 socket.close(); } }
UDP应用2:单方面不断发送消息
物皆对象,创建两个类,Sender类和Receiver类
Sender类:
import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.util.Scanner; //UDP聊天实现 public class UdpSenderDemo1 { public static void main(String[] args) throws Exception { //1.要知道对方的端口,ip InetAddress serverIP=InetAddress.getByName("localhost"); int port=6666; //2.要建立一个socket连接,这里与TCP用的不同类 DatagramSocket s=new DatagramSocket(); //3.准备发送消息,控制台输入 BufferedReader reader=new BufferedReader(new InputStreamReader(System.in)); //4.不断读取数据,将数据打包 while (true){ String data=reader.readLine();//读取数据 DatagramPacket packet=new DatagramPacket(data.getBytes(),0,data.getBytes().length,serverIP,port); s.send(packet); if(data.equals("bye")){break;} } //5.关闭资源 s.close(); } }
Receiver类
import java.net.DatagramPacket; import java.net.DatagramSocket; public class UdpReceiverDemo1 { public static void main(String[] args) throws Exception{ //1.开放端口 DatagramSocket socket=new DatagramSocket(6666); //2.不断接受包,并输出包裹内容 while (true){ byte[] buffer=new byte[1024];//将包放到缓冲区 DatagramPacket packet=new DatagramPacket(buffer,0,buffer.length);//这里需要新建一个packet空间来接收 socket.receive(packet);//阻塞接收 String receiveData=new String(packet.getData(),0, packet.getLength()); System.out.println(receiveData);//输出包裹内容 if(receiveData.equals("bye")){break;} } //3.关闭资源 socket.close(); } }
UDP应用3:双方都能发送和接受消息(即聊天实现)
万物皆对象:
双方都能够发送和接受信息,因此要有一个Send类和一个Receive类
双方都能同时操作,因此双方是两个线程
Send类
import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetSocketAddress; import java.net.SocketException; public class TalkSend1 implements Runnable { //1.定义基本的信息 private int toPort; private int localPort; private String toIP; //2.要建立一个socket连接 DatagramSocket s = null; public TalkSend1(int localPort, String toIP, int toPort) { this.toPort = toPort; this.localPort = localPort; this.toIP = toIP; //2.向s传参数localPort try { s = new DatagramSocket(localPort); } catch (SocketException e) { throw new RuntimeException(e); } } @Override public void run() { //3.准备发送消息,控制台输入 BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); while (true) { try { //4.创建一个包裹 String data = reader.readLine();//读取数据字符串 DatagramPacket packet = new DatagramPacket(data.getBytes(), 0, data.getBytes().length, new InetSocketAddress(this.toIP, this.toPort)); //5.发送包裹 s.send(packet); if (data.equals("bye")) { break; } } catch (Exception e) { e.printStackTrace(); } } //6.关闭资源 s.close(); } }
Receive类
import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException; public class TalkReceive1 implements Runnable { //1.定义基本的信息 private int localPort2; private String msgFrom; //2.要建立一个socket连接 DatagramSocket s=null; public TalkReceive1(int localPort2, String msgFrom) { this.localPort2 = localPort2; this.msgFrom = msgFrom; try {//2.向s传参数localPort2 s = new DatagramSocket(localPort2); } catch (SocketException e) { throw new RuntimeException(e); } } @Override public void run() { while (true) { try { //3.准备接受包裹 byte[] buffer = new byte[1024]; DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length); s.receive(packet); //4.输出包裹内容(理解为拆包裹) String receiveData = new String(packet.getData(), 0, packet.getData().length); System.out.println(msgFrom + ":" + receiveData.trim()); if (receiveData.equals("bye")) { break; } } catch (Exception e) { e.printStackTrace(); } } //5.关闭资源!!! s.close(); } }
启动学生线程:
public class TalkStudent { public static void main(String[] args) throws Exception { //开启两个线程 new Thread(new TalkSend1(1111,"localhost",2222)).start();//学生作为发送人时,fromport是发件人的端口,toport是接收人的端口 new Thread(new TalkReceive1(3333,"老师")).start();//学生作为接收人时 } }
启动老师线程:
public class TalkTeacher { public static void main(String[] args) throws Exception { //开启两个线程 new Thread(new TalkSend1(4444,"localhost",3333)).start(); new Thread(new TalkReceive1(2222,"学生")).start(); } }
这里遇到一个错误是Address already in use:cannot find
原因是我开始写receive1类的时候忘记添加关闭资源语句,以及我在写send1类的时候开始是把 DatagramSocket s的定义放在run()方法里,为了防止程序找不到s,我把s.close()放在了while循环内catch语句后。
重新调整了之后就不会报错了,借这个错误认识到了关闭资源的重要性。