C/S结构:全称为Client/Server结构,是指客户端和服务器结构
开发成本高,维护周期长,需要开发客户端和服务器
B/S结构:全称为Browser/Server结构,是指浏览器和服务器结构
开发成本较低,维护周期短,只要开发一个服务器即可
网络三要素: 通过IP找主机,通过端口号找程序,通过协议确定如何传输
IP地址:网络设备的唯一表示,通过IP地址可以找到对应的设备
端口:进程的唯一表示,每一个程序都有端口号(一个十进制的整数,取值范围(0~65535))
0~1024以下的端口是系统保留使用的,程序员开发得用1024~65535端口号
协议:用来规定计算机与计算机之间数据传输的格式(例:UDP协议和TCP协议)
InetAddress类:
一个InteAddress就代表一个IP地址对象 (127.0.0.1表示本机地址)
构造方法:
static InetAddress getLocalHost() 获得本机IP地址对象
InetAddress ia = InetAddress.getLocalHost();
static InetAddress getByName(String host) 根据主机名或IP地址字符串获得InetAddress对象
InetAddress ia = InetAddress.getByName("192.168.12.41");
常用方法:
String getHostName() 获得InetAddress对象的主机名
InetAddress ia = InetAddress.getLocalHost();
System.out.println(ia.getHostName()); //输出:cikinn-PC
String getHostAddress() 获得InetAddress对象的IP地址字符串
InetAddress ia = InetAddress.getLocalHost();
System.out.println(ia.getHostAddress()); //输出:172.20.10.2
UDP协议:
特点:
面向无连接协议(无需连接就将信息发出,不管对方是否收到)
只管发送,不确认对方是否收到
速度快,但不可靠,会造成数据丢失
传输数据大小限制在64K以下
基于数据包传输:将要发送的数据,接收端的IP地址、
端口号等信息打包到一个数据包发送
DatagramSocket类:
构造方法:
DatagramSocket() 创建发送端的Socket对象
DatagramSocket ds = new DatagramSocket();
DatagramSocket(int port) 创建指定端口号的接收端Socket对象
DatagramSocket ds = new DatagramSocket(8989);
创建时传入端口号的则为接收端Socket,没有端口号的是发送端Socket对象
常用方法:
void send () 发送数据包
void recive () 接受数据包
void close () 关闭Socket对象
DatagramPacket类:
数据包对象,用来封装要发送或接受的数据的包对象
构造方法:
DatagramPacket(byte[] buf, int length, InetAddress address, int port) 用于创建发送端数据包对象
InetAddress ia = InetAddress.getLocalHost();
byte [] b = new byte[1024];
DatagramPacket dp = new DatagramPacket(b,b.length,ia,8989);
buf 要发送的内容 ;length 要发送的内容长度,单位字节; address 接收端的ip地址 ;port 接收端口号
DatagramPacket(byte buf[], int length) 用于创建接收端数据包对象
byte [] b = new byte[1024];
DatagramPacket dp = new DatagramPacket(b,b.length);
buf 用于接收数据的数组; length buf数组的长度
创建对象时4个参数的是发送端对象,2个参数的是接受端对象
UDP传输的流程逻辑如图
首先由DatagramSocket发送端将DatagramPacket数据包发送出去
接受端接受的时候也得由一个DatagramPacket接受发送过来的数据包,然后再到服务器接受
UDP服务器:
DatagramSocket ds = new DatagramSocket(8989); //服务器的DatagramSocket端口号
byte[] b = new byte[1024];
DatagramPacket dp = new DatagramPacket(b,b.length);
ds.receive(dp);
System.out.println(dp.getAddress()+" "+new String(b,0,dp.getLength()));
UDP客户端:
DatagramSocket ds = new DatagramSocket();
InetAddress ia = InetAddress.getLocalHost();
byte[] b = "申请连接UDP服务器".getBytes();
DatagramPacket dp = new DatagramPacket(b,b.length,ia,8989);
ds.send(dp);
ds.close();
使用DatagramSocket的send()和recive()方法对datagramPacket数据包对象进行发送和接受
客户端的DatagramPacket对象必须传入服务器的IP地址对象(ia)和服务器的端口号(8989)一致
最后客户端发送完信息后记得调用close()方法关闭发送端对象
先开启服务器,然后开启客户端,即可在服务器中接收到客户端发送的申请连接消息
TCP协议:
面向连接的协议
通过三次握手建立连接,形成数据传输通道
通过四次挥手断开连接
速度慢,但可靠,传输数据大小没有限制
基于IO流的数据传输
Socket类:
一个该类对象就代表一个客户端程序
构造方法:
Socket (String host , int port ) 根据服务器的IP地址和端口号创建客户端对象
Socket s = new Socket("127.0.0.1", 8989);
Socket (InetAddress adrss , int port ) 根据服务器的IP地址对象和端口号创建客户端对象
Socket s = new Socket(InetAddress.getLocalHost(),8989);
以上两种方式都代表创建本机IP地址端口号8989的客户端对象
常用方法:
OutputStream getOutputStream () 获得字节输出流对象
Socket s = new Socket(InetAddress.getLocalHost(),8989);
OutputStream out = s.getOutputStream();
InputStream getInputStream () 获得字节输入流对象
Socket s = new Socket(InetAddress.getLocalHost(),8989);
InputStream in = s.getInputStream();
ServerSocket类:
一个该类对象就代表一个服务器端程序
ServerSocket (int port) 根据端口号创建服务器端Socket对象
Socket accpet() 等待客户端连接并获得与客户端相关的Socket对象
ServerSocket ss = new ServerSocket(8989);
Socket s = ss.accept();
先完成简单的客户端程序与服务器之间以IO流的形式传输数据的功能实现
TCP服务器:
ServerSocket ss = new ServerSocket(8989);
Socket s = ss.accept();
InputStream in = s.getInputStream();
OutputStream out = s.getOutputStream();
System.out.println(s.getInetAddress() + "连接成功");
int len = 0;
byte[] b = new byte[1024];
while ((len = in.read(b)) != -1) {
System.out.println(new String(b, 0, len));
}
TCP客户端:
Socket s = new Socket(InetAddress.getLocalHost(), 8989);
InputStream in = s.getInputStream();
OutputStream out = s.getOutputStream();
out.write("申请连接".getBytes());
out.flush();
s.shutdownOutput();
s.close();
服务器接受到客户端发来的申请连接如图:
TCP的连接在客户端创建Socket对象时要传入服务器IP地址和端口号
然后利用Socket的方法获取输入输出流,根据IO流的传输手段,将信息或在服务器之间传输
若想让服务器能一直开启,并且接受多个客户端从本地上传到客户端再发送到服务器的文件
则需要运用到多线程开启服务器,且使用FileInputStream和FileOutputStream保存文件到服务器端的硬盘中
创建一个线程类继承Thread:
public class MyThread extends Thread {
private Socket s;
public MyThread(Socket s) {
this.s = s;
}
public void run() {
try {
InputStream in = s.getInputStream();
OutputStream out = s.getOutputStream();
FileOutputStream fos = new FileOutputStream("zz.txt");
System.out.println(s.getInetAddress() + "连接成功");
int len = 0;
byte[] b = new byte[1024];
while ((len = in.read(b)) != -1) {
fos.write(b, 0, len);
}
} catch (Exception e) {
}
}
}
主程序中:
ServerSocket ss = new ServerSocket(8989);
while (true) {
Socket s = ss.accept();
MyThread mt = new MyThread(s);
mt.run();
}
将ServerSocket的accept()放在开启线程死循环的第一条,
由于accept()方法本身就有等待客户端连接动作,所以直到有客户端连接时才会开启MyThread线程
不会造成死循环开好线程等待客户连接的浪费资源情况
TCP客户端:
Socket s = new Socket();
InputStream in = s.getInputStream();
OutputStream out = s.getOutputStream();
FileInputStream fis = new FileInputStream("c.txt");
int len = 0;
byte[] b = new byte[1024];
while ((len = fis.read(b)) != -1) {
out.write(b, 0, len);
}
s.shutdownOutput();
s.close();
客户端从本地磁盘中上传文件时,默认是读取文件字节到-1时结束
然后再在此循环中将这些字节发给服务器,当文件字节读到-1时已经结束发送了
那么客户端在上传给服务器的时候,并没有一个-1的值来当做结束标记,让服务器知道客户端已经上传完毕
所以客户端在上传完毕文件或者传输完信息时候
需要调用Socket的shutdownOutput()方法来告知服务器,已经不会再传输任何东西
先开启多线程服务器,然后再启用客户端发送c.txt文本
发现文本发送完成后,服务器仍在运行等待下一个客户端连接
且从根目录中上传的c.txt也已经从客户端发送到服务器再下载到根目录下保存为zz.txt