admin管理员组文章数量:1621909
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
网络编程
1.什么是网络编程?
网络编程就是编写网络应用程序,例如QQ,迅雷等都是网络应用程序,它们既可以将数据利用网络发到某太主机上,又能从网络上下载资源,这种利用了网络的程序的编写就叫做网络编程。
2.网络编程三要素
ip地址,端口号,传输协议。看下图:
OSI参考模型是国际化组织将网络划分的层次,处于模块化的考虑,使得网络这个复杂的问题被分成几个简单的问题来解决。
但是这种划分是比较复杂的,TCP/IP参考模式是在OSI的基础上进行了简化,它将网络划分为四层:应用层,传输层,网际层,网络接口。
3.传输协议和Socket
传输协议: TCP:面向连接,因此可靠,但是速度慢(下载),通过三次握手,在连接中传输数据。 对于TCP,我们可以将其想象成打电话,我们知道打电话,对方必须接了我们才能通话,而对方只要接了,就建立了连接,而且能肯定将信息传送到对方那里,也就是可靠的。但是速度比较慢,因为我们需要等待对方接电话。但是每次发送多少信息是不限制的,因为只要通着电话,我们想说多久就说多久。 UDP:面向无连接,因此不可靠,但是速度快(QQ),每个数据报在64K以内。 对于UDP,我们可以想象成发短信,只要我们有对方的号码,那么不需要对方同意,我们一样可以将信息发过去,当然了,由于不知道对方的确切状态,比如关机等等,我们不能肯定对方一定接收到了信息,也就是不可靠。但是它速度快,因为我们不需要等待对方的确定。但是我们知道,每条短信的字数是有固定的限制的,这就是数据报的大小。Socket:套接字,也叫做插座。我们知道,网络应用程序的使用,至少需要有两个端点,例如客户端和服务端,它们通过网络互相交换数据等,那么又是什么帮助他们跟网络进行交互的呢?没错,就是Socket,它作为网络中的端点,负责处理网络问题。
4.使用TCP/UDP的具体步骤
UDP:DatagramSocket:用于以UDP形式发送接收数据的套接字。
DatagramPacket:用于被DatagramSocket发送和接收的数据格式。
发送数据流程:
1.建立UDPSocket服务。
2.封装数据报DatagramPacket。
3.send方法发送数据。
4.关闭资源
接收数据流程:
1.建立UDPSocket服务,需要监听一个端口。
2.定义一个数据报,存储字节数据。之所以存入数据报对象中,是因为它有很多方法可以提取信息。
3.通过receive(阻塞式)方法将数据存入数据报。
4.通过数据报的方法提取信息,并使用这些信息。
5.关闭资源。
/*
UDP方式发送接收数据(键盘输入,over结束发送,类似收音机,一边发,一边收,单工)
发送数据流程:
1.建立UDPSocket服务。
2.封装数据报DatagramPacket。
3.send方法发送数据。
4.关闭资源
接收数据流程:
1.建立UDPSocket服务,需要监听一个端口。
2.定义一个数据报,存储字节数据。之所以存入数据报对象中,是因为它有很多方法可以提取信息。
3.通过receive(阻塞式)方法将数据存入数据报。
4.通过数据报的方法提取信息,并使用这些信息。
5.关闭资源。
为什么发送接收的while(true)没有死循环呢?
这是因为Scanner.nextLine和Socket的receive方法都是阻塞式的。
*/
import java.*;
import java.util.*;
class Send
{
public static void main(String[] args) throws Exception
{
DatagramSocket ds = new DatagramSocket(8888);//该套接字与8888相关联
byte[] buf=new byte[1024];
DatagramPacket dp=new DatagramPacket(buf,buf.length,InetAddress.getByName("127.0.0.1"),10000);//通过10000发送数据
Scanner scn=new Scanner(System.in);
while(true)
{
String data=scn.nextLine();
dp.setData(data.getBytes());
dp.setLength(data.getBytes().length);
ds.send(dp);
if(data.equals("over"))break;
}
ds.close();
}
}
class Receive
{
public static void main(String[] args) throws Exception
{
DatagramSocket ds=new DatagramSocket(10000);
byte[] buf=new byte[1024];
DatagramPacket dp=new DatagramPacket(buf,buf.length);
while(true)
{
ds.receive(dp);
String ip=dp.getAddress().getHostAddress();
int port=dp.getPort();
String data=new String(dp.getData(),0,dp.getLength());
System.out.println(ip+"在端口:"+port+"上发送:"+data);
if(data.equals("over"))break;
}
ds.close();
}
}<strong>
</strong>
运行图:
TCP:
Socket:建立TCP中的客户端(建立时就要指定服务端主机)。
ServerSocket:建立TCP中的服务端。
使用客户断IO流传输数据。
关闭资源。
客户端:
1.创建Socket对象,指定服务器主机。
2.为了发送数据,获取输出流,向流中写入数据。
(3.如果服务端有返回信息,那就获取输入流,获取数据。)
4.关闭资源(关闭Socket也就自动关闭了与其相连的资源,例如输出流)。
服务端:
1.创建ServerSocket对象。
2.获取连接过来的客户端Socket对象,通过accept方法,该方法为阻塞方法。
3.获取客户端对象的输入流读取数据并操作数据。
(4.可以通过客户端对象的输出流来将信息发回给客户端)
5.关闭客户端。
6.关闭服务端(可选,一般服务端是不会关闭的)。
//测试TCP方式的客户端和服务端
//客户端将信息发送服务端,服务端打印到控制台(并没有返回信息给客户端)
import java.io.*;
import java.*;
class TcpClient
{
public static void main(String[] args) throws Exception
{
Socket s= new Socket("127.0.0.1",10001);
OutputStream os=s.getOutputStream();
os.write("我爱你中国".getBytes());
s.close();
}
}
class TcpServer
{
public static void main(String[] args) throws Exception
{
ServerSocket ss=new ServerSocket(10001);
Socket s=ss.accept();
String ip=s.getInetAddress().getHostAddress();
System.out.println("ip:"+ip+"...is connected!");
InputStream is=s.getInputStream();
byte[] buf=new byte[1024];
int len=is.read(buf);
System.out.println(new String(buf,0,len));
s.close();
ss.close();
}
}
运行图:
5.网络编程小练习
文本转换服务:
//需求:服务端收到客户端信息后,会反馈信息给客户端
//文本转换器,客户端发送文本到服务端,服务端返回大写给客户端
import java.io.*;
import java.*;
import java.util.*;
class TcpClient2
{
public static void main(String[] args) throws Exception
{
//指定服务端主机
Socket s=new Socket("127.0.0.1",10002);
//读写键盘
BufferedReader bw = new BufferedReader(new InputStreamReader(System.in));
//网络输出流
BufferedWriter bwOut = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
//网络读取流
BufferedReader bwIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line=null;
while((line=bw.readLine())!=null)
{
bwOut.write(line);
//重点:1.主要带缓冲区的IO对象要刷新,否则数据并没有写入流中,而是存在于缓冲区中
// 2.文件结束标记,此处我们使用over为双方认可结束标记(1.over等自定义标记,2.时间戳,3.Socket方法)
// 3.要发送换行符,因为readLine方法是以换行为标记的,如果读取不到换行,那么它会一直阻塞
bwOut.newLine();
bwOut.flush();
if(line.equals("over"))break;
System.out.println(bwIn.readLine());
}
//关闭流资源
bw.close();
s.close();
}
}
class TcpServer2
{
public static void main(String[] args) throws Exception
{
//创建服务端Socket对象,指定监听端口号
ServerSocket ss = new ServerSocket(10002);
Socket s = ss.accept();
//网络输出流
BufferedWriter bwOut = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
//网络读取流
BufferedReader bwIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line=null;
while((line=bwIn.readLine())!=null)
{
if(line.equals("over"))break;
bwOut.write(line.toUpperCase());
bwOut.newLine();
bwOut.flush();
}
s.close();
ss.close();//可选
}
}
运行图:
上传文件:
/*
从客户端向服务端上传文件
客户端循环上传文件内容,接收一句服务端返回的上传成功的信息
服务端收到客户端信息,写入本地一个文件。
由于要报告上传进度,因此需要文件大一点比较明显
在此我们选择一个.exe的压缩文件作为上传对象
将文件封装成File对象,方便操作
用来测试Tcp上传文件的测试文件
需求:客户端将此文件上传到服务端
服务端收到后将内容写到本地一个文件中
特点:
1.客户端根据上传进度,每上传百分之十打印到控制台一次
2.服务端将文件写入本地时,文件名为原文件名+上传开始的时间毫秒数
因此上传前客户端先发应该的文件名过去给服务端创建文件。
发现问题:
==============
1.
如果我们向先写入一个文件名(字符串),再写入一堆数据
那么我们不能全部使用一个字节流输出,否则文件名会混在数据当中,无法获取到。
==============
2.
当我们在客户端从图片或者文件中读取数据时使用BufferedInputStream,向网络输出流写数据使用BufferedOutputStream
在服务端从网络读取流中取数据使用BufferedOutputStream,写入新文件中使用BufferedOutputStream
只要使用了包装类,也就是用到了缓冲区,那么都需要进行flush,否则数据会丢失一部分。
*/
import java.*;
import java.io.*;
class TcpUploadFileClient
{
public static void main(String[] args) throws Exception
{
Socket s = new Socket("127.0.0.1",10003);
//将文件封装成对象
File file = new File("TcpUpload.exe");
//使用.分割需要写作"\\."来转义。
String[] strs=file.getName().split("\\.");
String filename=strs[0]+(System.currentTimeMillis()+"")+"."+strs[1];
//首先我们要将文件名写到服务端
//问题:如果使用OutputStream写入文件名的话,文件名的字节数组会和数据混杂在一起
//因此我们使用BufferedWriter
BufferedWriter bwOut = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
bwOut.write(filename);
bwOut.newLine();
bwOut.flush();
//文件读取流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
//网络输出流
BufferedOutputStream bosOut = new BufferedOutputStream(s.getOutputStream());
//网络读取流,作用就是最后读取服务端发回来的上传成功信息
BufferedReader brIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
byte[] buf = new byte[1024];//1K
int sum = (int)(file.length()/1024.0/10.0);//百分之十需要传几次
for(int count=0,len=bis.read(buf);len!=-1;len=bis.read(buf))
{
count++;
if(count%sum==0)System.out.println("进度:%"+(10*(count/sum)));
bosOut.write(buf,0,len);
bosOut.flush();
}
s.shutdownOutput();
String line = brIn.readLine();
System.out.println(line);
bis.close();
s.close();//关闭此流,与其相关的IO流也就关闭了。
}
}
class TcpUploadFileServer
{
public static void main(String[] args) throws Exception
{
ServerSocket ss = new ServerSocket(10003);
Socket s = ss.accept();
//首先读取文件名并创建该文件
//================================================
//为什么客户端写入26个字节,但是此处却将buf读满了呢???
//问题:我们向将文件名写入流,26字节,又将其他内容写入流,而在读取时,read不会知道
// 哪一部分是文件名,而又没遇到文件结束标记,因此它就会读满buf,也就是1024
//关键在于使用了InputStream来读取数据,我们知道文件名都是字符,因此可以用BufferedReader来读取。
BufferedReader brIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
String filename = brIn.readLine();
File file = new File(filename);
file.createNewFile();
//================================================
//网络读取流
BufferedInputStream bisIn = new BufferedInputStream(s.getInputStream());
//文件输出流,将数据写入文件中
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
//网络输出流,用于再最后向客户端发送反馈信息
BufferedWriter bwOut = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
byte[] buf = new byte[1024];
for(int len=bisIn.read(buf);len!=-1;len=bisIn.read(buf))
{
bos.write(buf,0,len);
bos.flush();
}
bwOut.write("上传成功!");
bwOut.newLine();
bwOut.flush();
bos.close();
s.close();
ss.close();
}
}
运行图:
文件并发上传:
/*
并发上传(多线程)
可以上传多种数据类型(文件,音频,视频)
客户端会显示上传进度(百分比)
上传结束服务端返回"上传成功"等信息
结束标记使用shutDownOutput方法。
*/
import java.io.*;
import java.*;
class TcpMoreUploadFileClient
{
public static void main(String[] args) throws Exception
{
Socket s = new Socket("127.0.0.1",10004);
//将文件封装成对象
File file = new File("TcpMoreUpload.avi");
//使用.分割需要写作"\\."来转义。
String[] strs=file.getName().split("\\.");
String filename=strs[0]+(System.currentTimeMillis()+"")+"."+strs[1];
//首先我们要将文件名写到服务端
//问题:如果使用OutputStream写入文件名的话,文件名的字节数组会和数据混杂在一起
//因此我们使用BufferedWriter
BufferedWriter bwOut = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
bwOut.write(filename);
bwOut.newLine();
bwOut.flush();
//文件读取流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
//网络输出流
BufferedOutputStream bosOut = new BufferedOutputStream(s.getOutputStream());
//网络读取流,作用就是最后读取服务端发回来的上传成功信息
BufferedReader brIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
byte[] buf = new byte[1024];//1K
int sum = (int)(file.length()/1024.0/10.0);//百分之十需要传几次
int xx=0;
for(int count=0,len=bis.read(buf);len!=-1;len=bis.read(buf))
{
count++;
if(count%sum==0)System.out.println("进度:%"+(10*(count/sum)));
bosOut.write(buf,0,len);
bosOut.flush();
xx+=len;
}
//System.out.println(xx);
s.shutdownOutput();
String line = brIn.readLine();
System.out.println(line);
bis.close();
s.close();//关闭此流,与其相关的IO流也就关闭了。
}
}
class Server implements Runnable
{
private Socket s;
Server(Socket s)
{
this.s=s;
}
public void run()
{
try
{
BufferedReader brIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
String filename = brIn.readLine();
File file = new File(filename);
file.createNewFile();
//修改回InputStream
BufferedInputStream bisIn = new BufferedInputStream(s.getInputStream());
//修改回OutputStream
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
BufferedWriter bwOut = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
byte[] buf = new byte[1024];
int xx=0;
for(int len=bisIn.read(buf);len!=-1;len=bisIn.read(buf))
{
bos.write(buf,0,len);
bos.flush();
xx+=len;
}
//System.out.println(xx);
bwOut.write("上传成功!");
bwOut.newLine();
bwOut.flush();
bos.close();
s.close();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
class TcpMoreUploadFileServer
{
public static void main(String[] args) throws Exception
{
ServerSocket ss = new ServerSocket(10004);
{
while(true)
{
Socket s = ss.accept();
new Thread(new Server(s)).start();
}
}
}
}
运行图:
图形界面的聊天窗口:
/*
图形化界面的聊天窗口
需求:
1.收发在一起,类似QQ
2.一个程序中既有收功能,也有发功能(多线程技术)
*/
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.*;
import java.util.*;
import java.text.*;
class Receive implements Runnable
{
private DatagramSocket ds;
private TextArea receiveTa;
private Label ipLab;
Receive(TextArea receiveTa,Label ipLab)
{
this.receiveTa=receiveTa;
this.ipLab=ipLab;
}
public void run()
{
try
{
ds=new DatagramSocket(10000);
byte[] buf=new byte[1024];
DatagramPacket dp=new DatagramPacket(buf,1024);
while(true)
{
ds.receive(dp);
String ip=dp.getAddress().getHostAddress();
String data=new String(dp.getData(),0,dp.getLength());
//处理数据,如何将数据显示到Ta中。
//方法,通过将展示数据的组件传入该类中,得以在该类中操作
ipLab.setText("对方IP:"+ip);
receiveTa.append(new SimpleDateFormat().format(new Date())+"\r\n"+data+"\r\n");
if(data.equals("over"))break;
}
ds.close();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
class MyWindow
{
private Frame win;
private Label ipLab;
private TextArea receiveTa,sendTa;
private Button sendBtn;
MyWindow(){init();}
private void init()
{
win = new Frame("HQ");
win.setBounds(300,200,500,350);
win.setLayout(new FlowLayout());
ipLab = new Label("对方的IP地址显示区域");
receiveTa = new TextArea();
receiveTa.setEditable(false);
sendTa = new TextArea(3,50);
sendBtn = new Button("发送");
win.add(ipLab);
win.add(receiveTa);
win.add(sendTa);
win.add(sendBtn);
myEvent();
win.setVisible(true);
//发送并不在一个独立线程中,而在主线程中
//new Thread(new Send()).start();
//接收在一个独立线程中,时刻等待着接收
//同时我们将展示数据的组件传给接收线程对象,方便其操作该组件内容
new Thread(new Receive(receiveTa,ipLab)).start();
}
private void myEvent()
{
win.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
public void windowOpened(WindowEvent e)
{
//窗口出现,发送框即获得焦点
sendTa.requestFocus();
}
});
//发送按钮添加监听器
sendBtn.addActionListener(new ActionListener(){
private DatagramSocket ds;
public void actionPerformed(ActionEvent e)
{
if(sendTa.getText().trim().length()>0)
sendEvent(ds);
}
});
//文本区域添加监听器(回车即发数据)
sendTa.addKeyListener(new KeyAdapter(){
private DatagramSocket ds;
public void keyPressed(KeyEvent e)
{
//响应回车键,发送信息,但是依然有回车键的默认行为
//也就是换行,也就是每发一次信息,都会直接换到下一行,也就是第二行
if(sendTa.getText().trim().length()>0)
{
if(e.getKeyCode()==KeyEvent.VK_ENTER)
{
sendEvent(ds);
e.consume();//取消默认行为,此处也就是换行的行为
}
}
//不能在没内容的情况下敲击回车,敲了也没反应
if(e.getKeyCode()==KeyEvent.VK_ENTER)
{
e.consume();
}
}
});
}
private void sendEvent(DatagramSocket ds)
{
try
{
ds=new DatagramSocket(8888);
String data = sendTa.getText();
DatagramPacket dp=new DatagramPacket(data.getBytes(),data.getBytes().length,
InetAddress.getByName("127.0.0.1"),10000);
ds.send(dp);
ds.close();
sendTa.setText("");
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
}
class ChatTest
{
public static void main(String[] args)
{
new MyWindow();
}
}
运行图:
6.自定义客户端或者服务端
/*
需求:客户端访问服务器
1.
客户端:浏览器
服务端:自定义
2.
客户端:浏览器
服务端:Tomcat
3.
客户端:自定义浏览器
服务端:Tomcat
*/
import java.io.*;
import java.*;
class CustomServer
{
public static void main(String[] args) throws Exception
{
ServerSocket ss = new ServerSocket(11000);
while(true)
{
Socket s = ss.accept();
System.out.println(s.getInetAddress().getHostAddress());
PrintWriter out = new PrintWriter(new OutputStreamWriter(s.getOutputStream()),true);
//使用PrintWriter要注意:不是所有方法都带自动刷新的。例如write就没有,而println就有
out.println("<a href='www.baidu'>我爱你中国</a>");
s.close();
System.out.println("断开连接");
}
//ss.close();
}
}
运行图:
7.URL和URLConnection
URL,URLConnection:*URL:统一资源定位符,URI范围要比URL要大。
*URLConnection(应用层对象):内部封装了Socket(传输层对象),数据先到传输层,拆包,再将数据主体向上传给应用层。该对象通常由URL的openConnection获得。
好处:1.得到的是数据主体,不包括协议头信息。2.自动发送那些协议内容,不需要自己手动发送。
//测试URL对象的常用方法
import java.*;
class URLDemo
{
public static void main(String[] args) throws Exception
{
URL url = new URL("http://www.baidu/Dir/demo.html?name=helong&age=22");
System.out.println("getProtocol:"+url.getProtocol());
System.out.println("getHost:"+url.getHost());
System.out.println("getPort:"+url.getPort());
System.out.println("getPath:"+url.getPath());
System.out.println("getFile:"+url.getFile());
System.out.println("getQuery:"+url.getQuery());
//如果不指定端口号,那么getPort()==-1,通常操作如下
int port = url.getPort()==-1?80:url.getPort();
System.out.println("Port:"+port);
}
}
/*
getFile()
获取此 URL 的文件名。
String getHost()
获取此 URL 的主机名(如果适用)。
String getPath()
获取此 URL 的路径部分。
int getPort()
获取此 URL 的端口号。
String getProtocol()
获取此 URL 的协议名称。
String getQuery()
获取此 URL 的查询部分。
*/
运行图:
//测试:使用URLConnection来连接服务端获取数据
import java.io.*;
import java.*;
class URLConnectionDemo
{
public static void main(String[] args) throws Exception
{
URL url = new URL("http://www.baidu");
URLConnection conn = url.openConnection();
InputStream in = conn.getInputStream();
byte[] buf=new byte[1024];
in.read(buf);
System.out.println(new String(buf,0,100));
}
}
运行图:
由于百度主页返回的数据量太大,而我们又无法解析,因此只打印了前100个字节的数据。
8.域名解析
我们知道,平时我们需要上哪个网站,例如百度,我们不可能去输入百度的ip地址吧,其实输地址当然也是可以的,但是ip地址比较难以记忆,而网站却非常容易记,但是我们发现,在Socket网络通信中,貌似用的都是ip地址啊,那为什么我们输入网址也能正确访问到我们想要访问的网站呢?这就是域名解析的功劳。 域名解析:将一个网址解析成它对应的ip地址。 我们不仅要问了,这个映射关系式存放在哪里呢?使我们本机吗?这句话只能说对一半错一半,对的是,我们确实可以在本机上配置这个映射关系,错的是我们可以想象,互联网上的网站千千万万,如果都配到我们主机上,那么估计也卡死了吧,所以大部分的网站和它们ip的映射关系是存放在一个叫做域名解析服务器(DNS)上的,下面我们看看使用网址访问一个网站的流程:我们可以看到,当我们输入一个网址后,浏览器首先到本机的hosts文件中查找是否有对应的映射关系,如果找到了,那么就直接返回ip地址,如果没找到,那么就会前往公网的一台DNS上去查找,找到就返回ip地址,如果还是没找到,则浏览器会给出网址错误之类的提示信息。 配置本地映射表:c-windows-system32-drivers-etc-hosts文件
127.0.0.1----localhost
用处:
1.屏蔽网站例如:127.0.0.1----www.xxx
2.加快上网速度:12.13.14.15----www.sina(假设12.13.14.15就是新浪的ip地址)
9.网络编程心得总结
网络编程这一部分还是相当重要的,因为我们知道,现在的程序很多都具备网络功能,这也是发展的一个趋势,因为网络有它无与伦比的特点,那就是资源共享,因此网络编程也就自然而然的水涨船高啦。
重点是掌握TCP,UDP的传输,以此使用对应的对象来构建端点进行网络通信和资源共享等。还有比较重要的就是URL,URLConnection两个类,尤其是URLConnection对象,它的优点在于它是应用层的对象,我们使用它时,可以忽略那些传输层的协议头等问题,只专注于应用层的问题即可。
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
版权声明:本文标题:黑马程序员----网络编程 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/dongtai/1728851701a1176751.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论