网络编程


网络模型:

OSI(Open System Interconnection开放系统互连)参考模型

    OSI模型把网络通信的工作分为7层。1至4层被认为是低层,这些层与数据移动密切相关。5至7层是高层,包含应用程序级的数据。

    第1层物理层:原始比特流的传输。集线器、网线

    第2层数据链路层:在此层将数据分帧,并处理流控制。本层指定拓扑结构并提供硬件寻址。网卡,网桥,交换机

    第3层网络层:本层通过寻址来建立两个节点之间的连接,它包括通过互连网络来路由和中继数据。路由器,防火墙

    第4层传输层:常规数据递送-面向连接或无连接。包括全双工或半双工、流控制和错误恢复服务。计算机的进程和端口

    第5层会话层:在两个节点之间建立端连接。此服务包括建立连接是以全双工还是以半双工的方式进行设置,尽管可以在层4中处理双工方式。建立会话,SESSION认证、断点续传

    第6层表示层:格式化数据,以便为应用程序提供通用接口。这可以包括加密服务。编码方式,图像编解码、URL字段传输编码

    第7层应用层:直接对应用程序提供服务,应用程序可以变化,但要包括电子消息传输。应用程序,如FTP,SMTP,HTTP

 

TCP/IP参考模型

    这个模型只有四层。主机至网络层,网际层,传输层,应用层。


网络通讯要素:IP地址  端口号  传输协议

IP地址:InetAddress

网络中设备的标识,长度为4个字节,也就是32个0和1的数字编码,为了方便使用,写成十进制。比如192.168.1.100,没个数不超过255,因为一个字节,8个0和1的数最多表示255。

本地回环地址:127.0.0.1   它代表设备的本地虚拟接口,只用来访问本机,不能访问其他机器。一般都会用来检查本地网络协议、基本数据接口等是否正常的。

IP地址不易记忆,就有了主机名:localhost,就是计算机的名字,这个名字可以随时更改。

端口号:用于标识进程的逻辑地址,不同进程的标识。

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

传输协议:通讯的规则

 常见协议:  

   TCP   传输控制协议

 建立连接,形成数据传输通道

 在连接中进行大量数据传输

         通过三次握手完成链接,是可靠协议

 必须建立连接,效率稍低

   UDP  数据报文协议

 将数据及源和目的封装成数据包中,不需要建立连接

 每个数据报的大小限制在64K内

         因无连接,是不可靠协议

 不需要建立连接,速度快 


package day25;

import java.net.InetAddress;

import java.net.UnknownHostException;


public class IPDemo {


public static void main(String[] args) throws UnknownHostException {

//获取本地主机IP对象

InetAddress ip=InetAddress.getLocalHost();

System.out.println(ip.getHostAddress());

System.out.println(ip.getHostName());

//获取其他主机的IP地址对象

ip=InetAddress.getByName("www.baidu.com");

System.out.println(ip.getHostName());

System.out.println(ip.getHostAddress());

}


}   



解析地址的过程:

平常上网IP地址不便于记忆,比如新浪的IP是10.0.0.1    上新浪网总不能输入这个吧,太难记了。

我们输的是www.sina.com 。 那网址就要和IP对应啊,所以输了这个不是直接就去新浪了,先找DNS(域名解析器),它专门记录域名和IP的对应关系。它会返回给你一个IP,然后你就拿着这个IP才去了新浪。

域名解析时先走本机的hosts域名解析文件,解析失败后才走DNS。

所以平常有广告网页时,把他的域名复制,写到hosts文件里,对应的IP写成本机127.0.0.1 ,域名解析先走本地文件,这样就可以屏蔽广告了。


 

Socket(可以理解为插头,港口)

Socket就是为网络服务提供的一种机制。通信的两端都有Socket。

网络通信就是Socket间的通信,数据在两个Socket间通过IO传输。





UDP传输

DatagramSocket   用来发送和接收数据包的套接字。

DatagramPacket   此类表示数据报包。数据报包用来实现无连接包投递服务。


UDP传输的发送端:

package day25;

import java.io.IOException;

import java.net.DatagramPacket;

import java.net.DatagramSocket;

import java.net.InetAddress;


