网络编程(基础阶段了解即可)
本章大纲
此文只是对初学Java小白的基础篇,设计的网络编程知识以及TCP/IP协议深度较浅,请选择性决定是否阅读,对于HTTP等协议感兴趣并想进一步了解的同学可以阅读《图解HTTP》中文版
- 网络通信要素
- IP和端口号
- 网络协议
- TCP网络编程
- UDP网络编程
网络编程目的
直接或间接地通过网络协议与其他计算机进行数据交换,进行通讯
网络编程的两个问题
- 如何准确的定位网络上的主机,定位到主机的特定的应用
- 找到主机后如何可靠高效地进行数据传输
网络编程中的两个要素
对应问题一:IP和端口号
对应问题二:提供网络通信协议:TCP/IP
参考模型
- 应用层
- 传输层
- 网络层
- 物理+数据链路层
通信要素一:IP和端口号
IP
IP:唯一的标识 Internet上的计算机(通信实体)
在Java中用 InetAddress类代表IP
InetAddress ip1 = InetAddress.getByName("192.168.0.1"); InetAddress ip2 = InetAddress.getByName("www.baidu.com"); InetAddress ip3 = InetAddress.getByName("localhost"); InetAddress ip4 = InetAddress.getLocalHost();
IP分类: IPV4&IPV6 局域网&万维网
域名:www.baidu.com 域名通过DNS解析转到具体的IP地址后再访问
本地回路地址:127.0.0.1 对应域名localhost
端口号
端口号表示正在计算机上运行的进程
- 不同的进程有不同的端口号
- 别规定为一个16位的整数 0-65535
- 端口分类
- 公认端口 0~1023
- 注册端口 1024~49151【如Tomcat-8080 MySQL-3366】
- 动态/私有端口 49153~65535
端口号和IP地址的组合得出一个网络套接字==–Socket–==
Socket
- 通信的两端都要Socket,是两台机器通信的端点
- 网络通信其实就是Socket之间的通信
- Socket允许程序把网络连接当成一个流,数据在两个Socket之间通过IO传输
- 一般主动发起通信的应用程序为客户端,等待的为服务端
- Socket分类
- Stream Socket 流套接字–使用 TCP提供可依赖的字节流服务
- Datagram Socket 数据报套接字–使用 UDP 提供数据服务----- 【只管无脑发送】
通信要素二:网络协议
TCP&UDP
TCP协议 ==传输控制协议
- 使用前必须建立连接
- 传输前,采用三次握手机制【文末解读】
- 可进行大数据量的传输
- 传输完毕需要关闭资源,效率低
UDP协议 用户数据报协议
- 不需要建立连接,将数据、源、目的封装成数据包
- 每个数据包的大小限制为64k
- 发送接受不确认,不可靠
- 无需释放资源,开销小,效率高
基于TCP的网络编程
java实现步骤
客户端:
- 创建socket对象,指明服务器端的IP和端口号
- 获取输出流,输出数据
- 资源关闭
服务器端:
- 创建ServerSocket对象,指明自己的端口号
- 调用accept方法接受客户端的socket
- 获取输入流,读取数据
- 关闭资源
代码实现
附文章最后
感兴趣的同学可以自行编写下面两个题目,我将在后续的blog中更新答案
- 服务端读取图片发送给客户端,客户端保存到本地
- 客户端给服务端发送文本,服务端将文本大写后返回给客户端
基于UDP网络编程
- 类DatagramSocket 和 DatagramPacket 实现了基于UDP 协议的编程
- UDP数据包通过套接字DatagramSocket发送和接受,但不安全可靠
- DatagramPacket对象封装了UDP数据报,在数据报中发送了包含发送端IP地址和端口号以及接收端地址和端口号
- UDP协议中,每个数据报都给出了完整的地址信息,so无需建立发送和接受
代码实现附文末
名词解释
三次握手 用于传输资源前的连接确认
客户端 A 服务器端 B
A:我来了,我要开始了
B:你来吧
A:那我来了
四次挥手 用于关闭连接
A:我要走
B:好的
B:你走了吗
A:我走了
TCP编程代码实现
基础Demo1
此处代码使用JUnit,也可以分开单独写成两个类
public class TcpDemo1 { @Test public void ServerTask (){ ServerSocket serverSocket =null; Socket sss = null; InputStream isr = null; ByteArrayOutputStream baos = null; try{ //1. 调用ServerSocket(int port) 创建套接字,绑定到指定端口,开始监听,此处为了关闭资源安全,将其提前声明在try catch外 serverSocket = new ServerSocket(8899); //2. 调用accpet监听请求连接,如果客户端请求连接,则接受,反之,返回套接字对象 sss = serverSocket.accept(); //调用socket类的输入流 isr = sss.getInputStream(); baos = new ByteArrayOutputStream(); byte []cache = new byte[3]; int readnum = 0; while ((readnum=isr.read(cache))!=-1){ baos.write(cache,0,readnum); } System.out.println(baos.toString()); System.out.println(); }catch(Exception e){ e.printStackTrace(); System.out.println("Exception"); }finally { try{//4. 关闭流,一定要释放资源 baos.close(); isr.close(); sss.close(); serverSocket.close(); }catch(Exception e){ e.printStackTrace(); System.out.println("Exception"); } } } @Test public void ClientTask(){ Socket socket = null; OutputStream os = null; try{ //1. 创建socket 绑定IP和端口号 socket = new Socket("localhost",8899); //2. 打开socket的流 此处为输出流 os = socket.getOutputStream(); //3. 进行流的读写操作 os.write("hello world".getBytes()); }catch(Exception e){ e.printStackTrace(); System.out.println("Exception"); }finally { try{ //4. 关闭流,一定要释放资源 socket.close(); os.close(); }catch(Exception e){ e.printStackTrace(); System.out.println("Exception"); } } } }
Dem2
//此案例容易bug的地方位于,read为阻塞方法,具体在代码中有解释 //客户端发送文件给服务端,服务端保存在本地,并返回成功语句 public class TcpPrintConsle { @Test public void Server1(){ ServerSocket sscoket1 = null; Socket socket1 = null; InputStream is = null; FileOutputStream fos = null; OutputStream os = null; try{ sscoket1 = new ServerSocket(9999); socket1 = sscoket1.accept(); is = socket1.getInputStream(); fos = new FileOutputStream("c:/tcp.jpg"); byte []cache = new byte[1024]; int readnum = 0 ; while ((readnum=is.read(cache))!=-1){ fos.write(cache,0,readnum); } //----发送成功的消息返回客户端----- os = socket1.getOutputStream(); os.write("接收成功".getBytes()); }catch(Exception e){ e.printStackTrace(); System.out.println("Exception"); } finally { try{ is.close(); socket1.close(); sscoket1.close(); }catch(Exception e){ e.printStackTrace(); System.out.println("Exception"); } } } @Test public void Client1(){ try{ Socket socket = new Socket("127.0.0.1",9999); OutputStream os = socket.getOutputStream(); FileInputStream fis = new FileInputStream("c:/test.png"); int readnum = 0 ; byte []cache = new byte[1024]; while ((readnum=fis.read(cache))!=-1){ os.write(cache,0,readnum); } //告诉服务器已经结束传输,不然服务器会一直处于read状态,一直while循环中 socket.shutdownOutput(); //========接收 ByteArrayOutputStream baos = new ByteArrayOutputStream(); InputStream is = socket.getInputStream(); int len = 0; byte []buffer = new byte[1024]; while ((len=is.read(buffer))!=-1){ baos.write(buffer,0,len); } System.out.println(baos.toString()); os.close(); fis.close(); socket.close(); }catch(Exception e){ e.printStackTrace(); System.out.println("Exception"); }finally { } } }
UDP编程代码实现
@Test //发送端 public void Sender(){ DatagramSocket ds = null; try{ ds = new DatagramSocket(); byte []txt = "hello world ".getBytes(); DatagramPacket dp = new DatagramPacket(txt,0,txt.length, InetAddress.getByName("127.0.0.1"),6868); ds.send(dp); }catch(Exception e){ e.printStackTrace(); System.out.println("Exception"); }finally { try{ ds.close(); }catch(Exception e){ e.printStackTrace(); System.out.println("Exception"); } } }
@Test //接收端 public void Receiver(){ DatagramSocket ds = null; try{ ds = new DatagramSocket(6868); byte []txt = new byte[1024]; DatagramPacket dp = new DatagramPacket(txt,txt.length); ds.receive(dp); String str = new String(dp.getData(),0,txt.length); System.out.println(str); }catch(Exception e){ e.printStackTrace(); System.out.println("Exception"); } }