网络基本概念
1、应用程序有两种结构:C/S和B/S
C:Client客户端
S:Server服务器
B:Browser浏览器
C/S和B/S的区别?
C/S:需要开发两套程序,一套是客户端程序,一套是服务器端程序,用户这边需要在它的电脑安装我们客户端程序。
B/S: 用户统一使用浏览器访问我们的服务器端程序。
其实本质上B/S也是C/S结构,客户端是由浏览器公司帮我们开发了。
2、网络的通信需要哪些要素?
(1)IP地址:收发地址
(2)端口号:电脑中通过端口号来识别应用程序
相当于 姓名:收发人姓名
(3)网络协议
内容:要求双方看得懂
3、IP地址
互联网协议地址(Internet Protocol Address),俗称IP。IP地址用来给一个网络中的计算机设备做唯一的编号。
分类1:IPV4,IPV6
分类2:A,B,C...根据范围来分。
特殊的IP地址:127.0.0.1 本地回环地址,它对应的域名:localhost
一会儿大家要写网络编程的应用程序了,但是你是在同一台电脑上写了客户端服务器端,
此时需要网络通信时,地址怎么表示?表示自己给自己发,可以用127.0.0.1。
4、域名:方便用户记忆和使用
域名对应一个IP地址
www.atguigu.com ==> 202.108.35.210
域名->IP,需要DNS,域名解析器
5、端口号:作用是唯一定位到一个进程(运行中的应用程序)
是一个整数,范围是0-65535
分为:
公认端口:0~1023
注册端口:1024~49151 mysql:3306,tomcat:8080
动态/私有端口:49152~65535。
6、网络协议
在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。
一开始是7层,后来实际是4层。
其中传输层有两大经典协议:
TCP:传输控制协议 (Transmission Control Protocol)
面向连接的、可靠的、基于字节流的传输协议
什么叫做面向连接的?
在传输数据之前,需要先建立连接,并且要确认这个连接是没问题,才会开始传输数据。
尝试连接并确认连接这个过程一般需要经过“三次握手”。
在传输数据之后,需要断开连接,并且要保证数据传输完成后再彻底断开。
断开连接过程一般需要经过“四次挥手”。
什么叫做可靠的?
使用TCP协议发生数据的话,数据会拆为好几个包,这些包会陆陆续续到达目的地。并且这些数据包可能不能经过相同的路径到达目的地。目的地这边,会对这些数据包进行重组。重组时,会按照编号进行处理,但是如果发现某个编号的数据包丢了,先等一会儿,一会儿还没有,就会让发送方重传。
优点:可靠,适用于传输大量数据
UDP:用户数据报协议(User Datagram Protocol)
非面向连接的,不可靠的、基于数据报的传输协议
基于UDP协议传输数据的话,在传输数据之前,不会确认接收方是否存在(是否能联通),直接发送。如果对方没收到,也不会主动重传。
优点:快,只适用于传输小的数据,每一个数据报容量64K以内。
InetAddress类
InetAddress类主要表示IP地址,两个子类:Inet4Address、Inet6Address。
Internet上的主机有两种方式表示地址:
- 域名(hostName):www.atguigu.com
- IP 地址(hostAddress):202.108.35.210
lInetAddress 类没有提供公共的构造器,而是提供 了 如下几个 静态方法来获取InetAddress 实例
- public static InetAddress getLocalHost()
- public static InetAddress getByName(String host)
- public static InetAddress getByAddress(byte[] addr)
InetAddress 提供了如下几个常用的方法
- public String getHostAddress() :返回 IP 地址字符串(以文本表现形式)。
- public String getHostName() :获取此 IP 地址的主机名
Socket类
Socket是代表网络通信的一方。双方都有各自的Socket。
Socket可以分为:
(1)流套接字:ServerSocket和Socket类,用于TCP协议编程。
ServerSocket:负责在服务器端,接收客户端的连接用的。即它不负责传输数据。
Socket类:负责传输数据。
(2)数据报套接字:DatagramSocket,用于UDP协议编程。
DatagramSocket:发送方和接收方都有DatagramSocket的对象。Socket是负责与网卡驱动交互。
ServerSocket类的构造方法:
- ServerSocket(int port) :创建绑定到特定端口的服务器套接字。
ServerSocket类的常用方法:
- Socket accept():侦听并接受到此套接字的连接。
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 InetAddress getLocalAddress():获取套接字绑定的本地地址。
- 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。 即不能通过此套接字的输出流发送任何数据。
**注意:**先后调用Socket的shutdownInput()和shutdownOutput()方法,仅仅关闭了输入流和输出流,并不等于调用Socket的close()方法。在通信结束后,仍然要调用Scoket的close()方法,因为只有该方法才会释放Socket占用的资源,比如占用的本地端口号等。
基于UDP协议的网络编程仍然需要在通信实例的两端各建立一个Socket,但这两个Socket之间并没有虚拟链路,这两个Socket只是发送、接收数据报的对象,Java提供了DatagramSocket对象作为基于UDP协议的Socket,使用DatagramPacket代表DatagramSocket发送、接收的数据报。
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 字段包含所接收信息的长度。如果信息比包的长度长,该信息将被截短。
DatagramPacket类的常用方法:
- 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 int getLength()返回将要发送或接收到的数据的长度。
TCP编程示例1
服务器
/*
客户端与服务器端连接成功后,客户端给服务器发一个消息:“hello"
然后服务器回复客户端一个"hi"
服务器端程序与客户端程序谁先运行?服务器端
*/
public class TestServer {
public static void main(String[] args) throws IOException {
//(1)开启服务器,准备一个ServerSocket对象
//有一个参数必须指定,你服务器监听的端口号
//一会儿客户端要连接服务器,必须指定服务器的端口号
ServerSocket server = new ServerSocket(8888);
//(2)接受客户端的连接
//accept()方法是一个阻塞式方法,即如果没有客户端连接,这个方法不往下走
//一旦有客户端连接,这个方法就会提供一个Socket对象,负责与该客户端通信
Socket socket = server.accept();
System.out.println(socket.getInetAddress().getHostAddress() +"连接成功!");
//(3)如果要支持同时收和发,就需要多线程
//如果不是多线程,就要规定,是先收还是先发
//例如:先收
InputStream inputStream = socket.getInputStream();
byte[] data = new byte[10];
int len;
while((len = inputStream.read(data))!=-1){
System.out.println(new String(data, 0, len));
}
//回复消息
OutputStream outputStream = socket.getOutputStream();
outputStream.write("hi".getBytes());
//(4)如果通信结束,可以断开连接,释放对应的资源
outputStream.close();
inputStream.close();
socket.close();
server.close();
}
}
客户端
public class TestClient {
public static void main(String[] args) throws IOException {
//(1)主动连接服务器,准备一个Socket对象
//至少指定两个参数:服务器的IP地址,和服务器监听的端口号
Socket socket = new Socket("127.0.0.1",8888);
//(2)如果要支持同时收和发,就需要多线程
//如果不是多线程,就要规定,是先收还是先发
// 例如:先发
OutputStream outputStream = socket.getOutputStream();
outputStream.write("hello".getBytes());
socket.shutdownOutput();//告诉服务器我不再发消息了,对方才能读到流末尾标记-1
//收消息
InputStream inputStream = socket.getInputStream();
byte[] data = new byte[10];
int len;
while((len = inputStream.read(data))!=-1){
System.out.println(new String(data, 0, len));
}
//(3)如果通信结束,可以断开连接,释放对应的资源
outputStream.close();
inputStream.close();
socket.close();
}
}
UDP编程实例
发送方
/*
基于UDP协议的编程。
发送方:“十一假期马上来了"
接收方:收到后打印
*/
public class TestSend {
public static void main(String[] args) throws Exception {
//(1)准备DatagramSocket
DatagramSocket ds = new DatagramSocket();
// (2)把数据报装到数据报中
//DatagramPacket(byte[] buf, int length, InetAddress address, int port)
//参数一:字节数组,参数二:长度,参数三:接收方的IP地址,参数四:接收方的端口号
String message = "十一假期马上来了";
byte[] data = message.getBytes();
byte[] ip = {(byte)192,(byte)168,40,116};
InetAddress address = InetAddress.getByAddress(ip);
DatagramPacket dp1 = new DatagramPacket(data, data.length, address, 8888);
DatagramPacket dp2 = new DatagramPacket(data, data.length, InetAddress.getLocalHost(), 9999);
//(3)发送数据报
ds.send(dp1);
ds.send(dp2);
System.out.println("发送完毕");
//(4)如果接收,关闭
ds.close();
}
}
接受方
public class TestReceive {
public static void main(String[] args)throws Exception {
//(1)准备DatagramSocket,必须指定监听端口号
DatagramSocket ds = new DatagramSocket(9999);
//(2)准备数据报来接收消息
//DatagramPacket(byte[] buf, int length)
byte[] data = new byte[1024];
DatagramPacket dp = new DatagramPacket(data, data.length);
//(3)接收数据报
ds.receive(dp);
//(4)处理数据
System.out.println(new String(dp.getData(),0,dp.getLength()));
//(5)关闭
ds.close();
}
}
TCP编程示例2
服务器
public class TestServer {
public static void main(String[] args) throws Exception{
//(1)开启服务器
ServerSocket server = new ServerSocket(8888);
//(2)接收多个客户端
while(true) {
Socket socket = server.accept();//一句这个代码可以接收一个客户端的连接
//这句代码每运行一次,就表示接收一个客户端
System.out.println(socket.getInetAddress().getHostAddress()+"连接成功");
//每一个客户端都有单独的线程为它服务
new MyThread(socket).start();
}
}
}
class MyThread extends Thread{
private Socket socket;
public MyThread(Socket socket) {
this.socket = socket;
}
@Override
public void run(){
try(InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
InputStreamReader isr = new InputStreamReader(inputStream);
BufferedReader br = new BufferedReader(isr);
PrintStream ps = new PrintStream(outputStream);
){
//服务器接收消息,并且返回给客户端
/*byte[] data = new byte[1024];
int len;
while((len = inputStream.read(data)) != -1){
}*///不好处理每个单词的分界
//处理方式改为:按行读取,对方(客户端)每输入一个单词回车结束,发送过来一个单词一行
//返回给客户端也按行处理
String line;
while((line = br.readLine()) != null){
System.out.println("客户端" + socket.getInetAddress().getHostAddress() + "发送的单词是:" + line);
//把单词反转并返回
ps.println(new StringBuilder(line).reverse());
}
}catch (Exception e){
e.printStackTrace();
}
}
}
服务器
public class TestClient {
public static void main(String[] args)throws Exception {
//(1)连接服务器
Socket socket = new Socket("127.0.0.1",8888);
OutputStream outputStream = socket.getOutputStream();
PrintStream ps = new PrintStream(outputStream);
InputStream inputStream = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(inputStream);
BufferedReader br = new BufferedReader(isr);
//(2)从键盘输入单词或成语等,非服务器发过去
Scanner input = new Scanner(System.in);
while(true){
System.out.print("请输入单词或成语:");
String word = input.nextLine();
if("stop".equals(word)){
break;
}
ps.println(word);
//接收服务器返回的消息
System.out.println("服务器返回:" + br.readLine());
}
//(3)关闭
ps.close();
br.close();
isr.close();
inputStream.close();
outputStream.close();
socket.close();
}
}
TCP编程示例3
客户端
/*
案例:从客户端给服务器上传文件,可以支持多个客户端同时上传。
服务器接收完文件之后,返回“xx文件接收完毕”
*/
public class TestFileServer {
public static void main(String[] args)throws Exception {
//(1)开启服务器
ServerSocket server = new ServerSocket(8888);
//(2)同时支持多个客户端
while(true){
Socket socket = server.accept();
System.out.println(socket.getInetAddress().getHostAddress()+"连接成功!");
//每一个客户端一个线程
new FileUploadThread(socket).start();
}
}
}
class FileUploadThread extends Thread{
private Socket socket;
public FileUploadThread(Socket socket) {
this.socket = socket;
}
public void run(){
FileOutputStream fos = null;
BufferedOutputStream bos = null;
try(
InputStream inputStream = socket.getInputStream();
BufferedInputStream bis = new BufferedInputStream(inputStream);
//文件名是字符串,文件内容不一定是字符串,肯定不能用字符流
//如果使用纯字节流,需要 区别是文件名还是文件内容,即知道文件名一共多少个字节?很难办
//如何解决?DataInputStream或ObjectInputStream
DataInputStream dis = new DataInputStream(bis);
OutputStream outputStream = socket.getOutputStream();
PrintStream ps = new PrintStream(outputStream);
){
//接收文件名
String fileName = dis.readUTF();
//处理文件名
//如果服务器端原样存储文件的话,可能有文件“重名”问题,怎么办?
//解决方案有很多种,其中一种是 用时间戳+ip地址+文件的后缀名
long time = System.currentTimeMillis();
String ip = socket.getInetAddress().getHostAddress().replaceAll("\\.","");
String ext = fileName.substring(fileName.lastIndexOf("."));
String newFileName = time + "_" + ip + ext;
fos = new FileOutputStream("upload/" + newFileName);
bos = new BufferedOutputStream(fos);
//接收文件内容
byte[] data = new byte[1024];
int len;
while((len = dis.read(data)) != -1){
bos.write(data, 0, len);
}
//反馈接收完毕
ps.println(fileName + "接收完毕!");
}catch(Exception e){
e.printStackTrace();
}finally{
//手动关闭
try {
if(bos!=null){
bos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if(fos!=null){
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
服务器
public class TestFileClient {
public static void main(String[] args) throws Exception{
//(1)连接服务器
Socket socket = new Socket("127.0.0.1",8888);
//(2)指定上传的文件
Scanner input = new Scanner(System.in);
System.out.print("请输入要上传的文件的路径名:");
String filePathName = input.nextLine();
//D:\Download\img\美女\15.jpg
File file = new File(filePathName);
//(3)发送文件名和文件的内容
OutputStream outputStream = socket.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(outputStream);
DataOutputStream dos = new DataOutputStream(bos);
FileInputStream fis = new FileInputStream(filePathName);
BufferedInputStream bis = new BufferedInputStream(fis);
//发送文件名
dos.writeUTF(file.getName());
//发送文件内容
byte[] data = new byte[1024];
int len;
while((len = bis.read(data)) != -1){
dos.write(data, 0 ,len);
}
dos.flush();//把缓冲区中的数据先刷出去,才能关闭输出通道
socket.shutdownOutput();//告诉服务器,文件内容传输完毕
//接收服务器反馈的消息
InputStream inputStream = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(inputStream);
BufferedReader br = new BufferedReader(isr);
System.out.println(br.readLine());
//各种关闭
br.close();
isr.close();
inputStream.close();
bis.close();
fis.close();
dos.close();
bos.close();
outputStream.close();
socket.close();
}
}