您的位置:首页 > 理论基础 > 计算机网络

黑马程序员---网络编程(重点)

2013-05-23 19:41 351 查看
------- Java、.NetAndroid培训期待与您交流!-------
一、网络模型

OSI参考模型(开放式网络:7层体系结构)。

TCP/IP参考模型(4层体系结构)。



二、网络通讯三要素

1、IP地址:InetAddress(找到对方IP地址):

网络中设备的标识不易记忆,可用主机名。本地回环地址:127.0.0.1,主机名:localhost。

InetAddress类:表示互联网协议 (IP) 地址,该类中都是对IP地址的操作。

static InetAddress getLocalHost():返回本地主机。

String getHostAddress():返回 IP 地址字符串(以文本表现形式)。

String getHostName():获取此 IP 地址的主机名。

static InetAddress getByName(String host):获取网络上已连接的主机信息,可能只有IP地址而没有主机名,是因为该主机IP和主机名的映射关系未被注册到DNS服务器上。

注:对网络操作,获取IP地址最重要,即:getHostAddress 。

2、端口号或逻辑端口号:

用于标识进程的逻辑地址,不同进程的标识。

有效端口:0~65535,其中0~1024为系统使用或保留端。

3、传输协议(重点),常见协议:TCP和UDP

3.1、用户数据报协议 UDP:User Datagram Protocol

面向无连接的,不可靠,只求速度数据包在64K以内。

将数据以及源和目的封装在数据包中,不需要建立连接,速度块。

如:视屏对话、桌面共享、某些聊天工具、步话机等。

3.2、传输控制协议 TCP:Transmission Control Protocol

面向连接的,可靠,传输量大,速度慢。

建立连接,形成传输数据的通道,通过三次握手完成连接,效率稍低。

如:打电话。

三次握手:

第一次:发送方A向接收方B发送数据,等待B接收到数据后返回的确认信息。

第二次:B接收到数据,返回给A一个接收到数据的信息,若B未接收到任何数据则不会发送任何信息。

第三次:若A接到到B的确认信息,则发送下一条数据。若A在一定时间内未接收到B返回的信息(可能B未发送信息或者B发送了信息但是在网络传输中出现了延迟或丢失),则认为是刚才发送的数据丢失,再次重新发送,直到收到B的确认信息。

三、Socket套接字

1、网络编程即socket编程,是两台机器间通信的端点。

2、通信的两端都有Socket。

3、数据在两个Socket间通过IO流传输。

四、UDP和TCP编码流程(记住流程)

1、UDP数据包传输

UDP发送端:

示例:通过udp传输方式,把键盘录入将文字数据发送出去。

思路:

1,建立Updsocket服务。

2,通过输入流,录入数据,并将数据封装到数据包中。

3,通过socket服务的发送功能,将数据包发出去。

4,关闭资源。

import java.net.*;
import java.io.*;

class UdpSend
{
public static void main(String[] args) throws Exception
{
//1、建立UDP服务,通过DatagramSocket对象建立。(可以对对象指定一个端口进行监听)
DatagramSocket ds = new DatagramSocket(8888);
//2、通过输入流输入数据,并将数据封装到数据包中
BufferedReader bufIn = new  BufferedReader(new InputStreamReader(System.in));
String line = null;
while ((line=bufIn.readLine())!=null)
{
if ("886".equals(line))
break;
byte[] buf = line.getBytes();
DatagramPacket dp =
new DatagramPacket(buf,buf.length,InetAddress.getByName("psy"),10000);
//3,通过socket服务,将已有的数据包发送出去。通过send方法。
ds.send(dp);
}
//4、关闭资源
ds.close();
}
}

示例:定义一个应用程序,用于接收udp协议传输的数据,并把处理后的信息返回给发送者。

思路:

1,定义UdpSocket服务。通常会监听一个端口。其实就是给这个接收网络应用程序定义数字标识。方便于明确哪些数据过来该应用程序可以处理。

2,定义一个数据包,要存储接收到的字节数据。因为数据包对象中有更多功能可以提取字节数据中的不同数据信息。

3,通过socket服务的receive方法将收到的数据存入已定义好的数据包中。