public class UDPSendDemo {


/**

* UDP传输的发送端、

* 1.建立UDP的socket服务。

* 2.将要发送的数据封装到数据包中

* 3.通过UDP的socket服务将数据包发送出去

* 4.关闭socket服务

* 注意:在我们写的这个例子中得先打开接收端,而不是发生端。

*/

public static void main(String[] args) throws IOException {

System.out.println("发送端启动。。。");

//1.建立UDP的socket服务。使用DatagramSocket对象

DatagramSocket ds=new DatagramSocket();

//2.将要发送的数据封装到数据包中。使用DatagramPacket将数据封装到该对象包中。

String str="UDP传输演示:哥们来了!";

byte[] buf=str.getBytes();

DatagramPacket dp=new DatagramPacket(buf, buf.length, InetAddress.getByName("192.168.31.74"), 10000);

//3.通过UDP的socket服务将数据包发送出去。使用send方法

ds.send(dp);

//4.关闭socket服务

ds.close();

}

}




UDP传输的接收端:

package day25;

import java.io.IOException;

import java.net.DatagramPacket;

import java.net.DatagramSocket;


public class UDPReceiveDemo {


/**

* UDP传输的接收端

* 1.建立UDP的socket服务。  因为是要接收数据,所以必须明确端口号,和发送端口号一致才行。不指定端口号就是随机的,就收不到。

* 2.创建数据包,用于存储接收到的数据,方便用数据包对象的方法解析数据。

* 3.使用socket服务的receive方法将接受的数据存储到数据包中。

* 4.通过数据包对象的方法解析数据包中的数据。

* 5.关闭资源。

* 注意:在我们写的这个例子中得先打开接收端,而不是发生端。

*/

public static void main(String[] args) throws IOException {

System.out.println("接收端启动。。。");

//1.建立UDP的socket服务。使用DatagramSocket对象

DatagramSocket ds=new DatagramSocket(10000);   //端口号必须和发送的端口号一致才行。

//2.创建数据包,用于存储接收到的数据,方便用数据包对象的方法解析数据。

byte[] buf=new byte[1024];

DatagramPacket dp=new DatagramPacket(buf,buf.length);

//3.使用socket服务的receive方法将接受的数据存储到数据包中。

ds.receive(dp);       //阻塞式的方法。

//4.通过数据包对象的方法解析数据包中的数据。比如IP地址,端口,数据内容等

String ip=dp.getAddress().getHostAddress();   

int port=dp.getPort();

String text=new String(dp.getData(),0,dp.getLength());

System.out.println(ip+":"+port+":"+text);

//5.关闭资源

ds.close();

}


}







练习:实现一个简易的聊天软件

package day25;

import java.net.DatagramSocket;

import java.net.SocketException;


public class ChatDemo {


/**

* 因为有可能发送的同时要接受,所以要用到多线程

* 要实现群聊功能,只需要把chatsend中发送的IP地址最后一个字节改为255即可。

* 改成255就会发送给该网段的所有人。

* @param args

* @throws SocketException 

*/

public static void main(String[] args) throws SocketException {

DatagramSocket send=new DatagramSocket();

DatagramSocket receive=new DatagramSocket(8000);

ChatSend s=new ChatSend(send);

ChatReceive r=new ChatReceive(receive);

new Thread(s).start();

new Thread(r).start();

}


}

 

发送模块

package day25;

import java.io.BufferedReader;

import java.io.InputStreamReader;

import java.net.DatagramPacket;

import java.net.DatagramSocket;

import java.net.InetAddress;


public class ChatSend implements Runnable {


private DatagramSocket ds;

public ChatSend(DatagramSocket ds){

this.ds=ds;

}

@Override

public void run() {

try {

BufferedReader bufr=new BufferedReader(new InputStreamReader(System.in));

String line=null;

while((line=bufr.readLine())!=null){

byte[] buf=line.getBytes();

DatagramPacket dp=new DatagramPacket(buf, buf.length, InetAddress.getByName("192.168.31.74"), 10000);

ds.send(dp);

if("over".equals(line)){

break;

}

ds.close();

}

} catch (Exception e) {

}

}


}

接收模块

package day25;

import java.net.DatagramPacket;

import java.net.DatagramSocket;


public class ChatReceive implements Runnable {

private DatagramSocket ds;

public ChatReceive(DatagramSocket ds){

this.ds=ds;

}

@Override

public void run() {

try{

while(true){

        byte[] buf=new byte[1024];

DatagramPacket dp=new DatagramPacket(buf,buf.length);

ds.receive(dp);    

        String text=new String(dp.getData(),0,dp.getLength());

        if(text.equals("over")){

        System.out.println("对方已退出");

        }

        System.out.println(text);

}

}catch(Exception e){}

}

}



