-----------android培训、java培训、java学习型技术博客、期待与您交流! ------------
一、网络编程:
1、为什么需要网络编程技术?
IO技术的存在是为了更加便捷的操作数据,但是存在问题那就是对数据的操作只限于本机内数据操作,通俗的话就是说你玩的都是单机,不能和其他用户共享数据,也就是说不能把本机上的数据发给其他的电脑上的应用程序,打个比方说,将QQ比作文件数据,QQ1写下信息通过流发给QQ2,QQ2写下信息再通过流发给QQ1,实现QQ间信息交换,但是QQ1和QQ2都存在在本机中,难道天天没事和自己聊天玩?
网络编程技术的存在就是解决了以上数据不能在电脑间的数据传输问题,也就是说你的QQ信息可以发给其他的人了。
2、网络编程技术需要什么?
网络编程技术是为了实现数据在电脑间的数据传输,那需要什么条件才能实现以上的功能的?
第一:有网络存在,通俗的来说,没有网络存在玩的永远是单机
第二:应用程序,打个比方,张三的电脑上安装了QQ,李四的电脑上没有装QQ,张三说我现在要给李四的电脑发送QQ消息,我的QQ占用的端口号是10000我把消息通过网络发给李四电脑10000端口号上,但是李四电脑上没装QQ,10000端口号上可能是其他程序或者什么程序都没有,李四就啥也接受不到。
3、什么是网络编程?
个人理解:针对网络传输的编程技术。
4、网络编程需要了解哪些东西?
(1)网络模型
(2)网络通讯要素
二、什么是网络模型?
网络模型分为OSI参考模型和TCP/IP参考模型
1、OSI参考模型:
OSI参考模型的全称是开放系统互连参考模型,它是由国际化标准组织ISO提出的一个网络系统互连模型。ISO组织把网络通讯工作分为7层,一到四层被认为是低层, 这些层与数据移动密切相关,5到7层是高层,包含应用程序级的数据,每一层负责一项具体的工作,然后把数据交给下一层。把用户程序作为最高层,把物理线程作为最低层,将其间的协议分为若干层,规定每层处理的任务,也规定每层的接口标准。
但OSI模型目前只用于教学理解,在实际使用中,网络硬件设备中基本参考的都是TCP/IP模型。可以把TCP/IP模型想象成OSI的简化版本
2、TCP/IP参考模型:
三、网络通讯要素
1、网络通讯要素有哪些?
IP地址、端口号、传输协议
2、怎么样理解网络通讯要素?
拿QQ聊天举例子,你要把QQ消息发给哪台电脑首先要找到那台电脑的IP号,通过IP号建立两台电脑之间的联系,联系建立好之后那QQ消息发送到电脑哪里呢?电脑为每个应用程序都分配了个端口号,比如QQ占用了电脑10000端口号,那么10000端口号的程序就是QQ,端口号起到标识的作用,联系建立好之后那QQ消息发送到指定IP电脑的指定端口上,对方电脑上10000端口的QQ就接受到了QQ消息,在传输过程中必须要遵循传输协议(TCP/IP协议)否则信息会传送失败。
3、在java中怎么样获取IP地址?
InetAddress对象中getLocalHost()方法和getByName(“192.168.1.152”)方法
例如:InetAddress id= InetAddress . getByName(“192.168.1.152”);
System.out.println(id.getHostAddress()); //打印IP地址
System.out.println(id.getHostName()); //打印主机名
4、什么是端口号?
(1)用于标识进程逻辑地址。为了标示不同的应用程序,所以给这些网络应用程序都用数字进行表示,这个表示就叫端口。
(2)有效的端口:0~65535 ,其中0~1024被系统使用或保留端口
5、常用的传输协议有哪些?
UDP和TCP
四、TCP与UDP
1、概念:
(1)TCP (Transmission Control Protocol)
是指传输控制协议,它和IP协议一起组成TCP/IP协议,TCP协议协议负责数据或文件的拆包与封包,而IP协议负责发送和接收数据包。在简化的计算机网络OSI模型中,它完成第四层传输层所指定的功能,TCP是一种面向连接的、可靠的、基于字节流的运输层通信协议。也就是说只有建立了可靠的连接才能进行通信,因为他们是结果三次“握手”的过程,是指先发送一次包,然后等待对方确认收到并回复一次包,最后在开始发送数据包。TCP协议就像生活中的打电话,A想要和B联系,A就先拨通B的号码,然后等待B接电话,如果B接听了就表示收到消息了,然后A才会开始和B说话,这就使用了TCP通信。还有如视频聊天、拨打网络电话、固定电话等都是TCP通信,双方都需要确认数据是否完全发送。
(2)UDP (User Datagram Protocol)
是指用户数据报协议,UDP是与TCP同一层内另一个重要的传输协议。它也跟IP协议一起使用,但他不对连接状态与数据丢失做检查,只保证数据发送出去,不管对方是否收到。生活中常见的就是对讲机,这就是TCP网络通信协议,它一般是不关闭对讲机的,需要时直接说话,如果对方听到就可以进行通信,听不到,你也就白说了。还有如平时我们用的QQ聊天,MSN,发邮件等,他们都可以离线发送消息,不管对方是否收到。
(3)java对TCP和UDP协议提供了强有力的支持,分别引入了Socket类和DatagramSocket类,来解决两个不同协议的网络编程。
2、UDP和TCP的区别:
分类比较:
UDP
(1)是否建立连接:将数据及源和目的封装到数据包中,不需要建立连接
(2)传输数据量:每个数据包的大小在限制在64k内,少量数据
(3)传输可靠性:因不需要建立连接,是不可靠协议
(4)传输速度:因为不需要建立连接,速度快
TCP
(1)是否建立连接:建立连接,形成传输数据的通道。
(2)传输数据量:在连接中进行大数据量传输
(3)传输可靠性:通过三次握手完成连接,是可靠协议
(4)传输速度:必须建立连接,效率会稍低
注:三次握手:
第一次:我问你在么?
第二次:你回答在。
第三次:我反馈哦我知道你在。
单项比较:
(1)是否建立连接:
1) UDP不需要建立连接,将数据及源和目的封装到数据包中;
2) TCP建立连接,形成传输数据的通道。
(2)传输数据量:
1) UDP每个数据包的大小在限制在64k内,少量数据
2) TCP在连接中进行大数据量传输
(3)传输可靠性:
1) UDP因不需要建立连接,是不可靠协议
2) TCP通过三次握手完成连接,是可靠协议
(4)传输速度:
1) UDP因为不需要建立连接,速度快
2) TCP必须建立连接,效率会稍低
五、端口与套接字
1、端口(Port)
这里说的端口并非真实存在的,如果把IP地址比作一间房子,端口就是出入这间房子的门。真正的房子只有几个门,但是一个IP地址的端口可以有65536(即:2^16)个之多!端口是通过端口号来标记的,端口号只有整数,范围是从0 到65535(2^16-1)。其实计算机上的1-1023之间的端口都被系统占用了,因此在定义自己的端口时,不能使用这一段端口号,应该使用1024-65535之间的任意端口,但也别使用其他软件已经使用的端口号。
2、套接字(Socket)
套接字,是支持TCP/IP的网络通信的基本操作单元,可以看作是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。简单的举例说明:Socket = Ip address + TCP/UDP port(IP地址+端口号)。
个人理解可以将Socket 理解成码头上的控制塔,记录着各码头的具体地址;控制着各个码头的开关和连接,船的停靠,以及货物的装载和卸下方式。A城市的码头将货物运到B码头通过河流运输,A城市电脑A,B城市电脑B,码头:端口,货物就是数据,河流就是IO流
在java API中将套接字抽象化称为Socket类,所以程序只需创建该类的对象,就能使用套接字。
那么java就是使用Socket类的流对象进行数据传输的,该流分为输入流和输出流。
六、服务器和客户端
为了使两台计算机之间能够进行通信,必须为这两台计算机建立一个网络,将这两台计算机进行连接,把一台作为客户端,一台作为服务器。
客户端:是指发送请求信息的计算机或程序。
服务器:是指能提供信息的计算机或程序。
但有时候两台计算机都是相互发送信息,相互接收信息的,所以很难区分。
为了保证能将两台或更多的计算机之间能进行通信,必须有某种相互遵守的条约,这就是协议。
例如互联网使用IP协议,这种协议使用4个字节来标识网络中的一台计算机。
在公司内部局域网中,每台机器都有一个IP地址如192.168.0.1,这就是IP协议地址,在一个网段中它必须是唯一的。
七、IP地址 InetAddress
1、定义:
InetAddress 此类表示互联网协议 (IP) 地址,是与IP相关的类。
IP 地址是 IP 使用的 32 位或 128 位无符号数字,
它是一种低级协议,UDP 和 TCP 协议都是在它的基础上构建的。
InetAddress 的实例包含 IP 地址,
还可能包含相应的主机名(取决于它是否用主机名构造或者是否已执行反向主机名解析)。
2、主机名解析
主机名到IP地址的解析:
通过使用本地机器配置信息和网络命名服务,如域名系统DNS和网络信息服务NIS来实现。
要使用的特定命名服务默认情况下是本地机器配置的那个,对于任何主机名称,都返回其相应的IP地址。
反向名称解析:
意味着对于任何IP地址,都返回与IP地址关联的主机。
3、InetAddress缓存
InetAddress类具有一个缓存,用于存储成功及不成功的主机名解析。
默认情况下,当为了防止 DNS 哄骗攻击安装了安全管理器时,正主机名解析的结果会永远缓存。
当未安装安全管理器时,默认行为将缓存一段有限(与实现相关)时间的条目。
不成功主机名解析的结果缓存非常短的时间(10 秒)以提高性能。
4、此类常用的方法:
getHostName() 获取此IP地址的主机名。
getHostAddress() 返回IP地址字符串(以文本表现形式)。
SgetCanonicalHostName()获取此IP地址的完全限定域名
静态方法:(返回:static InetAddress)
getLocalHost() 返回本地主机的InetAddress对象
getByName(String host) 在给定主机名的情况下获取主机的IP地址
getByAddress(byte[] addr)在给定原始IP地址的情况下,返回InetAddress对象
getByAddress(String host, byte[] addr) 根据提供的主机名和IP地址创建InetAddress对象
5、代码示例:获取指定主机信息
import java.net.*;
class InetAddressDemo {
public static void sop(Object obj) {
System.out.println(obj);
}
public static void main(String[] args) throws Exception {
// 获取本地主机
InetAddress i = InetAddress.getLocalHost();
sop("本地主机:" + i);
sop("完全限定域名:" + i.getCanonicalHostName()); // 获取此IP地址的完全限定域名
sop("IP地址的哈希码:" + i.hashCode()); // 返回此 IP 地址的哈希码。
sop("IP地址的主机名:" + i.getHostName()); // 获取此 IP 地址的主机名
sop("IP地址的字符串:" + i.getHostAddress()); // 返回 IP 地址字符串(以文本表现形式)。
// 获取网络上指定主机的IP地址
InetAddress ia = InetAddress.getByName("www.baidu.com");
sop("\n正在获取“www.baidu.com”的信息");
sop("完全限定域名:" + ia.getCanonicalHostName()); // 获取此IP地址的完全限定域名
sop("IP地址的哈希码:" + ia.hashCode()); // 返回此 IP 地址的哈希码。
sop("IP地址的主机名:" + ia.getHostName()); // 获取此 IP 地址的主机名
sop("IP地址的字符串:" + ia.getHostAddress()); // 返回 IP 地址字符串(以文本表现形式)。
sop("是否能连接主机:" + ia.isReachable(5000)); // 测试指定时间内(毫秒)是否可以达到该地址
sop("将IP转为字符串:" + ia.toString()); // 将此 IP 地址转换为 String。
sop(ia.isSiteLocalAddress()); // 检查 InetAddress 是否是站点本地地址的实用例行程序
}
}
/*
* 结果(只粘贴www.baidu.com部分的信息) 正在获取“www.baidu.com”的信息 完全限定域名:115.239.210.27
* IP地址的哈希码:1945096731 IP地址的主机名:www.baidu.com IP地址的字符串:115.239.210.27
* 是否能连接主机:false 将IP转为字符串:www.baidu.com/115.239.210.27 false
*/
八、UDP程序设计
1、类 DatagramSocket
此类表示用来发送和接收数据报包的套接字。数据报套接字是包投递服务的发送或接收点。
每个在数据报套接字上发送或接收的包都是单独编址和路由的。
从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。
在DatagramSocket上总是启用UDP广播发送。
为了接收广播包,应该将 DatagramSocket 绑定到通配符地址。
在某些实现中,将 DatagramSocket 绑定到一个更加具体的地址时广播包也可以被接收。
构造方法:
DatagramSocket() 构造数据报套接字并将其绑定到本地主机上任何可用的端口。
DatagramSocket(int port) 创建数据报套接字并将其绑定到本地主机上的指定端口。
DatagramSocket(int port, InetAddress laddr) 创建数据报套接字,将其绑定到指定的本地地址。
DatagramSocket(SocketAddress bindaddr) 创建数据报套接字,将其绑定到指定的本地套接字地址。
构造方法中的参数:
port - 要使用的端口。
laddr - 要绑定的本地地址
bindaddr - 要绑定的本地套接字地址,对于未绑定的套接字为 null。
方法:
close() 关闭此数据报套接字。
getPort() 返回此套接字的端口。
isBound() 返回套接字的绑定状态。
isClosed() 返回是否关闭了套接字。
disconnect() 断开套接字的连接。
isConnected() 返回套接字的连接状态。
getLocalPort() 返回此套接字绑定的本地主机上的端口号。
getInetAddress() 返回此套接字连接的地址。
send(DatagramPacket p) 从此套接字发送数据报包
getLocalSocketAddress() 返回此套接字绑定的端点的地址,如果尚未绑定则返回 null。
getRemoteSocketAddress() 返回此套接字连接的端点的地址,如果未连接则返回 null。
receive(DatagramPacket p) 从此套接字接收数据报包。
connect(SocketAddress addr) 将此套接字连接到远程套接字地址(IP 地址 + 端口号)。
connect(InetAddress address, int port) 将套接字连接到此套接字的远程地址。
------------------------------------------------------------------------------------------------------------------
2、类 DatagramPacket
此类表示数据报包。
数据报包用来实现无连接包投递服务。每条报文仅根据该包中包含的信息从一台机器路由到另一台机器。
从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。不对包投递做出保证。
构造方法:
DatagramPacket(byte[] buf, int length)
构造 DatagramPacket,用来接收长度为 length 的数据包。
DatagramPacket(byte[] buf, int offset, int length)
构造 DatagramPacket,用来接收长度为 length 的包,在缓冲区中指定了偏移量。
DatagramPacket(byte[] buf, int length, SocketAddress address)
构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。
方法:
getPort() 返回某台远程主机的端口号。int
getData() 返回数据缓冲区。byte[]
getLength() 返回将要发送或接收到的数据的长度。int
getOffset() 返回将要发送或接收到的数据的偏移量。int
getAddress() 返回某台机器的IP地址。InetAddress
getSocketAddress() 获取要将此包发送到的或发出此数据报的远程主机的SocketAddress。
setData(byte[] b) 为此包设置数据缓冲区。
setLength(int len) 为此包设置长度。
setPort(int iport) 设置要将此数据报发往的远程主机上的端口号。
setAddress(InetAddress iaddr) 设置要将此数据报发往的那台机器的 IP 地址。
setData(byte[] buf,int off,int len) 为此包设置数据缓冲区。
setSocketAddress(SocketAddress address) 设置要将此数据报发往的远程主机的SocketAddress。
代码示例1:UDP发送端
需求:通过UDP传输方式,将一段文字发送出去。
思路:
(1)建立DatagramSocket服务;
(2)提供数据,并将数据封装到字节数组中;
(3)创建DatagramPacket数据包,并把数据封装到包中,同时指定IP和接收端口
(4)通过Socket服务的发送功能,利用send方法将数据包发送出去;
(5)关闭DatagramSocket和DatagramPacket服务。
构造函数:
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
创建数据包,将指定长度的包发送到指定主机上的指定端口号。
import java.net.*;
class UdpSend {
public static void main(String[] args) throws Exception {
// 创建UDP套接字绑定指定端口。不指定将随机分配。
DatagramSocket ds = new DatagramSocket(8888);
// 创建数组缓冲区,并指定数据
byte[] buf = "hello,你好".getBytes();
// 创建数据包对象--把缓冲区中的数据发送到指定主机的指定端口号。
DatagramPacket dp = new DatagramPacket(buf, buf.length,
InetAddress.getByName("192.168.0.100"), 10000);
System.out.println("发送成功");// 测试
// 通过套接字的send方法把数据包发送出去,然后关闭套接字。
ds.send(dp);
ds.close();
}
}
代码示例2:UDP接收端
需求:接收udp协议传输的数据并处理。
思路:
(1)建立DatagramSocket服务,并监听一个端口(其实就是给这个接收网络应用程序定义数字标识, 方便明确使用什么应用程序来处理数据);
(2)定义一个字节数组和一个数据包,同时将数组封装进数据包,因为要存储接收到的字节数据;
(3)通过DatagramSocket的receive方法,将接收的数据存入定义好的数据包中;
(4)通过数据包对象的特有功能,将这些不同的数据提取出来。打印在控制台。
(5)通过DatagramPacket关闭的方法,获取发送数据包中的信息;
(6)关闭DatagramSocket和DatagramPacket服务。
通过String类的构造方法来获取数据包中的数据:
String(byte[] bytes, int offset, int length)
通过使用平台的默认字符集解码指定的 byte 子数组,构造一个新的 String。
import java.net.*;
class UdpRece {
public static void main(String[] args) throws Exception {
// 创建套接字,绑定到指定端口
DatagramSocket ds = new DatagramSocket(10000);
while (true) {
// 创建数组缓冲区
byte[] buf = new byte[1024];
// 创建数据包,接收缓冲区中的全部数据
DatagramPacket dp = new DatagramPacket(buf, buf.length);
// 通过套接字ds的receive方法将收到的数据存入数据包dp中。阻塞式方法。
ds.receive(dp);
// 获取数据包中的IP地址、数据、端口号
String ip = dp.getAddress().getHostAddress();
String data = new String(dp.getData(), 0, dp.getLength());
int port = dp.getPort();
// 打印到控制台
System.out.println("发送者IP:" + ip);
System.out.println("发送者端口:" + port);
System.out.println("发送的内容:" + data);
}
// ds.close();//不能在while循环外部关闭
}
}
代码示例3:UDP通信(用键盘录入方式传输数据,并发送给接收端)
import java.io.*;
import java.net.*;
//UDP发送端
class UDPSend2 {
public static void main(String[] args) throws Exception {
// 创建数据报套接字,默认绑定到任何可用的端口
DatagramSocket ds = new DatagramSocket();
// 将键盘录入的字节转换成字符,然后存入字符流缓冲区。
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
// 循环读取字符缓冲区br中的一行数据
String line = null;
while ((line = br.readLine()) != null) {
// 如果遇到"886",就跳出循环
if ("886".equals(line))
break;
// 定义缓冲区,存储读到的数据
byte[] buf = line.getBytes();
// 创建数据报包,将缓冲区中的全部数据发送到指定主机上的指定端口号
DatagramPacket dp = new DatagramPacket(buf, buf.length,
InetAddress.getByName("192.168.0.100"), 10001);
// 从此套接字发送数据报包
ds.send(dp);
}
ds.close();
}
}
import java.net.*;
//UDP接收端
class UDPRece2 {
public static void main(String[] args) throws Exception {
// 创建数据报套接字并绑定到本主机上的指定端口
DatagramSocket ds = new DatagramSocket(10001);
while (true) {
// 创建缓冲区
byte[] buf = new byte[1024];
// 创建数据包,接收缓冲区中的全部
DatagramPacket dp = new DatagramPacket(buf, buf.length);
// 从套接字接收数据报包
ds.receive(dp);
// 从数据包中获取ip地址和数据
String ip = dp.getAddress().getHostAddress();
String data = new String(dp.getData(), 0, dp.getLength());
System.out.println(ip + ":" + data);
}
}
}
代码示例4:UDP聊天
需求:用UDP方式编写一个聊天程序,需要发送端和接收端同时执行。
分析:
同时执行就要用到多线程技术,一个线程控制收,一个线程控制发。
因为收和发动作是不一致的,所以要定义两个run方法。
而且这两个方法要封装到不同的类中。
import java.io.*;
import java.net.*;
//发送端
class SendSocket implements Runnable {
private DatagramSocket ds;
public SendSocket(DatagramSocket ds) {
this.ds = ds;
}
// 设置发送端线程run方法
public void run() {
try {
// 将键盘录入的数据存入字符流缓冲区
BufferedReader bufr = new BufferedReader(new InputStreamReader(
System.in));
String line = null;
// 读取缓冲区中一行数据
while ((line = bufr.readLine()) != null) {
// 设置跳出循环标记
if ("886".equals(line))
break;
// 将读到的数据保存到
byte[] b = line.getBytes();
// 将缓冲区中数据发送懂指定主机的指定端口上。
DatagramPacket dp = new DatagramPacket(b, b.length,
InetAddress.getByName("192.168.0.13"), 51814);
// 将数据包发送到套接字中
ds.send(dp);
}
ds.close();
} catch (Exception e) {
throw new RuntimeException("发送失败");
}
}
}
// 接收端
class ReceSocket implements Runnable {
private DatagramSocket ds;
// 构造数据报套接字
public ReceSocket(DatagramSocket ds) {
this.ds = ds;
}
// 设置接收端线程的run方法
public void run() {
try {
while (true) {
// 创建字节数组缓冲区
byte[] by = new byte[1024];
// 创建数据包对象--接收缓冲区全部数据
DatagramPacket dp = new DatagramPacket(by, by.length);
// 把套接字中接收到的数据存入数据包
ds.receive(dp);
// 获取数据包中的ip地址
String ip = dp.getAddress().getHostAddress();
// 获取数据包喊缓冲区中的数据
String data = new String(dp.getData(), 0, dp.getLength());
System.out.println(ip + ":" + data);
}
} catch (Exception e) {
throw new RuntimeException("接收失败");
}
}
}
class SocketDemo {
public static void main(String[] args) throws Exception {
// 创建发送和接收服务的对象
DatagramSocket sendSocket = new DatagramSocket();
DatagramSocket receSocket = new DatagramSocket(51814);
// 创建两个线程,同时执行
new Thread(new SendSocket(sendSocket)).start();
new Thread(new ReceSocket(receSocket)).start();
}
}
说明:查看端口的方法:
在Windows中要查看端口,可以使用Netstat命令:
依次点击“开始→运行”,键入“cmd”并回车,打开命令提示符窗口。在命令提示符状态下键入“netstat -a -n”,按下回车键后就可以看到以数字形式显示的TCP和UDP连接的端口号及状态。
九、TCP程序设计
1、类 ServerSocket (服务器套接字)
服务器套接字等待请求通过网络传入。它基于该请求执行某些操作,然后可能向请求者返回结果。
服务器套接字的实际工作由 SocketImpl 类的实例执行。应用程序可以更改创建套接字实现的套接
字工厂来配置它自身,从而创建适合本地防火墙的套接字。
构造方法:
ServerSocket()
创建非绑定服务器套接字。
ServerSocket(int port)
创建绑定到特定端口的服务器套接字。
ServerSocket(int port, int backlog)
利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号。
ServerSocket(int port, int backlog, InetAddress bindAddr)
使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址创建服务器。
构造方法参数:
port - 本地 TCP 端口
backlog - 侦听 backlog
bindAddr - 要将服务器绑定到的 InetAddress
方法:
void close() 关闭此套接字。
Socket accept() 侦听并接受到此套接字的连接。
boolean isBound() 返回ServerSocket的绑定状态。
boolean isClosed() 返回ServerSocket的关闭状态。
String toString() 作为String返回此套接字的实现地址和实现端口。
int getLocalPort() 返回此套接字在其上侦听的端口。
InetAddress getInetAddress() 返回此服务器套接字的本地地址。
SocketAddress getLocalSocketAddress() 返回此套接字绑定的端点的地址,如果尚未绑定则返回 null。
void bind(SocketAddress endpoint) 将ServerSocket绑定到特定地址(IP 地址和端口号)。
void bind(SocketAddress endpoint, int backlog) 将ServerSocket绑定到特定地址(IP 地址和端口号)。
方法中的参数:
endpoint - 要绑定的 IP 地址和端口号。
backlog - 侦听 backlog 长度。
2、类 Socket(客户端套接字/套接字)
套接字是两台机器间通信的端点。 套接字的实际工作由 SocketImpl 类的实例执行。
应用程序通过更改创建套接字实现的套接字工厂可以配置它自身,以创建适合本地防火墙的套接字。
构造方法:
Socket() 通过系统默认类型的 SocketImpl 创建未连接套接字
Socket(InetAddress address, int port)
创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
Socket(InetAddress address, int port, InetAddress localAddr, int localPort)
创建一个套接字并将其连接到指定远程地址上的指定远程端口。
Socket(String host, int port)
创建一个流套接字并将其连接到指定主机上的指定端口号。
Socket(String host, int port, InetAddress localAddr, int localPort)
创建一个套接字并将其连接到指定远程主机上的指定远程端口
构造方法参数:
port - 端口号。
address - IP 地址。
localAddr - 要将套接字绑定到的本地地址
localPort - 要将套接字绑定到的本地端口
host - 主机名,或者为 null,表示回送地址。
方法:
void close() 关闭此套接字。
boolean isBound() 返回套接字的绑定状态。
boolean isClosed() 返回套接字的关闭状态。
String toString() 将此套接字转换为 String。
int getPort() 返回此套接字连接到的远程端口。
int getLocalPort() 返回此套接字绑定到的本地端口。
void shutdownOutput() 禁用此套接字的输出流。
InetAddress getInetAddress() 返回套接字连接的地址。
InputStream getInputStream() 返回此套接字的输入流。
OutputStream getOutputStream() 返回此套接字的输出流。
void connect(SocketAddress endpoint) 将此套接字连接到服务器。
--------------------------------------------------------------------------------
代码示例1:大写字母转换服务器
需求:客户端给服务端发送文本数据(字母),服务端将数据转换成大写字母返回给客户端。
而且客户端可以不断的进行文本转换,当客户端输入结束标记如"over"时,关闭服务端。
分析:
客户端:既然是操作设备上的数据,那么就可以使用IO技术,并按照IO的操作规律来思考。
源:键盘录入。
目标:网络设备,即网络输出流。
操作的是文本数据,可以使用字符流。
步骤:
1)建立Socket服务。
2)获取键盘录入的数据。
3)将数据发送给服务端。
4)获取服务端返回的大写数据。
5)关闭资源。
都是文本数据,可以使用字符流进行操作,为提高效率,加入缓冲。
客户端和服务端都使用阻塞式方法。
服务端:
源:Socket读取流。
目标:Socket输出流。
import java.io.*;
import java.net.*;
//客户端
class TransClient {
public static void main(String[] args) throws Exception {
// 创建客户端套接字,绑定到指定主机的指定端口。
Socket s = new Socket("192.168.0.13", 59291);
// 从键盘录入数据,转换为字符流,存入字符读取流缓冲区bufr。
BufferedReader bufr = new BufferedReader(new InputStreamReader(
System.in));
/* BufferedWriter bufOut = new BufferedWriter(new OutputStreamWriter(
s.getOutputStream()));*/
// 直接使用打印流接收套接字中的输出流。true:println方法将刷新输出缓冲区
PrintWriter out = new PrintWriter(s.getOutputStream(), true);
// 获取套接字中的输入流,转换成字符流,存入字符读取流缓冲区bufIn。
BufferedReader bufIn = new BufferedReader(new InputStreamReader(
s.getInputStream()));
String line = null;
// 从字符读取流缓冲区bufIn读取一行数据
while ((line = bufr.readLine()) != null) {
if ("over".equals(line))
break;
/* bufOut.write(line); // 把数据发给服务端。
bufOut.newLine(); // 添加行结束标记
bufOut.flush(); // 刷新缓冲区
*/ // 使用打印流技术就省了以上三句。
// 用打印流的println方法把读到的数据打印到字符打印流中。此方法自动换行自动刷新缓冲区。
out.println(line);
// 获取缓冲区bufIn中的一行数据,打印到控制台。
System.out.println("服务器返回的数据:" + bufIn.readLine());
}
bufr.close();
s.close();
}
}
import java.io.*;
import java.net.*;
//服务端
class TransServer {
public static void main(String[] args) throws Exception {
// 创建服务器套接字,绑定到指定端口。
ServerSocket ss = new ServerSocket(59291);
// 侦听并接受到此套接字的连接。返回新套接字s,accept方法在接收到连接之前一直阻塞。
Socket s = ss.accept();
// 从套接字中获取连接进来的IP
String ip = s.getInetAddress().getHostAddress();
System.out.println("IP=" + ip);// 测试
// 获取套接字输入流中的数据,转换成字符流,存到字符读取流缓冲区。
BufferedReader bufIn = new BufferedReader(new InputStreamReader(
s.getInputStream()));
/* // 把数据写入套接字输出流,存到写入流缓冲区。
BufferedWriter bufOut = new BufferedWriter(new OutputStreamWriter(
s.getOutputStream()));*/
// 用打印流技术替换上一句,字节流字符流都能接收,而且自动刷新。
PrintWriter out = new PrintWriter(s.getOutputStream(), true);
String line = null;
// 从字符读取流缓冲区bufIn读取一行数据
while ((line = bufIn.readLine()) != null) {
// 把读到的数据转换成大写打印到字符打印流中。就是发给客户端
out.println(line.toUpperCase());
}
s.close();
ss.close();
}
}
代码示例2:TCP上传文件
import java.io.*;
import java.net.*;
//客户端
class UpLoadText {
public static void main(String[] args) throws Exception {
Socket s = new Socket("192.168.0.13", 59338);
BufferedReader bufr = new BufferedReader(
new FileReader("NoteBook.java"));
PrintWriter out = new PrintWriter(s.getOutputStream(), true);
String line = null;
while ((line = bufr.readLine()) != null) {
out.println(line);
}
s.shutdownOutput();// 禁用客户端的输出流,相当于给流中加入结束标记-1。
BufferedReader bufIn = new BufferedReader(new InputStreamReader(
s.getInputStream()));
String str = bufIn.readLine();
System.out.println(str);
bufr.close();
s.close();
}
}
import java.io.*;
import java.net.*;
// 服务端
class TextRece {
public static void main(String[] args) throws Exception {
ServerSocket ss = new ServerSocket(59338);
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
System.out.println("来自IP:" + ip);
BufferedReader bufIn = new BufferedReader(new InputStreamReader(
s.getInputStream()));
PrintWriter out = new PrintWriter(
new FileWriter("NoteBook_upload.java"), true);
String line = null;
while ((line = bufIn.readLine()) != null) {
if ("over".equals(line))
break;
out.println(line);
}
PrintWriter pw = new PrintWriter(s.getOutputStream(), true);
pw.println("上传成功");// 给客户端发送提示信息。
out.close();
s.close();
ss.close();
}
}
代码示例3:TCP上传图片到服务器
流程分析:
1)建立服务端点
2)读取客户端已有的图片数据
3)通过Socket输出流将数据发给服务端
4)读取服务端反馈信息
5)关闭资源
import java.io.*;
import java.net.*;
//客户端
class PicClient {
public static void main(String[] args) throws Exception {
Socket s = new Socket("192.168.0.13", 59637);
FileInputStream fis = new FileInputStream("g:\\11.jpg");
OutputStream out = s.getOutputStream();
byte[] buf = new byte[1024];
int len = 0;
while ((len = fis.read(buf)) != -1) {
out.write(buf, 0, len);
}
s.shutdownOutput();// 结束标志
InputStream in = s.getInputStream();
byte[] bufIn = new byte[1024];
int num = in.read(bufIn);
System.out.println(new String(bufIn, 0, num));
fis.close();
s.close();
}
}
import java.io.*;
import java.net.*;
//服务端
class PicServer {
public static void main(String[] args) throws Exception {
ServerSocket ss = new ServerSocket(59637);
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
System.out.println("来自IP:" + ip);
InputStream in = s.getInputStream();
FileOutputStream fos = new FileOutputStream("laola_Demo.bmp");
byte[] buf = new byte[1024];
int len = 0;
while ((len = in.read(buf)) != -1) {
fos.write(buf, 0, len);
}
OutputStream out = s.getOutputStream();
out.write("上传成功".getBytes());
fos.close();
s.close();
ss.close();
}
}
代码示例4:优化TCP上传图片
以上这个服务端有个局限性,当A客户连接上以后,被服务端获取到,服务端执行具体流程,
这时候B客户连接,只有等待,因为服务端还没有处理完A客户的请求,还要循环回来执行下
一次accept方法,所以暂时获取不到B客户对象。
为了可以让多个客户端同时并发访问服务端,那么服务端最好就是将每个客户端封装到一个
单独的线程中,这样就可以同时处理多个客户端请求。
如何定义线程呢?
只要明确每个客户端要在服务端执行的代码即可,将该代码存入run方法中。
import java.io.*;
import java.net.*;
//客户端
class PicClient2 {
public static void main(String[] args) throws Exception {
// 测试:用利用主函数传递文件名称
if (args.length != 1) {
System.out.println("请选择一个图片文件");
return;
}
// 判断是否是文件或是否存在
File file = new File(args[0]);
if (!(file.exists() && file.isFile())) {
System.out.println("该文件不存在或不是文件");
return;
}
// 判断文件名不不是.bmp格式
if (!file.getName().endsWith(".bmp")
&& !file.getName().endsWith(".jpg")) {
System.out.println("图片格式只支持jpg/bmp");
return;
}
// 判断文件大小
if (file.length() > 1024 * 1024 * 10) {
System.out.println("文件太大,必须小于10M");
return;
}
// 以上判断都满足,就创建ServerSocket对象
Socket s = new Socket("192.168.0.13", 59711);
FileInputStream fis = new FileInputStream("g:\\15.jpg");
OutputStream out = s.getOutputStream();
byte[] buf = new byte[1024];
int len = 0;
while ((len = fis.read(buf)) != -1) {
out.write(buf, 0, len);
}
s.shutdownOutput();// 结束标志
InputStream in = s.getInputStream();
byte[] bufIn = new byte[1024];
int num = in.read(bufIn);
System.out.println(new String(bufIn, 0, num));
fis.close();
s.close();
}
}
import java.io.*;
import java.net.*;
//服务端
class PicServer2 implements Runnable {
private Socket s;
PicServer2(Socket s) {
this.s = s;
}
public static void main(String[] args) throws Exception {
ServerSocket ss = new ServerSocket(59711);
// 循环判断,连进来一个客户端就新建一个线程
while (true) {
Socket s = ss.accept();
new Thread(new PicServer2(s)).start();
}
// 服务器一般是不关闭的
// ss.close();
}
public void run() {
// 定义局部变量
int count = 1;
String ip = s.getInetAddress().getHostAddress();
try {
System.out.println(ip + "请求连接");
InputStream in = s.getInputStream();
// 定义上传后的图片名称
File file = new File(ip + "(" + count + ").jpg");
while (file.exists())
file = new File(ip + "(" + (count++) + ").jpg");
FileOutputStream fos = new FileOutputStream(file);
byte[] buf = new byte[1024];
int len = 0;
while ((len = in.read(buf)) != -1) {
fos.write(buf, 0, len);
}
OutputStream out = s.getOutputStream();
out.write("上传成功".getBytes());
fos.close();
s.close();
} catch (Exception e) {
throw new RuntimeException(ip + " 上传失败");
}
}
}
代码示例5:TCP客户端并发登录
客户端通过键盘录入用户名,服务端对这个用户名进行效验
如果该用户存在,在服务端显示 XXX已登录,并在客户端显示 xxx欢迎光临。
如果该用户不存在,在服务端显示 XXX,尝试登录,并在客户端显示 xxx该用户不存在。
最多登录三次就不给登录了。
import java.io.*;
import java.net.*;
//客户端
class LoginClient {
public static void main(String[] args) throws Exception {
Socket s = new Socket("192.168.0.13", 59743);
// 读取键盘录入的数据
BufferedReader bufr = new BufferedReader(new InputStreamReader(
System.in));
PrintWriter out = new PrintWriter(s.getOutputStream(), true);
// 读取服务器返回的信息
BufferedReader bufIn = new BufferedReader(new InputStreamReader(
s.getInputStream()));
for (int x = 0; x < 3; x++) {
// 如果读到空,就跳出循环。
String line = bufr.readLine();
if (line == null)
break;
out.println(line);
String info = bufIn.readLine();
System.out.println(info);
if (info.contains("0"))
break;
// 如果服务端返回信息中包含”欢迎“就表示登录成功,结束登录循环次数
if (info.contains("欢迎"))
break;
}
bufr.close();
s.close();
}
}
import java.io.*;
import java.net.*;
//服务端
class UserThread implements Runnable {
private Socket s;
UserThread(Socket s) {
this.s = s;
}
public void run() {
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip + " 已连接进来");
try {
for (int x = 0; x < 3; x++) {
// 读取客户端发过来的用户名
BufferedReader bufIn = new BufferedReader(
new InputStreamReader(s.getInputStream()));
String name = bufIn.readLine();
BufferedReader bufr = new BufferedReader(new FileReader(
"UserInfo.txt"));
PrintWriter out = new PrintWriter(s.getOutputStream(), true);
String line = null;
// 定义一个是否存在的标记
boolean flag = false;
// 从数据库文件中一行一行读取,进行判断
while ((line = bufr.readLine()) != null) {
// 如果该name存在数据库中
if (line.equals(name)) {
// 标记为true,表示存在就登录成功
flag = true;
break;
}
}
// 如果用户名存在
if (flag) {
// 在服务端发送提示
System.out.println(name + " 已登录");
// 在客户端发送提示
out.println(name + " 欢迎光临");
break;
}
// 不存在
else {
// 在服务端发送提示
System.out.println(name + " 正在尝试登录");
// 在客户端发送提示
out.println(name + "用户名不存在 ");
}
}
// 关闭客户端连接
s.close();
} catch (Exception e) {
throw new RuntimeException(ip + "校验失败");
}
}
}
class LoginServer {
public static void main(String[] args) throws Exception {
ServerSocket ss = new ServerSocket(59743);
while (true) {
Socket s = ss.accept();
new Thread(new UserThread(s)).start();
}
}
}
代码示例6:一个简单的服务器
import java.io.*;
import java.net.*;
class Server {
public static void main(String[] args) throws Exception {
ServerSocket ss = new ServerSocket(59758);
Socket s = ss.accept();
// 获取客户端的IP
System.out.println(s.getInetAddress().getHostAddress());
PrintWriter out = new PrintWriter(s.getOutputStream(), true);
// 向客户端发送数据
out.println("<font color='red' size='7'>客户端你好</font>");
s.close();
ss.close();
}
}
十、URL (URLConnection)
1、定义:
URL(Uniform Resource Locator)的缩写。
URL类代表一个统一资源定位符,被称为网页地址,它是指向互联网“资源”的指针。
资源可以是简单的文件或目录,也可以是对更为复杂的对象的引用,例如对数据库或搜索引擎的查询。
通常URL可分成几个部分:
示例:http://edu.csdn.net/java/javaee.shtml
上面的URL示例指示使用的协议为 http(超文本传输协议)
并且该信息驻留在一台名为 edu.csdn.net 的主机上。
主机上的信息名称为:java/javaee.shtml。
主机上此名称的准确含义取决于协议和主机。
该信息一般存储在文件中,但可以随时生成,该URL的这一部分称为路径。
URL 可选择指定一个“端口”,它是用于建立到远程主机 TCP 连接的端口号。
如果未指定该端口号,则使用协议默认的端口。例如,http 协议的默认端口为 80。
还可以指定一个备用端口,如下所示:
http://edu.csdn.net:80/java/javaee.shtml
2、构造方法:
根据 String 表示形式创建 URL 对象。
URL(String spec)
根据指定 protocol、host、port 号和 file 创建 URL 对象。
URL(String protocol, String host, int port, String file)
根据指定的 protocol、host、port 号、file 和 handler 创建 URL 对象。
URL(String protocol, String host, int port, String file, URLStreamHandler handler)
根据指定的 protocol 名称、host 名称和 file 名称创建 URL。
URL(String protocol, String host, String file)
通过在指定的上下文中对给定的 spec 进行解析创建 URL。
URL(URL context, String spec)
通过在指定的上下文中用指定的处理程序对给定的 spec 进行解析来创建 URL。
URL(URL context, String spec, URLStreamHandler handler)
构造方法参数:
spec - 将作为URL解析的String。
host - 主机名称。
port - 主机端口号。
file - 主机上的文件
handler - URL 的流处理程序。
context - 要在其中解析规范的上下文。
protocol - 要使用的协议名称。
3、方法:
String getRef() 获取此 URL 的锚点(也称为“引用”)。String getFile() 获取此 URL 的文件名。
String getHost() 获取此 URL 的主机名(如果适用)。
String getPath() 获取此 URL 的路径部分。
int getPort() 获取此 URL 的端口号。
int hashCode() 创建一个适合哈希表索引的整数。
String getQuery() 获取此 URL 的查询部分。
Object getContent() 获取此 URL 的内容。
String getUserInfo() 获取此 URL 的 userInfo 部分。
String getProtocol() 获取此 URL 的协议名称。
String getAuthority() 获取此 URL 的授权部分。
int getDefaultPort() 获取与此 URL 关联协议的默认端口号。
Object getContent(Class[] classes) 获取此 URL 的内容。
boolean equals(Object obj) 比较此 URL 是否等于另一个对象。
4、代码示例:获取URL的各个参数
import java.net.*;
class URLDemo {
public static void sop(Object obj) {
System.out.println(obj);
}
public static void main(String[] args) throws Exception {
// 该URL ?后面的参数是加上去的
URL url = new URL(
"http://edu.csdn.net/java/javaee.shtml?name=zhangsan&age=20#30");
sop("URL授权部分:" + url.getAuthority());
sop("URL的协议:" + url.getProtocol());
sop("URL的IP地址:" + url.getHost());
sop("URL端口号:" + url.getPort());
sop("URL文件路径:" + url.getPath());
sop("URL文件名称:" + url.getFile());
sop("URL查询部分:" + url.getQuery());
sop("URL的锚点:" + url.getRef());
sop("与URL关联协议的默认端口号:" + url.getDefaultPort());
sop("userInfo部分:" + url.getUserInfo());
sop("构造此URL的字符串表示形式:" + url.toExternalForm());
sop("返回与此URL等效的URI:" + url.toURI());
// sop("URL的内容:"+url.getContent()); //这是无效的URL,所以使用该方法会报错
}
}
/*
* 运行结果: URL授权部分:edu.csdn.net URL的协议:http URL的IP地址:edu.csdn.net URL端口号:-1
* URL文件路径:/java/javaee.shtml URL文件名称:/java/javaee.shtml?name=zhangsan&age=20
* URL查询部分:name=zhangsan&age=20 URL的锚点:30 与URL关联协议的默认端口号:80 userInfo部分:null
* 构造此URL的字符串表示形式:http://edu.csdn.net/java/javaee.shtml?name=zhangsan&age=20#30
* 返回与此URL等效的URI:http://edu.csdn.net/java/javaee.shtml?name=zhangsan&age=20#30
*/