4,通过数据包对象的特有功能。将这些不同的数据取出。打印在控制台上。

5,关闭资源,接收端的资源一般不关。

import java.net.*;
import java.io.*;

class UdpReceive
{
public static void main(String[] args) throws Exception
{
//1、创建UdpSocket,建立端点,监听端口10000
DatagramSocket ds = new DatagramSocket(10000);
while (true)
{
//2、定义数据包,用于存储数据
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
//3、通过服务的receive方法将首的数据存入数据包中
ds.receive(dp);		//阻塞方法
//4、通过数据包方法获取其中的数据
String ip = dp.getAddress().getHostAddress();
String data = new String(dp.getData(),0,dp.getLength());
System.out.println(ip+"..."+data.toUpperCase());
}
//5、关闭资源
ds.close();
}
}


2、TCP传输

1>tcp分客户端和服务端。

2>客户端对应的对象是Socket,服务端对应的对象是ServerSocket。

客户端:通过查阅Socket对象,发现在该对象建立时,就可以去连接指定主机。因为TCP是面向连接的,所以在建立Socket服务时,就要有服务端存在,并连接成功,形成通路后,在该通道进行数据的传输。

需求:建立一个文本转换服务器。客户端给服务端发送文本,服务单会将文本转成大写在返回给客户端。而且客户端可以不断的进行文本转换。当客户端输入over时,转换结束。

分析:既然是操作设备上的数据,那么就可以使用io技术,并按照io的操作规律来思考。

源:键盘录入。

目的:网络设备,网络输出流。

而且操作的是文本数据。可以选择字符流。

步骤:

1,建立服务。

2,获取键盘录入。

3,将数据发给服务端。

4,获取服务端返回的大写数据。

5,结束,关资源。

都是文本数据,可以使用字符流进行操作,同时提高效率,加入缓冲。

import java.io.*;
import java.net.*;

class  TransClient
{
public static void main(String[] args) throws Exception
{
//创建客户端的Socket服务,并指定目的的主机和端口
Socket s = new Socket("psy",10002);
//定义读取键盘数据的流对象。
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
//为了发送数据,应该获取Socket流中的输出流。
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
//定义一个Socket读取流,读取服务端返回的信息
BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));

String line = null;
while ((line=bufr.readLine())!=null)
{
if ("over".equals(line))
break;
//发送数据。
out.println(line);

String str = bufIn.readLine();
System.out.println("server: "+str);
}
bufr.close();	//关闭键盘录入流
s.close();		//关闭客户端
}
}

服务端:服务端通过ServerSocket来指定,并监听客户端需要访问的一个端口,达到建立服务端的目的,实现与客户端数据通信的基础。

需求:建立一个文本转换服务器。客户端给服务端发送文本,服务单会将文本转成大写在返回给客户端。而且客户端可以不断的进行文本转换。当客户端输入over时,转换结束。

分析:

源:Socket读取流

目的:Socket输出流

都是文本,装饰一下。

步骤:

1,建立服务端的Socket服务。ServerSocket();并监听一个端口。

2,获取连接过来的客户端对象。通过ServerSokcet的 accept方法。没有连接就会等,所以这个方法是阻塞式的。

3,客户端如果发过来数据,那么服务端要使用对应的客户端对象,并获取到该客户端对象的读取流来读取发过来的数据,并把大写数据返回给客户端。

4,关闭服务端。(可选,一般不会关闭服务器)

import java.io.*;
import java.net.*;

class  TransServer{
public static void main(String[] args) throws Exception{
//1、建立服务端Socket服务,并监听一个端口。
ServerSocket ss = new ServerSocket(10002);
//2、通过accept方法获取连接过来的客户端对象。
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+"...is connected!");
//3、读取Socket读取流中的数据
BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
//目的:socket输出流。将大写数据写入到Socket输出流中,并放松给客户端。
PrintWriter out = new PrintWriter(s.getOutputStream(),true);

String line = null;
while ((line=bufIn.readLine())!=null)
{
out.println(line.toUpperCase());
}
s.close();		//关闭客户端
ss.close();		//关闭服务端
}
}