TCP传输


面向连接,要经过3次握手连接才能形成数据通道。

客户端:

package day25;

import java.io.IOException;

import java.io.OutputStream;

import java.net.Socket;

import java.net.UnknownHostException;


public class TCPClientDemo {


/**

* 需求:客户端发送数据到服务端。

* tcp要经过3次握手连接,才能形成数据传输通道。

* 注意:tcp是面向连接的,运行时必须先开服务端。

* @throws IOException 

* @throws UnknownHostException 

*/

public static void main(String[] args) throws UnknownHostException, IOException {

/*

* Tcp传输客户端建立的过程:

* 1.创建tcp客户端socket服务,使用的是cosket对象。建议该对象一创建就明确目的地,即要连接的主机。

* 2.如果建立连接成功,就会形成数据传输通道Socket流(网络IO流)。

*   Socket流是底层建立好的,既能输入又能输出。想要输出或者输入对象,可以找socket获取。

*   可以通过getInputStream()和getOutputStream来获取两个字节流。

* 3.使用输出流,将数据写出。

* 4.关闭资源。

*/

//创建客户端socket服务

Socket socket=new Socket("192.168.37.74",6666);  //创建对象时最好就明确发送地址的IP和端口

//获取socket流中的输出流

OutputStream out=socket.getOutputStream();

//使用输出流将指定的数据写出去

out.write("tcp演示:哥们来了!".getBytes());

//关闭资源

socket.close();

}

}



服务端:

package day25;

import java.io.IOException;

import java.io.InputStream;

import java.net.ServerSocket;

import java.net.Socket;


public class TCPServerDemo {


/**

* 需求:服务端接收客户端发送过来的数据,并打印在控制台上。

* 注意:tcp是面向连接的,运行时必须先开服务端。

* @param args

* @throws IOException 

*/

public static void main(String[] args) throws IOException {

/*

* Tcp传输服务端建立的过程:

* 1.创建服务端socket服务。通过ServerSocket对象。

* 2.服务端必须对外提供一个端口,否则客户端无法连接。

* 3.获取连接过来的客户端对象。

* 4.通过客户端对象获取socket流读取客户端发来的数据并打印。

* 5.关闭资源。这个不仅要关服务端,还得关客户端。

*/

//创建服务端对象

ServerSocket ss=new ServerSocket(95232);

//获取连接过来的客户端对象

Socket s=ss.accept();    //阻塞式方法

String ip=s.getInetAddress().getHostAddress();

//通过socket对象获取输入流,读取客户端发来的数据

InputStream in=s.getInputStream();

byte[] buf=new byte[1024];

int len=in.read(buf);

String text=new String(buf,0,len);

System.out.println("Server"+ip+"..."+text);

//关闭资源

s.close();

ss.close();

}


}





有时候会出现服务端和客户端都在等的情况,原因是用了阻塞式方法,具体可能有以下几个问题:

1.客户端根本没写进去。只写到了socket输出流里,没写到socket.getOutputStream()里面。给socket流加个刷新就行。

2.缺少结束标记。服务端读到换行才会结束读取,返回数据,所以加上"\r\n"。注意服务端和客户端都要加,因为两边都要读取数据。


解决上面两个问题的简便方法就是socket输出流直接用PrintWriter。

创建PrintWriter对象时直接加上true就能自动刷新了。

输出时,直接用println方法,直接就能换行。





练习:


需求:客户端输入字母数据,发送给服务端,服务端收到后显示到控制台。

      并将该数据转成大写返回给客户端,直到客户端输入over,转换结束。

      说白了,就是创建一个英文大写转换服务器。

客户端:

package day25;

import java.io.BufferedReader;

import java.io.IOException;

import java.io.InputStreamReader;

import java.io.PrintWriter;

import java.net.Socket;

import java.net.UnknownHostException;


public class TransClient {


public static void main(String[] args) throws UnknownHostException, IOException{

/*

* 思路:

* 1.客户端要先有socket断点

* 2.客户端的数据源:键盘

* 3.客户端的目的:socket

* 4.接收服务端的数据,  源:socket

* 5.将数据打印出来         目的:控制台

* 6.在这些流中操作的数据都是文本数据。可以用字符流操作,方便。

* 步骤:

* 1.创建socket客户端对象。

* 2.获取键盘录入

* 3.将录入的信息发送给socket输出流

*/

//创建socket客户端对象

Socket s=new Socket("192.168.1.113",10005);

//获取键盘录入

BufferedReader bufr=new BufferedReader(new InputStreamReader(System.in));

//socket输出流

PrintWriter out=new PrintWriter(s.getOutputStream(),true);  //true自动刷新

//也可以这样写 BufferedWriter bufw=new BufferedWriter(new OutputStreamWriter(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 upperStr=bufIn.readLine();

System.out.println(upperStr);

}

s.close();

}

}

服务端:

package day25;

import java.io.BufferedReader;

import java.io.IOException;

import java.io.InputStreamReader;

import java.io.PrintWriter;

import java.net.ServerSocket;

import java.net.Socket;


public class TransServer {

public static void main(String[] args) throws IOException{

/*

* 思路:

* 1.serversocket服务。

* 2.获取socket对象。

* 3.读取客户端发过来的数据     源:socket 

* 4.目的:显示在控制台上

* 5.将数据转成大写发回给客户端。

*/

ServerSocket ss=new ServerSocket(10005);

//获取socket对象。

Socket s=ss.accept();

//获取连接进来计算机的IP

String ip=s.getInetAddress().getHostAddress();

System.out.println(ip+"...connected");

//获取socket读取流,并装饰

BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream()));

//获取socket输出流,并装饰

PrintWriter out=new PrintWriter(s.getOutputStream(),true);     //true是自动刷新的

String line=null;

while((line=bufIn.readLine())!=null){

System.out.println(line);

out.println(line.toUpperCase());

}

s.close();

ss.close();

}

}





需求:做一个上传文件的程序。

客户端:

package day25;

import java.io.BufferedReader;

import java.io.FileReader;

import java.io.IOException;

import java.io.InputStreamReader;

import java.io.PrintWriter;

import java.net.Socket;

import java.net.UnknownHostException;


public class UploadCilent {

 /**

  * 注意问题:1.默认缓冲区大小是8kb,装满后悔自动刷新一次,

  *     所以如果文件超过8kb而且bufw不进行刷新操作,就会发现上传的数据不完整。

  *    2.服务端的while循环没有结束标记。所以运行时会发现服务端和客户端都在等的问题。

  * 只要在客户端发完数据后再发一个标记,服务端读到就可以结束了。

  * 那样还是麻烦。java提供了一个更简单方便的结束方法,socket流的shutdown方法。

  * @param args

  * @throws UnknownHostException

  * @throws IOException

  */

public static void main(String[] args) throws UnknownHostException, IOException {

Socket s=new Socket("192.168.1.113",10007);

BufferedReader bufr=new BufferedReader(new FileReader("client.txt"));

PrintWriter out=new PrintWriter(s.getOutputStream(),true);

String line=null;

while((line=bufr.readLine())!=null){

out.println(line);

}

s.shutdownOutput();   //java提供的更为简单方便的结束标记方法。

//out.print("over");  //给服务端一个结束的标记,要不结束不了。别用over,万一文本里也有over就完了。一般都用时间的毫秒值。

BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream()));

String str=bufIn.readLine();

System.out.println(str);

bufr.close();

s.close();

}

}



服务端:

package day25;

import java.io.BufferedReader;

import java.io.BufferedWriter;

import java.io.FileWriter;

import java.io.IOException;

import java.io.InputStreamReader;

import java.io.PrintWriter;

import java.net.ServerSocket;

import java.net.Socket;


public class UploadServer {


/**

* 注意问题:1.默认缓冲区大小是8kb,装满后悔自动刷新一次,

*          所以如果文件超过8kb而且bufw不进行刷新操作,就会发现上传的数据不完整。

*   2.服务端的while循环没有结束标记。所以运行时会发现服务端和客户端都在等的问题。

* 只要在客户端发完数据后再发一个标记,服务端读到标记就可以结束了。

* 那样还是麻烦。java提供了一个更简单方便的结束方法,socket流的shutdown方法。

* @param args

* @throws IOException 

*/

public static void main(String[] args) throws IOException {


ServerSocket ss=new ServerSocket(10007);

Socket s=ss.accept();

System.out.println(s.getInetAddress().getHostAddress()+".....connected");

BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream()));

BufferedWriter bufw=new BufferedWriter(new FileWriter("server.txt"));

String line=null;

while((line=bufIn.readLine())!=null){

bufw.write(line);

bufw.newLine();  //别忘了换行,要不写的数据贼乱。

bufw.flush();    //别忘了刷新,要不出问题,具体看上面的文档注释。

}

PrintWriter out=new PrintWriter(s.getOutputStream(),true);

out.println("上传成功!");

bufw.close();

s.close();

ss.close();

}

}




网络编程:

最常见的客户端:  浏览器  IE

最常见的服务端:  服务器  Tomcat


为了了解其原理:

1.自定义服务端使用已有的客户端IE,了解一下客户端给服务端发了什么请求?


自己写一个服务端,里面把客户端发过来的东西全打印出来,然后浏览器地址栏输入http://ip:服务器的端口/。这样就可以知道客户端给服务端到底发了什么请求。

发送的请求是:Get/HTTP/1.1       //第一行是请求行:请求方式(get和post)/请求的资源路径   HTTP协议版本

     //下面这些都是请求消息头。 属性名:属性值

     Accept:*/*         //支持接收的文件格式,一般都很长,这里就没写

     Accept-Language:zh-cn,zu-;q=0.5      //支持的语言

     Accept-Encoding:qzip,deflate      //支持的压缩方式

     User-Agent:Mozilla/4.0(compatible;MSIE 6.0;Windows NT 5.1;SV1;InfoPath.2)      //用户的一些信息

     Host:192.168.1.100:9090       //要访问的主机

     Connection:Keep-Alive

     //空行

     //下面有需要的话还有请求体。请求头和请求体中间有空行,方便服务器解析。


2.了解一下服务端给客户端发了什么?

和上面一个道理。自己写个客户端,把服务端发回来的信息全部打出来就行。

发回的信息是:HTTP/1.1 200 OK        //应答行,HTTP协议版本  应答状态码(200表示成功,404表示服务端没有这个信息)  应答状态描述信息

     //下面都是应答信息头。  属性名:属性值 

     Server:Apache-Coyote/1.1     

     ETag:W/"199-1323480176984"

     Last-Modified:Sat,10 Dec 2011 01:22:56 GMT  //上次访问缓存的时间,如果和服务器端一致,服务器就不返回信息,直接访问缓存好的,更快

     Content-Type:text/html       //文本类型

     Content-length:199           //文本长度

     Date:Fri,11 May 2012 07:51:39 GMT

     Connection:close

     //空行

     //下面就是应答消息体。解析后就是网页显示的内容。



URL是统一资源定位符,也就是我们通常说的网址。

URI是同一资源标识符。URL是URI的子类。所以URL都是URI。

package day25;

import java.io.IOException;

import java.io.InputStream;

import java.net.URL;


public class URLDemo {


/**

* @param args

* @throws IOException 

*/

public static void main(String[] args) throws IOException {

String str_url="http://192.168.1.113:8080/myweb/1.html?name=lisi";

//问号后面的是参数信息,原来是欢迎光临,加上这个就可以变成欢迎lisi光临

URL url=new URL(str_url);

System.out.println("getProtocol:"+url.getProtocol());

System.out.println("getHost:"+url.getHost());

System.out.println("getPort:"+url.getPort());

System.out.println("getFile:"+url.getFile());

System.out.println("getPath:"+url.getPath());

System.out.println("getQuery:"+url.getQuery()); 

//本来自己写的客户端不能解析,输出总是把应答信息头也输出了,这是没有必要的

//而url的openStream()就可以解决,输出时就不会输出应答信息头。

//这个例子中我写的url的IP以及文件是视频中老师的,我没装tomcat,所以才显示不出来。

InputStream in=url.openStream();

/*

*       url.openStream()内部实现的原理说白了就是openConnection()+getInputStream();

* URLConnection con=url.connection();

*       上面这句就是获取url连接器对象并封装成对象。java中内置的可以解析具体协议的对象+socket。

* InputStream in=con.getInputStream();

*/

byte[] buf=new byte[1024];

int len=in.read(buf);

String text=new String(buf,0,len);

System.out.println(text);

//url不用close

in.close();

}


}




网络结构:

1.C/S    client/server

  优点:客户端在本地可以分担一部分运算。

  缺点:该结构的软件,客户端和服务端都需要编写。

开发成本较高,维护较为麻烦。


2.B/S    browser/server

  优点:该结构的软件,只开发服务器端,不开发客户端,因为客户端直接由浏览器取代。

开发成本相对低,维护更为简单。

  缺点:所有运算都要在服务端完成。