一、Java基础知识图解
二、网络编程概述
- Java是Internet上的语言,它从语言级上提供了对网络应用程序的支持,程序员能够很容易开发常见的网络应用程序。
- Java提供的网络类库,可以实现无痛的网络连接,联网的底层细节被隐藏在Java的本机安装系统里,由JVM进行控制。并且Java实现了一个跨平台的网络库,程序员面对的是一个统一的网络编程环境。
- 计算机网络:
把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息、共享硬件、软件、数据信息等资源。
- 网络编程的目的:
直接或间接地通过网络协议与其他的计算机实现数据交换,进行通讯
- 网络编程中有两个主要的问题:
如何准确地定位网络上一台或多台主机:定位主机上的特定应用
找到主机后如何可靠高效地进行数据传输
三、网络通信要素概述
1、如何实现网络中主机互相通信
- 通信双方地址
|- IP
|- 端口号
- 一定的规则(即:网络通信协议。有两套参考模型)
|- OSI参考模型:模型过于理想化,未能在因特网上广泛推广
|- TCP/IP参考模型(或TCP/IP协议):事实上的国际标准
2、网络通信协议
四、通信要素1:IP和端口号
1、IP地址:InetAddress
- 唯一的标识Internet上的计算机(通信实体)
- 本地回环地址(hostAddress):127.0.0.1 主机名(hostName):localhost
- IP地址分类方式1:IPV4(4个字节)和IPV6(16个字节)
- IP地址分类方式2:公网地址(万维网使用)和私有地址(局域网使用)。192.168.开头的就是私有地址,范围即为192.168.0.0—192.168.255.255,专门为组织机构内部使用
-
2、端口号
端口号标识正在计算机上运行的进程(程序)
- 不同的进程有不同的端口号
- 被规定为一个16位的整数0~65535
- 端口分类
|- 公认端口:0~1023。被预先定义的服务通信占用(如:HTTP占用端口80,FTP占用端口21,SSH占用端口22,Telnet占用端口23)
|- 注册端口:1024~49151。分配给用户进程或应用程序。(如:Tomcat占用端口8080,Mysql占用端口3306,Oracle占用端口1521等)。
|- 动态/私有端口:49152~65535。
- 端口号与IP组合得出一个网络套接字:Socket
3、InetAddress类
- Internet上的主机有两种方式表示地址:
|- 域名(hostName):www.atguigu.com
|- IP地址(hostAddress):202.108.35.210
- InetAddress类主要表示IP地址,有两个类:Inet4Address、InetAddress。
- InetAddress类对象含有一个Internet主机地址的域名和IP地址:www.atguigu.com和202.108.35.210。
- 域名容易记忆,当在连接网络时输入一个主机的域名后,域名服务器(DNS)负责将域名转化为IP地址,这样才能和主机建立连接 —域名解析
4、InetAddress类常用方法
- InetAddress类没有提供公关的构造器,而是提供了如下几个静态方法来获取InetAddress实例
|- public static InetAddress getLocalHost()
|- public static InetAddress getByName(String host)
- InetAddress提供了如下几个常用的方法
|- public String getHostAddress():返回IP地址字符串(以文本表现形式)
|- public String getHostName():获取此IP地址的主机名
|- public boolean isReachable(int timeout):测试是否可以达到该地址
// 获取百度ip信息
@Test
public void testInetAddress() throws UnknownHostException {
InetAddress inetAddress = InetAddress.getByName("www.baidu.com");
System.out.println(inetAddress);
System.out.println(inetAddress.getHostName()); // 获取域名
System.out.println(inetAddress.getHostAddress()); // 获取ip
InetAddress localAddress = InetAddress.getLocalHost(); // 获取本地域名和IP
System.out.println(localAddress);
System.out.println(localAddress.getHostAddress());
System.out.println(localAddress.getHostName());
}
五、通信要素2:网络协议
1、网络通信协议概述
- 网络通信协议
计算机网络中实现通信必须有一些约定,即通信协议,对速率、传输代码、代码结构、传输控制步骤,出错控制等制定标准。
- 问题:网络协议太复杂
计算机网络通信协议涉及内容很多,比如指定源地址和目标地址,加密解密,压缩解压缩,差错控制、流量控制、路由控制,如何实现如此复杂的网络协议?
- 通信协议分层的思想
在制定协议时,把复杂成份分解成一些简单的成份,再将他们复合起来。最常见的复合方式是层次方式,即同层间可以通信、上一层可以调用下一层,而与再下一层不发生关系。各层互不影响,利于系统的开发和扩展
2、TCP/IP协议簇
- 传输层协议中有两个非常重要的协议:
|- 传输控制协议TCP(Transmission Control Protocol)
|- 用户数据报协议UDP(User Datagram Protocol)
- TCP/IP以其两个主要协议:传输控制协议(TCP)和网络互联协议(IP)而得名,实际上是一组协议,包括多个具有不同功能且互为关联的协议
- IP(Internet Protocol)协议是网络层的主要协议,支持网间互联的数据通信。
TCP/IP协议模型从更实用的角度出发,形成了高效的四层体系结构,即物理链路层、IP层、传输层和应用层。
3、TCP和UDP
TCP协议:
|- 使用TCP协议前,必须先建立TCP连接,形成数据传输数据通道
|- 传输前,采用“三次握手”方式,点对点通信,是可靠传输
|- TCP协议进行通信的两个应用进程:客户端、服务端
|- 在连接中可进行大数据量的传输
|- 传输完毕,需释放已建立的连接,效率低(四次分手)
- UDP协议:
|- 将数据、源、目的封装成数据包,不需要建立连接
|- 每个数据报的大小限制在64k内
|- 发送方不管对方是否准备好,接收方收到也不确认,故是不可靠的
|- 可以广播发送
|- 发送数据结束时无需释放资源,开销小,速度快
4、Socket
- 利用套接字(Socket)开发网络应用程序早已被广泛的采用,以至于成为事实上的标准。
- 网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标识符套接字。
- 通信的两端都要有Socket,是两台机器间通信的端点。
- 网络通信其实就是Socket间的通信。
- Socket运行程序把网络连接当成一个流,数据在两个Socket间通过IO传输
- 一般主动发起通信的应用程序属客户端,等待通信请求的为服务端。
- Socket分类:
|- 流套接字(stream socket):使用TCP提供可依赖的字节流服务
|- 数据报套接字(datagram socket):使用UDP提供“尽力而为”的数据报服务
5、Socket常用方法
- Socket类的常用构造器:
|- public Socket(InetAddress address, int port):创建一个流套接字并将其连接到指定IP地址的指定端口号
|- public Socket(String host,int port):创建一个流套接字,并将其连接到指定主机上的指定端口号
- Socket类常用的方法:
|- public InputStream getInputStream():返回此套接字的输入流。可以用于接收网络消息
|- public OutputStream getOutputStream():返回此套接字的输出流。可以用于发送网络消息
|- public InetAddress getInetAddress():返回此套接字连接到的远程IP地址:如果套接字未连接,则返回null
|- public int getPort():返回此套接字连接到的远程端口号;如果尚未连接套接字,则返回0.
|- public int getLocalPort():返回此套接字绑定到的本地端口。如果尚未绑定套接字,则返回-1。即本端口的端口号。
|- public void close():关闭此套接字。套接字被关闭后,便不可在以后的网络连接中使用(既无法重新连接或重新绑定)。需要创建新的套接字对象。关闭此套接字也将会关闭该套接字的InputStream和OutputStream。
|- public void shutdownInput():如果在套接字上调用 shutdownInput() 后从套接字输入流读取内容,则流将 返回 EOF(文件结束符)。 即不能在从此套接字的输入流中接收任何数据。
|- public void shutdownOutput():禁用此套接字的输出流。对于 TCP 套接字,任何以前写入的数据都将被发送,并且跟 TCP 的正常连接终止序列(客户端在把传输的数据写入到输出流后,需要调用此方法才能被数据发送给客户端)。 如果在套接字上调用 shutdownOutput() 后写入套接字输出流, 则该流将抛出 IOException。 即不能通过此套接字的输出流发送任何数据。
六、TCP网络编程
1、客户端基于Socket的TCP编程
- Java语言的基于套接字编程分为服务端编程和客户端编程,其通信模型如图所示:
- 客户端Socket的工作过程包含以下四个基本步骤:
|- 创建Socket:根据指定服务端的IP地址或端口号构造Socket类对象。若服务端响应,则建立客户端到服务器的通信线路。若连接失败,会出现异常。
|- 打开连接到Socket的输入/输出流:使用getInputStream()方法获得输入流,使用getOutputStream()方法获得输出流,进行数据传输。
|- 按照一定的协议对Socket进行读/写操作:通过输入流读取服务器放入线路的信息(但不能读取自己放入线路的信息),通过输出流将信息写入线程。
|- 关闭Socket:断开客户端到服务器的连接,释放线路
2、客户端创建Socket对象
- 客户端程序可以使用Socket类创建对象,创建的同时会自动向服务器发器连接。Socket的构造器是:
|- Socket(String host,int port)throws UnknownHostException,IOException:向服务器(域名是 host。端口号为port)发起TCP连接,若成功,则创建Socket对象,否则抛出异常。
|- Socket(InetAddress address,int port)throws IOException:根据InetAddress对象所表示的 IP地址以及端口号port发起连接。
- 客户端建立socketAtClient对象的过程就是向服务器发出套接字连接请求
3、服务端基于Socket的TCP编程
- 服务器程序的工作过程包含以下四个基本步骤:
|- 调用ServerSocket(int port):创建一个服务器端套接字,并绑定到指定端口上。用于监听客户端请求
|- 调用accept():监听连接请求,如果客户端请求连接,则接受连接,返回通信套接字对象
|- 调用该Socket类对象的getOutputStream() 和 getInputStream():获取输出流和输入流,开始网络数据的发送和接收
|- 关闭ServerSocket和Socket对象:客户端访问结束,关闭通信套接字
4、服务端建立ServerSocket对象
- ServerSocket对象负责等待客户端请求建立连接,类似邮局某个窗口的业务员。也就是说,服务器必须事先建立一个等待客户请求建立套接字连接的ServerSocket对象。
- 所谓“接收”客户的套接字请求,就是accept()方法会返回一个Socket对象
// 基于Socket的TCP网络编程——服务端
@Test
public void testServerSocket() throws IOException {
ServerSocket ss = new ServerSocket(9999); // 创建服务端ServerSocket
Socket s = ss.accept(); // 使用服务端ServerSocket的 accept() 方法监控客户端连接,该方法是一个阻塞方法,如果没有客户端连接,将一直等待
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = null;
if ((line = reader.readLine()) != null){
System.out.println(line);
}
OutputStream write = s.getOutputStream();
write.write("欢迎登录".getBytes());
write.flush();
write.close();
reader.close();
s.close();
ss.close();
}// 基于Socket的TCP网络编程——服务端
@Test
public void testServerSocket() throws IOException {
ServerSocket ss = new ServerSocket(9999); // 创建服务端ServerSocket
Socket s = ss.accept(); // 使用服务端ServerSocket的 accept() 方法监控客户端连接,该方法是一个阻塞方法,如果没有客户端连接,将一直等待
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = null;
if ((line = reader.readLine()) != null){
System.out.println(line);
}
OutputStream write = s.getOutputStream();
write.write("欢迎登录".getBytes());
write.flush();
write.close();
reader.close();
s.close();
ss.close();
}
// 基于Socket的TCP网络编程——客户端
@Test
public void testClientSocket() throws IOException {
Socket s = new Socket("127.0.0.1",9999); //创建Socket对象
// 发送数据 start
OutputStream outputStream = s.getOutputStream(); // 创建输出流
outputStream.write("hello".getBytes());
outputStream.flush();
s.shutdownOutput(); //会在流末尾写入一个“流的末尾标记”,对方才能读取到-1,否则对方的方法会一直阻塞
// 发送数据 end
// 接收数据 start
InputStream inputStream = s.getInputStream();
byte [] data = new byte[1024];
int len = 0;
while ((len = inputStream.read(data)) != -1){
System.out.println(new String(data,0,len));
}
// 接收数据 end
inputStream.close();
outputStream.close();
s.close();
}
七、UDP网络通信
1、UDP网络通信概述
- 类DatagramSocket和DatagramPacket实现了基于UDP协议网络程序
- UDP数据报通过数据报套接字DatagramSocket发送和接收,系统不保证UDP数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。
- DatagramPacket对象封装了UDP数据报,在数据报中包含了发送端的IP地址和端口号以及接收端的IP地址和端口号。
UDP协议中每个数据报都给出了完整的地址信息,因此无需建立发送方和接收方的连接。如同发快递包裹一样。
2、DatagramSocket类的常用方法
public DatagramSocket(int port):创建数据报套接字,并将其绑定到本地主机上的指定端口。套接字将被绑定到通配符地址,IP 地址由内核来选择。
- public DatagramSocket(int port,InetAddress laddr):创建数据报套接字,将其绑定到指定的本地地址。本地端口必须在 0 到 65535 之间(包括两者)。如果 IP 地址为 0.0.0.0,套接字将被绑定到通配符地址,IP 地址由内核选择。
- public void close():关闭此数据报套接字。
- public void send(DatagramPacket p):从此套接字发送数据报包。DatagramPacket 包含的信息指示:将要发送的数据、其长度、远程主机的 IP 地址和远程主机的端口号。
- public void receive(DatagramPacket p):从此套接字接收数据报包。当此方法返回时,DatagramPacket 的缓冲区填充了接收的数据。数据报包也包含发送方的 IP 地址和发送方机器上的端口号。 此方法在接收到数据报前一直阻塞。数据报包对象的 length 字段包含所接收信息的长度。如果信息比包的 长度长,该信息将被截短。
- public InetAddress getLocalAddress():获取套接字绑定的本地地址。
- public int getLocalPort():返回此套接字绑定的本地主机上的端口号。
- public InetAddress getInetAddress():返回此套接字连接的地址。如果套接字未连接,则返回 null。
- public int getPort():返回此套接字的端口。如果套接字未连接,则返回 -1。
- public DatagramPacket(byte[] buf,int length)构造 DatagramPacket,用来接收长度为 length 的数据包。 length 参数必须小于等于 buf.length。
- public DatagramPacket(byte[] buf,int length,InetAddress address,int port):构造数 据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。length 参数必须小于等于 buf.length。
- public InetAddress getAddress():返回某台机器的 IP 地址,此数据报将要发往该 机器或者是从该机器接收到的。
- public int getPort():返回某台远程主机的端口号,此数据报将要发往该主机或 者是从该主机接收到的。
- public byte[] getData():返回数据缓冲区。接收到的或将要发送的数据从缓冲区 中的偏移量 offset 处开始,持续 length 长度。
public int getLength():返回将要发送或接收到的数据的长度。
3、UDP网络通信
流程:
(1)DatagramSocket与DatagramPacket
(2)建立发送端、接受端
(3)建立数据包
(4)调用Socket的发送、接收方法
(5)关闭Socket
- 发送端与接受端是两个独立运行程序
八、URL编程
1、URL类
1、URL概述
- URL(Uniform Resource Locator):统一资源定位符,他表示Internet上某一资源的地址。
- 它是一个具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源
- 通过URL我们可以访问Internet上的各种网络资源,比如最常见的www,ftp站点。浏览器通过解析给定的URL可以在网络上查找相应的文件或其他资源。
- URL的基本结构由5部分组成:
<传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表
|- 例如:
http://192.168.1.100:8080/helloworld/index.jsp#a?username=shkstart&password=123
|- #片段名:即瞄点,例如看小说,直接定位到章节
|- 参数列表格式:参数名=参数值&参数名=参数值……
2、URL类构造器
- 为了表示URL,java.net中实现了类URL。我们可以通过下面的构造器来初始化一个URL对象:
|- public URL(String spec):通过一个元素表示URL地址的字符串可以构造一个URL对象。例如:URL url = new URL (“http://www. atguigu.com/“);
|- public URL(URL context, String spec):通过基于URL和相对URL构造一个URL对象。
例如:URL downloadUrl = new URL(url, “download.html”)
|- public URL(String protocol, String host, String file);
例如:new URL(“http”, “www.atguigu.com”, “download. html”);
|- public URL(String protocol, String host, int port, String file);
例如: URL gamelan = new URL(“http”, “www.atguigu.com”, 80, “download.html”);
URL类的构造器都声明抛出非运行时异常,必须要对这一异常进行处理,通常是用try-catch语句进行捕获。
3、URL类常用方法
一个URL对象生成后,其属性是不能被改变,但是可以通过它给定的方法来获取这些属性:
|- public String getProtocol() 获取URL协议
|- public String getHost() 获取该URL的主机名
|- public String getPort() 获取该URL的端口号
|- public String getPath() 获取该URL的文件路径
|- public String getFile() 获取该URL的文件名
|- public String getQuery() 获取该URL的查询名
4、URLConnection类(针对HTTP协议)
- URL的方法openStream():能从网络上读取数据
- 若希望输出数据,例如向服务器端的CGI(公共网关接口-Common Gateway Interface-的简称,是用户浏览器和服务端的应用程序进行连接的接口)程序发送一些数据,则必须先与URL建立连接,然后才能对其进行读写,此时需要使用URLConnection
- URLConnection:表示到URL所引用的远程对象的连接。当与一个URL建立连接时,首先要在一个URL对象通过方法openConnection()生成对应的URLConnection对象。如果连接过程失败,将产生IOExcetion。
|- URL url = new URL(“www.baidu.com”);
|- URLConnection u = url.openConnection();
- 通过URLConnection对象获取输入流和输出流,即可与现有的CGI程序进行交互。
- public Object getContent() throws IOException
- public int getContentLength()
- public String getContentType()
- piblic long getDate()
- public long getLastModified()
- public InputStream getInputStream() throws IOException
- public OutputStream getOutputStream() throws IOException
```java
@Override
public Map submitLogisticsOrder(Map input) throws Exception{
if (log.isErrorEnabled()) {
} //获取访问的url CfgHttpClient cfgHttpClient =(CfgHttpClient) CacheFactory.get(HttpClientCacheImpl.class, “inter_IOperateLogisticsCSV_submitLogisticsOrder”); String urlStr = cfgHttpClient.getUrlAddress(); String data = PartTool.getString(input,”data”,””); Map paramValueMap = CommonDataUtil.getParaDetail(“X”, “LOGISTICS_KEY”, “KEY”); String key = PartTool.getString(paramValueMap,”PARA1”,””); paramValueMap.clear(); paramValueMap = CommonDataUtil.getParaDetail(“X”, “LOGISTICS_CHANNEL_NAME”, “CHANNEL”); String channel = PartTool.getString(paramValueMap,”PARA1”,””); String file = PartTool.getString(input,”file”,””); Map resultMap = new HashMap(); BufferedWriter writer = null; BufferedReader bufferedReader = null; try{log.error("------------OperateLogisticsCSVImpl.submitLogisticsOrder 下物流单 开始-----------");
log.error("------------入参为: " + input.toString() + "---------------------------------");
//URL url = new URL(urlStr); //通过给定的URL字符串创建URL
URL url= new URL(null, urlStr, new sun.net.www.protocol.http.Handler());
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setConnectTimeout(45 * 1000); //连接主机的超时时间,单位毫秒
urlConnection.setReadTimeout(45 * 1000); //读取数据的超时时间,单位毫秒
//@1.1 信任所有https证书
//AESUtil.setTrustAll(urlConnection);
urlConnection.setRequestMethod("POST"); //设置请求方法
urlConnection.setRequestProperty("Connection", "Keep-Alive"); //设置请求头
//必须指定是json
urlConnection.setRequestProperty("Content-Type", "application/json"); //设置请求头
urlConnection.setDoOutput(true); //发送Post请求,setDoOutput()默认是false,需要手动设置为true,完了就可以调用getOutputStream()方法从服务器端获得字节输出流。
urlConnection.connect();
writer = new BufferedWriter(new OutputStreamWriter(urlConnection.getOutputStream(), "UTF-8"));
//参数加密
String content = AESUtil.encodeAESHexUpper(data, key);
//密文做消息摘要
String sign = AESUtil.sign(content, key);
//时间
String dateTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
//生成请求id
String messageId = UUID.randomUUID().toString();
Map<String,String> map = new HashMap<>();
map.put("messageId",messageId);
map.put("channel",channel);
map.put("dateTime",dateTime);
map.put("sign",sign);
map.put("content",content);
//文件内容(base64转码后的),没有可以不传,不需要参与加密
map.put("file",file);
//json工具类可以修改成自己工程使用的
String requestStr = JSON.toJSONString(map);
writer.write(requestStr);
writer.flush();
bufferedReader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(),"UTF-8"));
StringBuilder stringBuilder = new StringBuilder();
String line = null;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line);
}
resultMap = JSONUtil.toMap(stringBuilder.toString());
//下物流单成功后修改物流状态为1:物流单已送物流中心。
if("0".equals(PartTool.getString(resultMap,"returnCode","")))
{ //returnCode=0表示下单成功
Map temp = JSONUtil.toMap(data);
//下单成功则把物流状态改为1,让进程不再扫描这笔订单进行下单操作
String Logistics_state = "1";
Map reqMap = new HashMap();
reqMap.put("ORDER_NUM",PartTool.getString(temp,"orderNo",""));
reqMap.put("CHECK_CODE",PartTool.getString(temp,"checkCode",""));
reqMap.put("LOGISTICS_STATE",Logistics_state);
resultMap.clear();
resultMap = InterUtils.callCsfService("order_IEbOrderOperateCSV_updateOrderStateByOrderNum",reqMap);
}
}catch (Exception e){
log.error("-------------下单失败---------------");
log.error("-------------异常信息:" + e);
}finally {
try {
if (writer != null) {
writer.close();
}
if (bufferedReader != null) {
bufferedReader.close();
}
} catch (Exception e2) {
log.error("数据流关闭失败",e2);
}
}
return resultMap;
}
5、URI、URL和URN的区别
URI,是uniform resource identifier,统一资源标识符,用来唯一的标识一个资源。而URL是uniform resource locator,统一资源定位符,它是一个具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源。而URN,uniform resource name,统一资源命名,是通过名字来标识资源,比如mailto:java-net@java.sun.com。也就是说,URI是一种抽象的,高层次概念定义统一资源标识,而URL和URN则是具体的资源标识方式。URL和URN都是一种URI。
在Java的URI中,一个URI实例可以代表绝对的,也可以是相对的,只要它符合URI的语法规则。而URL类则不仅复合语义,他还包含了定位该资源的信息,因此他不能是相对的。
九、网络编程小结
注:需要详细了解、掌握TCP连接的三次握手与四次分手