3、演示客户端和服务端。

3.1、客户端:浏览器 (telnet)

服务端:自定义。

自定义服务端。

ServerSocket ss = new ServerSocket(10006);
Socket s = ss.accept();
System.out.println(s.getInetAddress().getHostAddress());
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
out.println("<font color='red' size='8'>客户端你好</font>");//返回给telnet的信息
s.close();
ss.close();

3.2、客户端:自定义。

服务端:Tomcat服务器。

自定义IE浏览器(客户端)。

Socket s = new Socket("127.0.0.1",8080);
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
out.println("GET /myweb/demo.html HTTP/1.1");
out.println("Accept: */*");
out.println("Accept-Language: zh-ch");
out.println("Host:psy:100"); //一定要有主机信息
out.println("Connection: closed");
//一定要有空行,保证请求消息头和请求数据体之间有一行空行
out.println();
BufferedReader bufr = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = null;
while ((line=bufr.readLine())!=null){
System.out.println(line);
}
s.close();

3.3、HTTP的请求消息头:浏览器在访问服务器时,向服务器发送的是HTTP请求,该请求信息中包含用户当前要访问服务端的信息内容,当服务端收到该HTTP请求时,会按照服务端浏览器支持的解压格式,将网页数据打包,再发送给服务端浏览器,这样做的目的是节省流量开支。

HTTP的请求消息头:

GET /myweb/demo.html HTTP/1.1 //GET请求:客户端要访问/myweb/demo.html,通过HTTP协议1.1版本
Accept: image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave
-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msw
ord, application/xaml+xml, application/x-ms-xbap, application/x-ms-application,
application/vnd.ms-xpsdocument  //可接收的文件类型(数据)
Accept-Language: zh-cn    //支持语言
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; QQDo
wnload 718; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET
CLR 3.5.30729)      //用户信息
Accept-Encoding: gzip, deflate  //支持压缩格式(重要:提高效率)
Host: 127.0.0.1:10006    //访问主机的10006端口(一台服务器可能有多个主机)
Connection: Keep-Alive    //保持连接

//请求消息头后是请求数据体,用一个空行隔开

应带消息头(从服务器发出)

HTTP/1.1 200 OK      //200:相应状态码(代表成功:OK)
Server: Apache-Coyote/1.1   //服务器
Accept-Ranges: bytes
ETag: W/"269-1364666220000"   //一个标记
Last-Modified: Sat, 30 Mar 2013 17:57:00 GMT //被修改时间
Content-Type: text/html    //文本类型
Content-Length: 269     //大小269字节
Date: Sat, 30 Mar 2013 18:38:45 GMT
Connection: close

五、统一资源定位符 URL Uniform Resource Locater

1、URL对象常用方法

构造方法:URL(String protocol, String host, int port, String file)

方法:String getFile() :获取此 URL 的文件名。

String getHost() :获取此 URL 的主机名(如果适用)。

String getPath() :获取此 URL 的路径部分。

int getPort() :获取此 URL 的端口号。

String getProtocol() :获取此 URL 的协议名称。

String getQuery() :获取此 URL 的查询部,即文件名后的附加参数信息

URLConnection openConnection():连接到指定的主机。

URLConnection 抽象类将Socket对象封装且封有内置协议,所以简化了Socket方法,一句话搞定网络连接,同时该抽象类应用于应用层,会通过HTTP协议将HTTP消息头解析,所以将不会再显示消息头。

InputStream openStream():开流,即线连接再获取流,封装了openConnection().getInputStream()。但是一般情况我们还是会分步来做,因为openConnection()可获取连接对象,有更多的操作可以实现。

2、域名解析

(1)http:\\127.0.0.1:8080/myweb/index.html

(2)http:\\www.sina.com.cn

问:两个URL在访问主机时做了什么事情?

答:(1)127.0.0.1:8080因为是IP+端口,所以直接访问指定主机,没做其它事情。

(2)www.sina.com.cn是一个主机名,那么它会先在本地C:\WINDOWS\system32\drivers\etc\hosts文件中找其与该主机名映射的IP地址,若找到则使用该IP;否则才会去公网上找一台域名解析服务器(该服务中有很多主机名和IP地址的映射关系)来解析该主机名对应的IP地址。

