一.网路编程知识
意义:过去我们IO流是把本地的文件传递而已,网络编程是把自己电脑的数据传递给另一台电脑,并且回馈信息。这是数据通讯
1.通讯三要素
1.1 IP地址
(1)获取对方主机IP
1.2 端口号
(1)数据发送到对方主机指定的应用程序上,为了区别主机上的应用程序,会定义一些数字标识(端口)来区分每一个应用程序。这个端口也被称为逻辑端口(只有相同的应用程序才能解析相应的数据,所以要发送给指定的应用程序)每一个网络程序都有一个端口号
1.3 传输协议
(1)定义通讯规则,这种规则被称为通信协议。两台主句必须按照同种协议才可以实现通信,协议不相同则不能通信。国际组织定义了通用协议TCP/IP。
游戏通信举例:
<1>.魔兽世界:
a.为什么全世界的玩家都能通信?因为都全世界都在用通用的TCP/IP协议
b.为什么你能看见其他玩家?首先你向暴雪服务器发送信息,然后期服务器反馈给你信息,你解析即可得到其他玩家的位置。
c.为什么你能向好友发送信息?你们加好友就是获取了对方的IP地址,然后发送信息就是普通的数据通信原理
<2>.CS
CS局域网连接如何实现?获取对方IP,获取对方CS客户端端口,都有通用协议TCP/IP。如果几个人用自己的协议,如IPX/NetBIOS,那么其他使用TCP/IP协议的人是无法连如这个局域网的
2.重点知识
(1)子网掩码:把一块区域划分到一块相当于局域网当中,这片区域都会用一个外网IP地址。子网掩码是为了多人共用一个地址而出现的。
(2)IPV6:定义了足够多的IP地址,其中不但有数字而且加入了字母
(3)端口号:0~65575端口可任选,不过0~1024端口被系统所用或保留端口。应用程序端口后未设定,系统随机分配。web服务:80;Tomcat:8080;mysql:3306
(4)本地回环地址:127.0.0.1 主机名:localhost,网络主机名www.baidu.com,前边是万维网标识,baidu自定义主机名,com主机所属区域
(5)TCP——Transmission Control Protocol UDP——User Datagram Protocol
(6)192.168.1.0不能用,这个地址代表一个网络地址,代表一个网络段;192.168.1.255代表广播地址,能够给该网络内所有存活主机发广播
3.参考模型
3.1 OSI参考模型( 7层)
3.2 TCP/IP参考模型( 4层)
(1)应用层:主要对应的——>JavaWeb(HTTP协议——Hypertext transfer protocol)
(2)传输层:(TCP/UDP)——>Java网络编程
(3)网际层:(IP)——>Java网络编程
(4)主机至网络层:计算机网络大学所学
二.Java网络编程
1.InetAddress类
方法摘要 | |
---|---|
tatic InetAddress | getLocalHost() 返回本地主机。throw UnknownHostException() |
static InetAddress | getByName(String host) 在给定主机名的情况下确定主机的 IP 地址。throw UnknownHostException() |
static InetAddress[] | getAllByName(String host) 在给定主机名的情况下,根据系统上配置的名称服务返回其 IP 地址所组成的数组。throw UnknownHostException() |
String | getHostAddress() 返回 IP 地址字符串(以文本表现形式)。 |
String | getHostName() 获取此 IP 地址的主机名。 |
2.Socket
(1)科普:最早赛扬系列CPU,CPU要插在主板相应的插槽上,最早的插槽为Socket 370,这里370指的是CPU的帧角数,370的插槽能插370帧角的CPU
(2)这里的Socket是指软件的Socket,每个软件都是一个插槽(Socket)准备接受对方传过来的数据。所以想要通信,必须先要有插槽(Socket)
(3)通常所说网络通信及Socket通信,通信的两端必须都有Socket,数据通过IO流传输
3.传输协议
3.1 UDP协议
(1)特点:面向无连接,无连接——网络通信有两端,你不用在意另外一端在与否,都向它发送数据包,没找到的话包则丢掉;通讯无需建立连接。所以速度快,是不可靠协议。将数据及源和目的封装成数据包,数据大小限制在64K以下。
(2)对应应用:QQ聊天、视频会议(凌波视频)
(3)原理:远程视频就算数据丢了也无所谓。可像下载就不建议使用这种协议,因为存在本地的数据必须保证数据的完整性
(4)模型:步话机(通过调节至共同频段来实现通话,对方没开的话你发数据对方无法收到)
3.1.1 DatagramSocket类
意义:用来发送和接收数据包的套接字
构造方法摘要 | |
---|---|
DatagramSocket() 构造数据报套接字并将其绑定到本地主机上任何可用的端口。 | |
DatagramSocket(int port) 创建数据报套接字并将其绑定到本地主机上的指定端口。 |
方法摘要 | |
---|---|
void | receive(DatagramPacket p) 从此套接字接收数据报包。(阻塞式方法) |
void | send(DatagramPacket p) 从此套接字发送数据报包。 |
void | close() 关闭此数据报套接字。 |
3.1.2 DatagramPacket类
意义:因为数据包是一个复杂事物,包括源端口,源地址,目的端口,目的地址,所有封装为对象
构造方法摘要 | |
---|---|
DatagramPacket(byte[] buf, int length) 构造 DatagramPacket ,用来接收长度为 length 的数据包。(定义要接受的数据包) | |
DatagramPacket(byte[] buf, int length, InetAddress address, int port) 构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。(定义要发送的数据包) |
方法摘要 | |
---|---|
InetAddress | getAddress() 返回某台机器的 IP 地址,此数据报将要发往该机器或者是从该机器接收到的。 |
byte[] | getData() 返回数据缓冲区。 |
int | getLength() 返回将要发送或接收到的数据的长度。 |
int | getPort() 返回某台远程主机的端口号,此数据报将要发往该主机或者是从该主机接收到的。 |
3.1.3 使用方法( 代码)
/*
需求:练习用Socket实现两台计算点间的的数据发送与接收
步骤:1.通过DatagramPacket来为数据打包
2.通过DatagramSocket来发送数据包
3.通过DatagramSocket来接收DatagramPacket数据包
*/
import java.net.*;
class SocketSendPractice
{
public static void main(String[] args)throws Exception{
//建立UDP的Socket的服务
DatagramSocket ds = new DatagramSocket();
//建立数据缓冲区
byte[] buf = "聊天程序的第一步".getBytes();
//获取本地主机的IP对象
InetAddress ia = InetAddress.getLocalHost();
//建立需要发送的数据包
DatagramPacket dp = new DatagramPacket(buf,buf.length,ia,10000);
//发送数据
ds.send(dp);
//关闭资源
ds.close();
}
}
class SocketReceivePractice
{
public static void main(String[] args)throws Exception{
//建立Socket服务
DatagramSocket ds = new DatagramSocket(10000);
//建立接收数据的缓冲区
byte[] buf = new byte[1024];
//建立接收数据的数据包
DatagramPacket dp = new DatagramPacket(buf,buf.length);
//接收数据
ds.receive(dp);
System.out.println(new String(dp.getData(),0,dp.getLength()));
ds.close();
}
}
3.1.4 键盘录入(UDP 代码)
import java.net.*;
import java.io.*;
/**
*键盘输出数据发送到指定PC,使用UDP传输
*/
class UdpSend
{
public static void main(String[] args)throws Exception{//代码观看方便未处理异常
//建立发送断Socket
DatagramSocket ds = new DatagramSocket(8888);
//获取键盘录入
//BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
BufferedInputStream bufis = new BufferedInputStream(System.in);
//建立对方IP地址对象
InetAddress ip = InetAddress.getLocalHost();
//String line = "";
int line;
byte[] buf = new byte[1024];
while((line=bufis.read(buf))!=-1){
//buf = line.getBytes();
//封装数据到包中
DatagramPacket dp = new DatagramPacket(buf,line,ip,10000);
ds.send(dp);
}
bufis.close();
ds.close();
}
}
class UdpReceive
{
public static void main(String[] args)throws Exception{
//建立发送断Socket
DatagramSocket ds = new DatagramSocket(10000);
//建立接收数据的包
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
while(true){
ds.receive(dp);
System.out.print(dp.getAddress().getHostName()+":"+dp.getPort()+"......"+new String(dp.getData(),0,dp.getLength()));
}
//ds.close();
}
}
3.1.5 仿QQ的聊天GUI界面实现( 代码)
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.text.*;
import java.util.*;
/**
*实现主机间的聊天并且用GUI实现窗口
*1.ChattingFrame类是界面实现类,Send是发送数据线程类,Receive是接收数据线程类
*2.界面类构建时就对开启接受线程,实现实时接收数据的功能
*3.没输入一条数据都会建立一个线程发送对象来发送数据包,并且分别向本机与对方机发送数据包
*/
class NewChattingFrameTest
{
public static void main(String[] args)throws Exception{
DatagramSocket dsSend = new DatagramSocket();
DatagramSocket dsReceive = new DatagramSocket(10000);
new ChattingFrame(dsSend,dsReceive);
}
}
class ChattingFrame
{
private Frame f;
private TextArea taSend,taReceive;
private Panel pSend,pReceive;
private Button b;
private DatagramSocket dsSend,dsReceive;
//这个窗体是为了传送接收信息的,构造时需要发送和接收数据的包
ChattingFrame(DatagramSocket dsSend,DatagramSocket dsReceive){
this.dsSend = dsSend;
this.dsReceive = dsReceive;
init();
Receive r = new Receive(dsReceive,taReceive);
new Thread(r).start();
}
public void init(){
f = new Frame("聊天程序");
f.setBounds(400,200,500,420);
f.setLayout(new BorderLayout(10,10));
//建立上下主面板
pSend = new Panel();
pSend.setPreferredSize(new Dimension(300,100));
pReceive = new Panel();
pReceive.setPreferredSize(new Dimension(300,300));
//布局下边的发送面板内的内容
pSend.setLayout(new BorderLayout(10,10));
taSend = new TextArea();
taSend.setPreferredSize(new Dimension(250,60));
b = new Button("发送");
b.setPreferredSize(new Dimension(30,20));
pSend.add(taSend,BorderLayout.CENTER);
pSend.add(b,BorderLayout.EAST);
//布局上边接收面板的内容
pReceive.setLayout(new BorderLayout());
taReceive = new TextArea();
taReceive.setEditable(false);
pReceive.add(taReceive);
//把主面板添加进窗体内
f.add(pSend,BorderLayout.SOUTH);
f.add(pReceive,BorderLayout.CENTER);
myEvent();
f.setVisible(true);
}
public void myEvent(){
f.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e){
System.exit(0);
}
});
//添加按钮监听,点击按钮发送信息
b.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
//建立发送线程并发送数据
new Thread(new Send(dsSend,taSend.getText())).start();
//清空文本区域
taSend.setText("");
}
});
//添加键盘监听,点击回车发送数据
taSend.addKeyListener(new KeyAdapter(){
public void keyPressed(KeyEvent e){
if(e.getKeyCode()==KeyEvent.VK_ENTER && !e.isControlDown()){
e.consume();
new Thread(new Send(dsSend,taSend.getText())).start();
taSend.setText("");
}
}
});
}
}
class Send implements Runnable
{
private DatagramSocket ds;
private String taSend;
//构造时需要接收要发送的数据和发送数据的包
Send(DatagramSocket ds,String taSend){
this.ds = ds;
this.taSend = taSend;
}
public void run(){
try
{
if(taSend==null)
return;
byte[] buf = taSend.getBytes();
if(buf.length!=0){
DatagramPacket dp = new DatagramPacket(buf,buf.length,InetAddress.getByName("10.23.12.86"),10000);//发送给指定IP
DatagramPacket owndp = new DatagramPacket(buf,buf.length,InetAddress.getLocalHost(),10000);//发送给本机
ds.send(dp);
ds.send(owndp);
}
else
return;
}
catch (UnknownHostException e)
{
throw new RuntimeException("未知主机");
}
catch(Exception e)
{
throw new RuntimeException("IO异常");
}
}
}
class Receive implements Runnable
{
private DatagramSocket ds;
private TextArea taReceive;
//构造时需要接收数据的包和输出接收数据的窗体组件TextArea
Receive(DatagramSocket ds,TextArea taReceive){
this.ds = ds;
this.taReceive = taReceive;
}
public void run(){
//为实现边输入边接收数据的功能,接收程序必须一直执行,不能停止,所以定义无限循环
while(true){
try
{
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
ds.receive(dp);
int length = dp.getLength();
//接收的数据以字符串形式存储
String content = new String(dp.getData(),0,length);
//获取现在的时间以字符串形式存储
SimpleDateFormat sdf = new SimpleDateFormat("H:m:s");
String date = sdf.format(new Date());
//排列出要显示的信息样式保存在一个字符串当中
String textReceive = dp.getAddress().getHostName()+" "+date+" \r\n"+content+"\r\n\r\n";
//把定义好的字符串添加进taRecieve组件
taReceive.append(textReceive);
}
catch (Exception e)
{
throw new RuntimeException("接收出现错误");
}
}
}
}
3.2 TCP协议
(1)特点:面向连接,面向连接——只有对方在才发送数据,否则不发送;通讯需建立连接。可实现大数据的传输,通过三次握手完成连接,是可靠协议。必须建立连接,速率稍低。三次握手——老师问你在吗?1次;我回答我在。2次;老师说我知道你在了。3次。通过这三次握手可完成TCP通道的建立
(2)模型:电话(建立通话连接,通话数据在通道中来回传输)
3.2.1 Socket类
意义:实现了客户端的套接字。建立对象时,通路一建立就会有一个Socket流,其中既有输入流也有输出流,可以用她们对数据进行操作
构造方法摘要 | |
---|---|
Socket() 通过系统默认类型的 SocketImpl 创建未连接套接字 | |
Socket(InetAddress address, int port) 创建一个流套接字并将其连接到指定 IP 地址的指定端口号。 | |
Socket(String host, int port) 创建一个流套接字并将其连接到指定主机上的指定端口号。 |
方法摘要 | |
---|---|
void | connect(SocketAddress endpoint) 将此套接字连接到服务器。 |
InetAddress | getInetAddress() 返回套接字连接的地址。 |
int | getPort() 返回此套接字连接到的远程端口。 |
InputStream | getInputStream() 返回此套接字的输入流。 |
OutputStream | getOutputStream() 返回此套接字的输出流。 |
void | shutdownInput() 此套接字的输入流置于“流的末尾”。 |
void | shutdownOutput() 禁用此套接字的输出流。 |
void | close() 关闭此套接字。 |
3.2.2 ServerSocket类
意义:服务端会获取每一个客户端Socket的对象,并且通过这个对象的输入、输出流来向相应客户端发送数据。获取客户端对象通过accept()方法完成
构造方法摘要 | |
---|---|
ServerSocket(int port) 创建绑定到特定端口的服务器套接字。 | |
ServerSocket(int port, int backlog) 利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号。(指的是连接到服务器中的客户端的最大个数) |
方法摘要 | |
---|---|
Socket | accept() 侦听并接受到此套接字的连接。(阻塞式方法) |
void | close() 关闭此套接字。(可选操作,服务器一般不用关闭) |
3.2.3 代码练习(键盘录入)
3.2.4 代码练习 (上传文件)
import java.io.*;
import java.net.*;
class Client
{
public static void main(String[] args)throws Exception{
//指定服务器地址、端口
Socket s = new Socket(InetAddress.getLocalHost(),10000);
BufferedReader bufr = new BufferedReader(new FileReader("d:\\java.java"));
//获取Socket的输入、输出流对象并且封装为高效率
BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
PrintStream out = new PrintStream(s.getOutputStream(),true);
//向服务器写入文件数据
String line = null;
while((line=bufr.readLine())!=null){
out.println(line);
}
out.shutDownOutput();
String str = in.readLine();
System.out.println(str);
s.close();
}
}
class Server
{
public static void main(String[] args)throws Exception{
//建立服务器Socket对象
ServerSocket ss = new ServerSocket(10000);+
Socket s = ss.accept();
//获取Socket的输入、输出流对象并且封装为高效率
BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
PrintStream out = new PrintStream(s.getOutputStream(),true);
//把读取到的数据写入到本地文件中
PrintWriter pw = new PrintWriter(new FileWriter("E:\\哈哈.java"),true);
String line = null;
while((line=in.readLine())!=null){
if("over".equals(line))
break;
pw.println(line);
}
//Thread.sleep(10000);
out.println("传输成功");
s.close();
}
}
3.2.5 代码练习(多线程上传图片)
import java.io.*;
import java.net.*;
class TcpClient
{
public static void main(String[] args)throws Exception{
File file = new File("1.jpg");
Socket s = new Socket(InetAddress.getLocalHost(),10000);
BufferedInputStream bufis = new BufferedInputStream(new FileInputStream(file));
BufferedInputStream bufin = new BufferedInputStream(s.getInputStream());
OutputStream bufout = s.getOutputStream();//注意客户端和服务端尽量统一写入和发出的流
byte[] buf = new byte[1024];
int len;
while((len=bufis.read(buf))!=-1){
bufout.write(buf,0,len);
}
s.shutdownOutput();
len = bufin.read(buf);
System.out.println(new String(buf,0,len));
bufis.close();
s.close();
}
}
class TcpServer
{
public static void main(String[] args)throws Exception{
ServerSocket ss = new ServerSocket(10000);
while(true){
Socket s = ss.accept();
new Thread(new ThreadServer(s)).start();//每个用户都会创建多个线程
}
//ss.close();
}
}
class ThreadServer implements Runnable
{
private Socket s;
ThreadServer(Socket s){
this. s = s;
}
public void run(){
String ip = null;
try{
int count = 1;
File file = new File("上传文件(1).jgp");
while(file.exists())
file = new File("上传文件("+(++count)+").jgp");//用户创建多个文件
ip = InetAddress.getLocalHost().getHostName();
System.out.println(ip+"......conect.......");
InputStream bufin = s.getInputStream();
PrintWriter out = new PrintWriter(new OutputStreamWriter(s.getOutputStream()),true);
FileOutputStream fos = new FileOutputStream(file);
byte[] buf = new byte[1024];
int len;
while((len=bufin.read(buf))!=-1){
fos.write(buf,0,len);
}
out.println("上传完毕");
fos.close();
s.close();
}catch (Exception e){
throw new RuntimeException(ip);
}
}
}
3.2.6 InetSocketAddress类--->实现SocketAddress接口
注1:此类与InetAddress类不同之处在于封装了IP地址与端口号
用法:Socket类有空参构造函数,可以通过connect(SocketAddress sa)这个方法关联服务端地址
4. URL类
构造方法摘要 | |
---|---|
URL(String spec) 根据 String 表示形式创建 URL 对象。 | |
URL(String protocol, String host, int port, String file) 根据指定 protocol 、host 、port 号和 file 创建 URL 对象。 |
方法摘要 | |
---|---|
boolean | equals(Object obj) 比较此 URL 是否等于另一个对象。 |
String | getFile() 获取此 URL 的文件名。 |
String | getHost() 获取此 URL 的主机名(如果适用)。 |
String | getPath() 获取此 URL 的路径部分。 |
int | getPort() 获取此 URL 的端口号。 |
String | getProtocol() 获取此 URL 的协议名称。 |
String | getQuery() 获取此 URL 的查询部分。(获取地址参数信息) |
URLConnection | openConnection() 返回一个 URLConnection 对象,它表示到 URL 所引用的远程对象的连接。(会连接URL指定主机,返回连接对象) |
5.URLConnection类
意义:这是连接对象,已经实现Socket客户端与服务端之间的连接,无需建立Socket对象。并且次对象内部封装了HTTP协议,能够解析http响应头信息
注1:能够解析获取http响应头数据
方法摘要 | |
---|---|
InputStream | getInputStream() 返回从此打开的连接读取的输入流。 |
OutputStream | getOutputStream() 返回写入到此连接的输出流。 |
6. 浏览器与服务器原理
6.1 服务器:自定义
import java.net.*;
import java.io.*;
/**
*客户端:浏览器
*服务端:自定义
*/
class java
{
public static void main(String[] args)throws Exception{
ServerSocket ss = new ServerSocket(10000);
Socket s = ss.accept();
BufferedReader bufr = new BufferedReader(new InputStreamReader(s.getInputStream()));
PrintStream pw = new PrintStream(s.getOutputStream());
String line = null;
while((line=bufr.readLine())!=null){//这里必须注释掉才能退出read()方法,因为客户端浏览器write()方法未添加结束标记
System.out.println(line);
}
pw.println("欢迎");
s.close();
}
}
这个程序接收到浏览器发送来的数据为:(打印在控制台上的数据)
GET / HTTP/1.1
Host: localhost:10000
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36 SE 2.X MetaSr 1.0
Accept-Encoding: gzip,deflate,sdch
Accept-Language: zh-CN,zh;q=0.8
这是HTTP请求消息头,是客户端浏览器发送过来的请求
6.2 浏览器:自定义
import java.net.*;
import java.io.*;
/**
*客户端:自定义浏览器
*服务端:Tomcat服务器
*/
class Client
{
public static void main(String[] args)throws Exception{
//向Tomcat服务器发送数据
Socket s = new Socket(InetAddress.getLocalHost(),80);
PrintWriter pw = new PrintWriter(s.getOutputStream());
//向Tomcat服务器发送Http请求消息头
pw.println("GET /myWeb/doing.html HTTP/1.1");
pw.println("Accept: */*");
pw.println("Accept-Language: zh-CN,zh;q=0.8");
pw.println("Host: localhost");
pw.println("Connection: closed");
pw.println();
pw.println();
BufferedReader bufr = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = null;
System.out.println(188);
while((line=bufr.readLine())!=null){
System.out.println(188);
System.out.println(line);//打印Tomcat返回的数据
}
s.close();
}
}