网络模型:
OSI参考模型
TCP/IP参考模型
网络通讯要素
IP地址
端口号
传输协议
2.网络参考模型
3.网络通讯要素:
IP地址:InetAddress
网络中设备的标识
不易记忆,可用主机名
本地回环地址:127.0.0.1 主机名:localhost
示例:
classIPDemo {
public static void main(String[] args) throwsException {
// 建立对象i返回本机地址
InetAddress i = InetAddress.getLocalHost();
// 打印本机地址信息
System.out.println(i.toString());
// 打印本机名和本机Ip
System.out.println(i.getHostName()+ ":" + i.getHostAddress());
// 建立对象,根据主机名获取对象信息再传递给对象
InetAddress ia = InetAddress.getByName("PC-Administrator");
// 获取主机地址
System.out.println("address:"+ ia.getHostAddress());
// 获取主机名
System.out.println("name:"+ ia.getHostName());
}
}
运行结果:
PC-Administrator/192.168.1.101
PC-Administrator:192.168.1.101
address:192.168.1.101
name:PC-Administrator
端口号:
用于标识进程的逻辑地址,不同进程的标识
有效端口:0-65535.其中0-1024系统使用或保留端口
传输协议:
通讯的规则:
常见协议:TCP、UDP
4.TCP和UDP
TCP:
将数据及源和目的封装在数据包中,不需要建立连接
每个数据报的大小限制在64k内
因无连接,是不可靠协议
不需要建立连接,速度快
UDP:
建立连接,形成传输数据的通道
在连接中进行大数据量传输
通过三次握手完成连接,是可靠协议
必须建立连接,效率会稍低
5.Socket
Socket就是为网络服务提供的一种机制
通信的两端都有Socket
网络通信其实就是Socket间的通信。
数据在两个Socket间通过IO传输
6.UDP传输
DatagramSocket与DataprogramPacket
·建立发送端、接收端
·建立数据包
·调用Socket的发送接受方法
·关闭Socket
·发送端与接收端是两个独立的运行程序
示例一:(通过UDP传输方式,将一段文字数据发送出去)
思路:(发送端)
1.建立updsocket服务;
2.提供数据,并将数据封装到数据包中;
3.通过Socket服务的发送功能,将数据包发出去;
4.关闭资源;
class UdpSend {
public static void main(String[] args) throwsException {
//创建udp服务。通过DatagramSocket对象。
DatagramSocket ds = new DatagramSocket(8888);
//确定数据,并封装成数据包。
byte[] by = "hello world javatest.".getBytes();
DatagramPacket dp = new DatagramPacket(by, by.length,InetAddress.getByName("192.168.1.101"), 10000);
//通过socket服务,将已有的数据包发送出去。通过send方法。
ds.send(dp);
//关闭资源
ds.close();
}
}
思路:(接收端)
1. 定义一个udpsocket服务,通常会监听一个端口,其实就是给这个接收网络应用程序定义数字标识,方便于明确哪些数据过来本程序可以处理;
2. 定义一个数据包,因为要存储接收到的字节数组(数据包对象中有更多功能可以提取字节数据中的不同数据信息);
3. 通过socket服务的receive方法将收到的数据存入到已经定义好的数据包中;
4. 通过数据包对象的特有功能,将这些不同的数据取出,打印在控制台上。
5. 关闭资源
class UdpRece {
public static void main(String[] args) throwsException {
//创建udp socket,建立端点。
DatagramSocket ds = new DatagramSocket(10000);
while(true) {
//定义数据包。用于存储数据
byte[] by = new byte[1024];
DatagramPacketdp = new DatagramPacket(by, by.length);
//通过服务的receive方法将收到数据存入数据包中。
ds.receive(dp);
//通过数据包的方法获取其中的数据。
String ip = dp.getAddress().getHostAddress();
String data = new String(dp.getData(), 0,dp.getLength());
int port = dp.getPort();
System.out.println(ip +"-" + port + ":" + data);
}
}
}
示例二:(现在将上面的数据通过键盘录入形式给出)
class UdpSend {
public static void main(String[] args) throwsException {
//创建udp服务,通过DatagramSocket对象
DatagramSocket ds = new DatagramSocket(8888);
BufferedReader in = new BufferedReader(newInputStreamReader(System.in));
String line = null;
while((line = in.readLine()) != null) {
if("886".equals(line))
break;
//确定数据,并封装成数据包
byte[] by = line.getBytes();
DatagramPacket dp = new DatagramPacket(by, by.length, InetAddress.getByName("192.168.1.101"),1000);
//通过socket服务,将已有的数据包发送出去
ds.send(dp);
}
//关闭资源
ds.close();
}
}
class UdpRece {
public static void main(String[] args) throwsException {
//创建udp socket,建立端点。
DatagramSocket ds = new DatagramSocket(10000);
while(true){
//定义数据包,用于存储数据
byte[] by=new byte[1024];
DatagramPacket dp=new DatagramPacket(by,by.length);
//通过服务的receive方法将收到数据存入数据包中
ds.receive(dp);
String ip=dp.getAddress().getHostAddress();
//通过数据包的方法获取其中的数据
int port=dp.getPort();
String data=newString(dp.getData(),0,dp.getLength());
System.out.println(ip+ ":" + port + "-" + data);
}
}
}
7.UDP聊天程序
通过键盘录入获取要发送的信息
将发送端和接收端分别装到两个线程中
//发送端
class Send implements Runnable {
private DatagramSocket ds;
public Send(DatagramSocket ds) {
this.ds= ds;
}
public void run() {
try{
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
String line = null;
while((line = in.readLine()) != null) {
byte[] by = line.getBytes();
DatagramPacket dp = new DatagramPacket(by, by.length,InetAddress.getByName("192.168.1.255"), 10000);
ds.send(dp);
if("886".equals(line))
break;
}
}catch (Exception e) {
System.out.println(e.toString());
}
}
}
//接收端
class Rece implements Runnable {
private DatagramSocket ds;
public Rece(DatagramSocket ds) {
this.ds= ds;
}
public void run() {
try{
while(true) {
byte[] by = new byte[1024];
DatagramPacket dp = new DatagramPacket(by, by.length);
ds.receive(dp);
String ip = dp.getAddress().getHostAddress();
String data = new String(dp.getData(), 0, dp.getLength());
if("886".equals(data)) {
System.out.println(ip+ ":" + "...离开聊天室");
break;
}
System.out.println(ip+ ":" + data);
}
}catch (Exception e) {
System.out.println(e.toString());
}
}
}
class ChatTest {
public static void main(String[] args) throws Exception {
DatagramSocket sendSocket = new DatagramSocket();
DatagramSocket receSocket = new DatagramSocket(1000);
newThread(new Send(sendSocket)).start();
newThread(new Rece(receSocket)).start();
}
}
8.TCP传输
Socket和ServerSocket
建立客户端和服务器端
建立连接后,通过Socket中的io流进行数据的传输
关闭socket
同样,客户端与服务端是两个独立的应用程序
示例:(TCP传输)
基本思路:
客户端:
客户端需要明确服务器的ip地址以及端口,这样才可以去试着建立连接,如果连接失败,会出现异常;
连接成功,说明客户端与服务端建立通道,那么通过IO流就可以进行数据的传输,而Socket对象已经提供了输入流和输出流对象,通过getInputStream与getOutputStream获取即可。
与服务端通讯结束后,关闭Socket。
//客户端
class TcpClient {
public static void main(String[] args) throws Exception {
//创建客户端的socket服务。指定目的主机和端口
Socket s = new Socket("192.168.1.1", 6666);
//为了发送数据,应该获取socket流中的输出流
OutputStream out = s.getOutputStream();
out.write("TcpHello world Java Test".getBytes());
s.close();
}
}
服务端:
服务端需要明确它要处理的数据是从哪个端口进入的;
当有客户端访问时,要明确是哪个客户端,可以通过accept获取已经连接的客户端对象,并通过该对象与客户端通过IO流进行数据传输;
当该客户端访问结束,关闭该客户端。
//服务端
class TcpServer {
public static void main(String[] args) throws Exception {
//建立服务端socket服务,并监听一个端口
ServerSocket ss = new ServerSocket(6666);
while(true) {
//通过accept方法获取连接过来的客户端对象
Socket s = ss.accept();
String ip =s.getInetAddress().getHostAddress();
System.out.println(ip+ "...connected");
//获取客户端发送过来的数据
InputStream in = s.getInputStream();
byte[] by = new byte[1024];
int len = in.read(by);
System.out.println(newString(by, 0, len));
//关闭客户端
s.close();
}
}
}
练习一:(建立一个文本转换服务器:客户端给服务器发送文本,服务器将文本转成大写后返回给客户端,而且服务端可以不断地进行文本转换,当客户端输入over时,转换结束)
分析:
客户端:
既然是操作设备上的数据,那么就可以使用io技术,并按照io的操作规律来思考,
源:键盘
目的:网络设备,网络输出流
操作对象:文本数据,选择字符流
步骤:
1. 建立连接;
2. 获取键盘录入;
3. 将数据发送给服务端;
4. 显示服务端返回的大写数据;
5. 结束,关闭资源
//客户端
class TransClient {
public static void main(String[] args) throwsException {
Socket s = new Socket("192.168.1.1", 6666);
BufferedReader fromUser = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(s.getOutputStream(), true);
BufferedReader fromServer = new BufferedReader(newInputStreamReader(s.getInputStream()));
String line = null;
while((line = fromUser.readLine()) != null) {
if("over".equals(line))
break;
out.println(line);
String str = fromServer.readLine();
System.out.println("server:"+ str);
}
fromUser.close();
s.close();
}
}
//服务端
class TransServer {
public static void main(String[] args) throwsException {
ServerSocket ss = new ServerSocket(6666);
Socket s = ss.accept();
String ip =s.getInetAddress().getHostAddress();
System.out.println(ip+ "...connected");
BufferedReader fromClient = new BufferedReader(new InputStreamReader(s.getInputStream()));
PrintWriter out = new PrintWriter(s.getOutputStream(), true);
Stringline = null;
while((line = fromClient.readLine()) != null) {
System.out.println(line);
out.println(line.toUpperCase());
}
s.close();
ss.close();
}
}
练习二:(上传文本)
//客户端
class TextClient {
public static void main(String[] args) throwsException {
Socket s = new Socket("localhost", 6666);
BufferedReader fromText = new BufferedReader(new FileReader("4.txt"));
PrintWriter out = new PrintWriter(s.getOutputStream(), true);
String line = null;
while((line = fromText.readLine()) != null) {
out.println(line);
}
//关闭客户端的输出流,相当于给流中加入一个结束标记-1
s.shutdownOutput();
BufferedReader fromServer = new BufferedReader(new InputStreamReader(s.getInputStream()));
String str = fromServer.readLine();
System.out.println(str);
fromServer.close();
s.close();
}
}
//服务端:
class TextServer {
public static void main(String[] args) throws Exception {
ServerSocket ss = new ServerSocket(6666);
Socket s = ss.accept();
String ip =s.getInetAddress().getHostAddress();
System.out.println(ip+ "...connected");
BufferedReader fromClient = new BufferedReader(new InputStreamReader(s.getInputStream()));
PrintWriter out = new PrintWriter(new FileWriter("server.txt"), true);
String line = null;
while((line = fromClient.readLine()) != null) {
out.println(line);
}
PrintWriter pw = new PrintWriter(s.getOutputStream(), true);
pw.println("上传成功");
out.close();
s.close();
ss.close();
}
}
练习三:(上传图片)
思路:
客户端通过socket输出流将数据发给服务端,服务端读取客户端发来的图片数据,再将反馈信息送至客户端即可
//客户端
class PicClient {
public static void main(String[] args) throwsException {
Socket s = new Socket("localhost", 6666);
FileInputStream fis = new FileInputStream("D:/Chrysanthemum.jpg");
OutputStream out = s.getOutputStream();
byte[] by = new byte[1024];
int len = 0;
while((len = fis.read(by)) != -1) {
out.write(by,0, len);
}
//告诉服务端数据已写完
s.shutdownOutput();
InputStreamin = s.getInputStream();
byte[] byin = new byte[1024];
int num = in.read(byin);
System.out.println(newString(byin, 0, num));
fis.close();
s.close();
}
}
//服务端
class PicServer {
public static void main(String[] args) throwsException {
ServerSocket ss = new ServerSocket(6666);
Socket s = ss.accept();
InputStream fromClient = s.getInputStream();
FileOutputStream fos = new FileOutputStream("server.bmp");
byte[] by = new byte[1024];
int len = 0;
while((len = fromClient.read(by)) != -1) {
fos.write(by,0, len);
}
OutputStream out = s.getOutputStream();
fos.write("上传成功".getBytes());
fos.close();
s.close();
ss.close();
}
}
练习四:(多用户上传图片)
这个服务端有局限性,当A客户端连接上以后,被服务端获取到,服务端执行具体流程,
这时B客户端连接,只有等待。因为服务端还没有处理完A客户端的请求,还要循环回来执行下次accept方法,所以暂时获取不到B客户端对象。
为了可以让多个客户端同时并发访问服务端,那么服务端最好就是将每个客户端封装到一个单独的线程中,这样,就可以同时处理多个客户端请求。
//客户端
class PicClient {
public static void main(String[] args) throwsException {
if(args.length != -1) {
System.out.println("请选择一个jpg格式的图片");
return;
}
File file = new File(args[0]);
if(file.exists() && file.isFile()) {
System.out.println("该文件有问题,要么不存在,要么不是文件");
return;
}
if(!file.getName().endsWith(".jpg")) {
System.out.println("图片格式错误,请重新选择");
return;
}
if(file.length() > 1024 * 1024 * 5) {
System.out.println("文件过大,没安好心");
return;
}
Socket s = new Socket("localhost", 6666);
FileInputStream fis = new FileInputStream(file);
OutputStream out = s.getOutputStream();
byte[] by = new byte[1024];
int len = 0;
while((len = fis.read(by)) != -1) {
out.write(by,0, len);
}
//告诉服务端数据已写完
s.shutdownOutput();
InputStream in = s.getInputStream();
byte[] byin = new byte[1024];
int num = in.read(byin);
System.out.println(newString(byin, 0, num));
fis.close();
s.close();
}
}
//图片处理线程
class PicThread implements Runnable {
private Socket s;
PicThread(Socket s) {
this.s= s;
}
public void run() {
int count = 1;
String ip =s.getInetAddress().getHostAddress();
try{
System.out.println(ip+ "...connected");
InputStream in = s.getInputStream();
File dir = new File("D:/Chrysanthemum.jpg");
File file = new File(dir, ip + "(" + (count++) +")" + ".jpg");
while(file.exists())
file= new File(dir, ip + "(" +(count++) + ")" + ".jpg");
FileOutputStream fos = new FileOutputStream(file);
byte[] by = new byte[1024];
int len = 0;
while((len = in.read(by)) != -1) {
fos.write(by,0, len);
}
OutputStream out = s.getOutputStream();
out.write("上传成功".getBytes());
fos.close();
s.close();
}catch (Exception e) {
throw new RuntimeException(ip + "上传失败");
}
}
}
//服务端
class PicServer {
public static void mian(String[] args) throwsException {
ServerSocket ss = new ServerSocket(6666);
while(true) {
Socket s = ss.accept();
new Thread(new PicThread(s)).start();
}
}
}
9.TCP传输最容易出现的问题
客户端连接上服务器,两端都在等待,没有任何数据传输
通过例子分析:
因为read或readLine方法是阻塞式。这些方法没有读到结束标记,那么就一直等
从而导致两端都在等待。
解决办法:
自定义结束标记
使用shutdownInput,shutdowmOutput方法(上面的练习中已经使用到了)