六、示例

需求:客户端通过键盘录入用户名。

服务端对这个用户名进行校验。

如果该用户存在,在服务端显示xxx,已登陆。并在客户端显示 xxx,欢迎光临。

如果该用户不存在,在服务端显示xxx,尝试登陆。并在客户端显示 xxx,该用户不存在。

最多就登录三次。

客户端实现方式

import java.io.*;
import java.net.*;
//客户端
class LoginClient
{
public static void main(String[] args) throws Exception
{
//创建客户端的socket服务。指定目的主机和端口
Socket s = new Socket("psy",10004);
//创建输入流
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
//获取客户端输出流
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
//获取客户端输入流
BufferedReader bufIn =
new BufferedReader(new InputStreamReader(s.getInputStream()));

for (int i=0; i<3; i++)
{
String line = bufr.readLine();
if (line==null)
break;	//跳出当前循环,执行循环体以后的内容
if (line.equals(""))
{
System.out.println("用户名不能为空!");
continue;//跳出当前循环,继续执行下一次循环
}
out.println(line);

String info = bufIn.readLine();
System.out.println("客户端:"+info);
if (info.contains("欢迎"))
break;
}

bufr.close();
s.close();
}
}

服务端实现方式

class LoginServer
{
public static void main(String[] args) throws Exception
{
//创建服务端,监听端口10004
ServerSocket ss = new ServerSocket(10004);
while (true)
{
//获取客户端(可获取多个不同客户端发来的数据)
Socket s = ss.accept();
//创建线程,将客户端引用作为值传递给线程构造函数。
//目的是保证不同的客户端,在服务端都享有一个独立的线程,互不影响,并发执行。
new Thread(new UserThread(s)).start();
}
}
}

将服务端处理客户端数据的过程封装进线程

class UserThread implements Runnable{
private Socket s;
UserThread(Socket s){
this.s = s;
}
public void run(){
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+"...connected");
try{
for (int x=0; x<3; x++){
//获取服务端输入流
BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
String name = bufIn.readLine();
if(name==null)
break;
//创建输入流关联用户文件
BufferedReader bufr = new BufferedReader(new FileReader("user.txt"));
//获取服务端输出流
PrintWriter out = new PrintWriter(s.getOutputStream(),true);

boolean flag = false;
String line = null;
while ((line=bufr.readLine())!=null){
if (line.equals(name)){
flag = true;
break;
}
}
if (flag){
System.out.println("服务端:"+name+",已登录");
out.println(name+",欢迎光临!");
break;  //或者用s.close()代替break。
}
else{
System.out.println("服务端:"+name+",尝试登录");
out.println("用户名"+name+"不存在!");
}
}
System.out.println("关闭客户端链接");
s.close();
}
catch (Exception e){
throw new RuntimeException(ip+",校验失败");
}
}
}

问:第15行和第32行的break都可达到一个目的跳出for循环执行关闭客户端语句,而且他们之中只要满足第一个break,就能跳出循环执行s.close() ,为什么还得在后面实现break?

答:目的是节约资源,提高服务端性能。

若只有第15行的break 语句,也能达到关闭客户端的目的。但是会导致即使满足了if (flag) 判断条件,还得要多执行一段时间,直到下一次读到第15行的break才跳出执行s.close() 。若用户太多的情况下,不利于服务端优化,高效释放资源。break存在的必要:为了跳出当前循环,以便关闭客户端,释放资源。

总结:网络编程同IO流一样的,都是重中之重,重点掌握实现他们的步骤流程,建立在这个基础上再来处理具体的逻辑问题,就会简单很多,就好比刚建的新房,其他就只需做的好内部装饰就OK了,这就是我们现在处理问题的思路,步步深入。网络编程一般都会和IO流相搭配来完成对网络数据的操作,因为很多时候都要实现服务端与客户端的动态交互。

该章重点掌握,特别是UDP和TCP传输协议的区别和应用,URL中的openConnection()方法的使用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: