C/S结构 :全称为Client/Server结构,是指客户端和服务器结构。常见程序有QQ、迅雷等软件。
B/S结构 :全称为Browser/Server结构,是指浏览器和服务器结构。常见浏览器有谷歌、火狐等。
两种架构各有优势,但是无论哪种架构,都离不开网络的支持。
网络编程:就是在一定的协议下,实现两台计算机的通信的程序。
网络通信协议:通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。
协议分类:
java.net 包中提供了两种常见的网络协议的支持:
1、TCP:
传输控制协议 (Transmission Control Protocol)。TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”。
完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛,例如下载文件、浏览网页等。
2、UDP:
特点:数据被限制在64kb以内,超出这个范围就不能发送了。
数据报(Datagram):网络传输的基本单位。
用户数据报协议(User Datagram Protocol)。UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输例如视频会议都使用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议。UDP的交换过程如下图所示。
1、协议
计算机网络通信必须遵守的规则。
2、IP地址
IP地址:指互联网协议地址(Internet Protocol Address),俗称IP。
IP地址分类:
IPv4:是一个32位的二进制数,通常被分为4个字节,表示成a.b.c.d 的形式,例如192.168.65.100 。
IPv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。
3、端口号
如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的进程(应用程序)了。
端口号:用两个字节表示的整数,它的取值范围是0~65535。其中,0~1023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败。
利用协议+IP地址+端口号 三元组合,就可以标识网络中的进程了,那么进程间的通信就可以利用这个标识与其它进程进行交互。
在Java中,提供了两个类用于实现TCP通信程序:
客户端:java.net.Socket 类表示。创建Socket对象,向服务端发出连接请求,服务端响应请求,两者建立连接开始通信。
服务端:java.net.ServerSocket 类表示。创建ServerSocket对象,相当于开启一个服务,并等待客户端的连接。
该类实现客户端套接字,套接字指的是两台设备之间通讯的端点。java.net.Socket 类表示。创建Socket对象,向服务端发出连接请求,服务端响应请求,两者建立连接开始通信。
构造方法:
public Socket(String host, int port) :创建套接字对象并将其连接到指定主机上的指定端口号。如果指定的host是null ,则相当于指定地址为回送地址。
成员方法:
public InputStream getInputStream() : 返回此套接字的输入流。
public OutputStream getOutputStream() : 返回此套接字的输出流。
如果此Scoket具有相关联的通道,则生成的OutputStream或InputStream 的所有操作也关联该通道。
关闭生成的InputStream或OutputStream也将关闭相关的Socket。
public void close() :关闭此套接字。
public void shutdownOutput() : 禁用此套接字的输出流。
ServerSocket类:这个类实现了服务器套接字,该对象等待通过网络的请求。
构造方法:
public ServerSocket(int port) :使用该构造方法在创建ServerSocket对象时,就可以将其绑定到一个指定的端口号上,参数port就是端口号。
成员方法:
public Socket accept() :侦听并接受连接,返回一个新的Socket对象,用于和客户端实现通信。该方法会一直阻塞直到建立连接。
代码:
public class TCPServer {
public static void main(String args[]) throws Exception{
System.out.println("服务器已经启动……等待客户端去连接");
Thread.sleep(3000);
//1.创建服务器ServerSocket对象并指定服务器的端口
ServerSocket server = new ServerSocket(8888);
//2.使用ServerSocket对象中的accept()方法去接收客户端发送过来的请求,获取到请求的客户端对象Socket
Socket socket = server.accept();
//System.out.println("Server:"+socket);
//3.使用Socket对象中的方法getInputStream()去获取网络字节输入流InputStream对象
InputStream is = socket.getInputStream();
//4.使用网络字节输入流InputStream对象中的read()方法,读取客户端发送的数据
byte[] bytes = new byte[1024];
int len = is.read(bytes);
System.out.println("客->服->内容:"+new String(bytes,0,len));
//5.使用Socket对象中的方法getOutputStream()去获取网络字节输出流OutputStream对象
OutputStream os = socket.getOutputStream();
//6.使用网络字节输出流OutputStream对象找到呢write()方法 给客户端回写数据
os.write("收到谢谢".getBytes());
//7.释放资源(Socket,ServerSocket)
socket.close();
}
}
public class TCPClient {
public static void main(String args[]) throws UnknownHostException, IOException {
//1.创建一个客户端对象Socket,构造方法绑定服务器的IP地址和端口号
Socket socket = new Socket("localhost", 8888);
System.out.println("Client:"+socket);
//2.使用Socket对象中的方法getOutputStrem()获取网络字节输出流OutputStream对象
OutputStream os = socket.getOutputStream();
//3.使用网络字节输出流对象去调用其下的方法write()给服务器端发送数据
os.write("你好呀服务器".getBytes());
//4.使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
InputStream is = socket.getInputStream();
//5.使用网络字节输入流InputStream中的方法read(),读取服务器回写给客户端的数据
byte[] bytes = new byte[1024];
int len = is.read(bytes);
System.out.println("服->可->内容:"+new String(bytes,0,len));
//6.释放资源(Socket)
socket.close();
}
}
代码:
public class TCPServer {
public static void main(String args[]) throws Exception{
System.out.println("服务器端已经开启……正在等待着客户端去连接……");
//通过死循环,让服务器端一直处于运行状态,供客户端去连接,避免每次连接时都要重新启动服务器端
//1.创建一个服务器ServerSocket对象,并且指定对连接的端口号 8888
ServerSocket serversocket = new ServerSocket(8888);
//死循环而言,只是提供供客户端去连接 而具体数据的读取 回写 交给循环去做
while(true){
//2.使用ServerScoket对象中的accept()方法,去接收客户端发送过来的请求,获取到请求的客户端Socket对象
Socket socket = serversocket.accept();//一定不能放到线程中
System.out.println("客户端已经连接成功……");
/**
* 客户端上传数据的时候 单独的开启一个线程去做
*
* 使用多线程技术,提高程序的效率
*
* 每来一个客户端,都要开启一个线程,负责数据的读取和回写 操作
*
* 数据的读取,和回写, 就能够和用于客户端连接的代码进行分离
*/
new Thread(new Runnable() {
//线程体,设置线程执行的任务 数据的读取,和回写.
@Override
public void run() {
try {
//3.使用Socket对象中的getInputStream()方法获取到网络字节输入流InputStream对象
InputStream is = socket.getInputStream();
//4.判断D://upload文件夹是否存在 不存在则创建
File file = new File("D://upload");
if(!(file.exists())){
file.mkdirs();
}
/**
* 给照片随机分配一个名字:
* 作用:
* 防止同名的文件被覆盖
* 规则:
* u=域名+毫秒值+随机数
*/
String fileName = "u="+"hongyang"+System.currentTimeMillis()+new Random().nextInt(99999999)+".jpg";
//5.创建一个本地的字节输出流FileOutpustStream对象,构造方法中要指定输出的目的地
//FileOutputStream fos = new FileOutputStream(file+"/copy.jpg");//D://upload/copy.jpg
FileOutputStream fos = new FileOutputStream(file+"/"+fileName);//D://upload/fileName u=2757279190,1879627098&fm=26&gp=0
int len = 0;//读取到的有效字节数
byte[] bytes = new byte[1024];
//6.使用网络字节输入流对象InputStream对象中的read()方法 去读取客户端上传的文件
while((len = is.read(bytes))!=-1){
System.out.println("11111111111");
//7.使用本地字节输出流FileoutputStream对象中的write()方法,把读取到的文件保存到服务器的硬盘上
fos.write(bytes, 0, len);
}
//8.使用Socket对象中的getOutputStream,获取网络字节输出流对象OutputStream对象
//9.使用网络字节输出流对象OutputStream中的方法write(),客户端回写"上传成功"的信息
OutputStream os = socket.getOutputStream();
os.write("上传成功".getBytes());
System.out.println("222222222222");
//10.释放资源(FileOutputStream,Socket)
fos.close();
socket.close();
//serversocket.close();
} catch (Exception e) {
// TODO: handle exception
}
}
}).start();
}
}
}
public class TCPClient {
public static void main(String args[]) throws Exception{
//1.创建一个本地字节输入流FileInputStream对象,构造方法中指定要读取的数据源
FileInputStream fis = new FileInputStream("D://copy.jpg");
//2.创建一个客户端Socket对象,构造方法中指定服务器的IP地址和端口号
Socket socket = new Socket("localhost", 8888);
//3.使用Socket中的方法getOutputStream()获取网络字节输出流OutputStream对象
OutputStream os = socket.getOutputStream();
int len = 0;
byte[] bytes = new byte[1024];
//4.使用本地字节输入流FileInputStream对象中的read()方法去读取本地文件
while((len = fis.read(bytes)) != -1){
//5.使用网络字节输出流OutputStream对象中的write(),把读取到的文件上传到服务器
os.write(bytes, 0, len);
}
/**
* 解决办法:上传完文件,给服务器写一个结束的标记
*
* void shutDown()禁用此套接字的输出流。
* 对于TCP套接字,任何先前写入的数据将被发送,随后是TCP的正常连接终止序列
*/
socket.shutdownOutput();
//6.使用Socket中的getInputStream()方法,获取字节输入流InputStream对象
InputStream is = socket.getInputStream();
//7.使用网络字节输入流InputStream对象中的read()方法读取服务器端回写的数据
while((len = is.read(bytes)) != -1){
//客户端去读取服务器端回写的数据 bytes 有效的字节转换为字符串打印输出
System.out.println("服务器端对客户端回写的数据:"+new String(bytes,0,len));
}
//8.释放资源(FileInputStream,Socket)
fis.close();
socket.close();
}
}
UDP:用户数据报协议(User Datagram Protocol)。UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输例如视频会议都使用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。
但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议
UDP协议弊端:发送数据时,极容易丢包,导致数据不完整,没办法进行正常使用。
1.【接收端】创建DatagramSocket对象,使用socket通信
2.【发送端】,创建DatagramSocket对象,使用socket通信
3.【接收端】指定数据包DatagramPacket,等到发送端发送
4.【发送端】socket对象,调用send()方法,向接收端写数据。
5.【接收端】socket对象,调用receive()方法,接收客户端数据
发送端向接收端发送数据成功。接收端向发送端回写数据。
6.【发送端】在DatagramSocket中指定回写数据时所用端口。
7.【接收端】在DatagramPacket中准备回写数据并连接,调用send()方法回写数据
8.【发送端】在发送端调用receive()方法接收数据
代码:
public class TestReceive {
public static void main(String args[]){
//从键盘上去输入 要回写的的数据
Scanner scanner = new Scanner(System.in);
System.out.println("等待发送端去发送数据……");
//指定接收端的端口号。供发送端去发送数据
try {
DatagramSocket datagramSocket = new DatagramSocket(9999);
byte[] buf = new byte[1024];//你好呀,接收端
//指定数据包去接收数据
DatagramPacket datagramPacket = new DatagramPacket(buf, buf.length);
/**
* 把接收到的数据打印出来,显示在控制台上
*
* 接收数据
*/
datagramSocket.receive(datagramPacket);
int length = datagramPacket.getLength();
//打印出来,显示在控制台上 将存储的数据包的字节数组转换为字符串进行打印输出
//按照实际字节大小去转为字符串输出
String data = new String(buf, 0, length);//有效字节?
System.out.println("接收端~接收的~发送端的内容:"+data);
/**
* TCP/IP协议一样
* 给发送端去回写数据 收到谢谢 发送和接收吗?
*
* 数据的发送 完成对发送端数据的回写
*
*/
//都是以字符串的形式存在的
//String data1 = "收到,谢谢";
System.out.println("请输入要回写的数据:");
String data1 = scanner.nextLine();
byte[] buf1 = data1.getBytes();//字符串数据转换过来的
/**
* 指定要连接哪个接收端 指定IP地址,端口号 数据包类
* 1.用于存储发送数据的这个字节数组
* 2.字节数组中要发送数据的长度
* 3.指定接收端的IP地址 InetAddress "localhost"
* 4.接收端的端口号
*/
try {
//成功的构建了数据包,要发送的数据,以及接收端的IP和Port
DatagramPacket datagramPacket1 = new DatagramPacket(buf1, buf1.length, InetAddress.getByName("localhost"), 8888);
//准备好的数据包发送出去,发送到我们的接收端 只需要调用一个方法就可以 send()
datagramSocket.send(datagramPacket1);
//关闭socket资源
datagramSocket.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class TestSend {
public static void main(String args[]) throws Exception{
//从键盘上去输入 要发送的数据
Scanner scanner = new Scanner(System.in);
//就是发送端和接收端进行通信的socket
DatagramSocket datagramSocket = new DatagramSocket(8888);//接收端 回写数据时 需要的端口号
//都是以字符串的形式存在的
//String data = "你好呀,接收端";
System.out.println("请输入要发送的数据:");
String data = scanner.nextLine();
byte[] buf = data.getBytes();//字符串数据转换过来的
/**
* 指定要连接哪个接收端 指定IP地址,端口号 数据包类
* 1.用于存储发送数据的这个字节数组
* 2.字节数组中要发送数据的长度
* 3.指定接收端的IP地址 InetAddress "localhost"
* 4.接收端的端口号
*/
try {
//成功的构建了数据包,要发送的数据,以及接收端的IP和Port
DatagramPacket datagramPacket = new DatagramPacket(buf, buf.length, InetAddress.getByName("localhost"), 9999);
//准备好的数据包发送出去,发送到我们的接收端 只需要调用一个方法就可以 send()
datagramSocket.send(datagramPacket);
//接收,接收端回写过来的数据 数据的接收
byte[] buf1 = new byte[1024];//你好呀,接收端
//指定数据包去接收数据
DatagramPacket datagramPacket1 = new DatagramPacket(buf1, buf1.length);
/**
* 把接收到的数据打印出来,显示在控制台上
*
* 接收数据
*/
datagramSocket.receive(datagramPacket1);
int length = datagramPacket1.getLength();
//打印出来,显示在控制台上 将存储的数据包的字节数组转换为字符串进行打印输出
//按照实际字节大小去转为字符串输出
String data1 = new String(buf1, 0, length);//有效字节?
System.out.println("接收端~接收的~发送端的内容:"+data1);
//关闭socket资源
datagramSocket.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}