一、概述
1、网络编程的核心是IP、端口(表示应用程序)、协议三大元素
2、网络编程的本质是进程间通信
3、网络编程的2个主要问题:1是定位主机,2是数据传输
二、网络通信的概念
1、网络通信协议
计算机网络中实现通信必须有一些约定即通信协议,对速率、 传输代码、代码结构、传输控制步骤、出错控制等制定标志。
2、网络通信接口
为了使两个结点之间能进行对话,必须在它们之间建立通信工具(即接口),使彼此之间能进行信息交换,接口包含两部分:
硬件部分:实现结点之间的信息传送
软件部分:规定双方使用哪种通信协议
3、通信协议的分层思想
由于结点之间联系很复杂,在制定协议时,把复杂的成份分解为一些简单的成份,再将它们复合起来,即同层间可以互相通信、上一层可以调用下一层,而与再下一层不发现关系,各层互不影响,利于系统的开发和扩展。TCP/IP的四层参考模型包括:应用层、 传输层、网络层、物理+数据链路层。
4、数据的封装和拆分
数据由上层网下层传输时,会再原数据上加上每一层的控制信息,最后转变为二进制代码通过物理层传输,然后挨个拆分控制信息把数据传输到指定的应用程序。也称为数据的封包和拆包。
三、IP协议
IP和端口能唯一定位到需要通信的进程。这里的IP表示地址,区别于IP协议。在OSI体系还是TCP/IP体系中,IP协议位于网际层,来封装IP地址到报文中。
四、TCP和UDP协议
TCP是Tranfer Control Protocol的简称,是一种面向连接的保证可靠传输的协议。通过TCP协议传输,得到的是一个顺序的无差错的数据流。发送方和接收方的成对的两个socket之间必须建立连接,以便在TCP协议的基础上进行通信,当一个socket(通常都是server socket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket连接起来,它们就可以进行双向数据传输,双方都可以进行发送或接收操作。
UDP是User Datagram Protocol的简称,是一种无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。
比较:
UDP:
- 每个数据报中都给出了完整的地址信息,因此无需要建立发送方和接收方的连接。
- UDP传输数据时是有大小限制的,每个被传输的数据报必须限定在64KB之内。
- UDP是一个不可靠的协议,发送方所发送的数据报并不一定以相同的次序到达接收方
- 速度快
TCP:
- 面向连接的协议,在socket之间进行数据传输之前必然要建立连接,所以在TCP中需要连接时间。
- TCP传输数据大小限制,一旦连接建立起来,双方的socket就可以按统一的格式传输大的数据。
- TCP是一个可靠的协议,它确保接收方完全正确地获取发送方所发送的全部数据。
- 速度慢
五、Socket
Socket是网络驱动层提供给应用程序编程接口和一种机制。我们可以把 Socket 比喻成是一个港口码头。应用程序只要把货物放到港口码头上,就算完成了货物的运送。对于接收方应用程序也要创建一个港口码头,只需要等待货物到达码头后将货物取走。
Socket 是在应用程序中创建的,它是通过一种绑定机制与驱动程序建立关系,告诉自己所对应的 IP 和 Port。在网络上传输的每一个数据帧,必须包含发送者的 IP 地址和端口号。创建完 Socket 以后,应用程序写入到 Socket 的数据,由 Socket 交给驱动程序向网络上发送数据,计算机从网络上收到与某个 Socket 绑定的 IP 和 Port 相关的数据后,由驱动程序再交给 Socket ,应用程序就可以从这个 Socket 中读取接收到的数据。网络应用程序就是这样通过 Socket 发送和接收的。
六、UDP用法
例1:需求:通过udp传输方式,将一段文字数据发送出去。
/* 定义发送端 1,建立updsocket服务。 2,提供数据,并将数据封装到数据包中。 3,通过socket服务的发送功能,将数据包发出去。 4,关闭资源。 */ class UdpSend{ public static void main(String[] args) throws Exception{ //1,创建udp服务,通过DatagramSocket对象. DatagramSocket ds = new DatagramSocket(8888); //2,确定数据,并封装成数据包.DatagramPacket(byte[] buf,int length,InetAddress address,int port) byte[] buf = "udp ge men lai le ".getBytes(); DatagramPacket dp = new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.1.254"),10000); //3,通过socket服务,将已有的数据包发送出去。通过send方法。 ds.send(dp); //4,关闭资源。 ds.close(); } } /* 定义udp的接收端。 1,定义udpsocket服务。通常会监听一个端口。用于明确哪些数据过来该应用程序可以处理。 2,定义一个数据包,因为要存储接收到的字节数据。因为数据包对象中有更多功能可以提取字节数据中的不同数据信息。 3,通过socket服务的receive方法将收到的数据存入已定义好的数据包中。 4,通过数据包对象的特有功能。将这些不同的数据取出。打印在控制台上。 5,关闭资源。 */ class UdpRece{ public static void main(String[] args) throws Exception{ //1,创建udp socket,建立端点。 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()); int port = dp.getPort(); System.out.println(ip+"::"+data+"::"+port); } //5,关闭资源 //ds.close(); } }
例2:编写一个聊天程序
/*有收数据的部分,和发数据的部分,这两部分需要同时执行,那就需要用到多线程技术,一个线程控制收,一个线程控制发。*/ import java.io.*; import java.net.*; class Send implements Runnable{ private DatagramSocket ds; public Send(DatagramSocket ds){ this.ds = ds; } public void run(){ try{ BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); String line = null; byte[] buf=null; while((line=bufr.readLine())!=null){ buf = line.getBytes(); DatagramPacket dp = new DatagramPacket( buf,buf.length,InetAddress.getByName("192.168.1.255"),10002);//255广播地址 ds.send(dp); if("886".equals(line)) break; } } catch (Exception e){ throw new RuntimeException("发送端失败"); } } } class Rece implements Runnable{ private DatagramSocket ds; public Rece(DatagramSocket ds){ this.ds = ds; } public void run(){ try{ byte[] buf=null; DatagramPacket dp=null; while(true){ buf = new byte[1024]; dp = new DatagramPacket(buf,buf.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){ throw new RuntimeException("接收端失败"); } } }
七、TCP用法
例1:需求:给服务端发送给一个文本数据。
/* TCP分客户端和服务端,客户端对应的对象是Socket,服务端对应的对象是ServerSocket。 客户端 该对象建立时,就可以去连接指定主机。 因为tcp是面向连接的。所以在建立socket服务时, 就要有服务端存在,并连接成功。形成通路后,在该通道进行数据的传输。 */ import java.io.*; import java.net.*; class TcpClient{ public static void main(String[] args) throws Exception { //创建客户端的socket服务。指定目的主机和端口 Socket s = new Socket("192.168.1.254",10003); //为了发送数据,应该获取socket流中的输出流。 OutputStream out = s.getOutputStream(); out.write("tcp ge men lai le ".getBytes()); s.close(); } } class TcpServer{ public static void main(String[] args) throws Exception{ //建立服务端socket服务。并监听一个端口。 ServerSocket ss = new ServerSocket(10003); while(true){ //通过accept方法获取连接过来的客户端对象。 Socket s = ss.accept(); //阻塞式方法 String ip = s.getInetAddress().getHostAddress(); System.out.println(ip+".....connected"); //获取客户端发送过来的数据,那么要使用客户端对象的读取流来读取数据。 InputStream in = s.getInputStream(); byte[] buf = new byte[1024]; int len = in.read(buf); System.out.println(new String(buf,0,len)); s.close();//关闭客户端. } //ss.close(); 可选,服务器端一般不关闭 } }
例2:TCP传输客户端和服务端的互访,客户端给服务端发送数据,服务端收到后,给客户端反馈信息
class TcpClient2 { public static void main(String[] args)throws Exception { Socket s = new Socket("192.168.1.254",10004); OutputStream out = s.getOutputStream(); out.write("服务端,你好".getBytes()); InputStream in = s.getInputStream(); byte[] buf = new byte[1024]; int len = in.read(buf); System.out.println(new String(buf,0,len)); s.close(); } } class TcpServer2{ public static void main(String[] args) throws Exception{ ServerSocket ss = new ServerSocket(10004); Socket s = ss.accept(); String ip = s.getInetAddress().getHostAddress(); System.out.println(ip+"....connected"); InputStream in = s.getInputStream(); byte[] buf = new byte[1024]; int len = in.read(buf); System.out.println(new String(buf,0,len)); OutputStream out = s.getOutputStream(); Thread.sleep(10000); out.write("哥们收到,你也好".getBytes()); s.close(); ss.close(); } }
例3:建立一个文本转换服务器
/* 客户端给服务端发送文本,服务单会将文本转成大写在返回给客户端。 而且客户度可以不断的进行文本转换。当客户端输入over时,转换结束。 都是文本数据,可以使用字符流进行操作,同时提高效率,加入缓冲。*/ import java.io.*; import java.net.*; class TransClient{ public static void main(String[] args) throws Exception{ Socket s = new Socket("192.168.1.254",10005); //定义读取键盘数据的流对象。 BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); //定义目的,将数据写入到socket输出流。发给服务端。 //BufferedWriter bufOut = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); PrintWriter out = new PrintWriter(s.getOutputStream(),true);//带自动刷新,用来代替BufferedWriter //定义一个socket读取流,读取服务端返回的大写信息。 BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream())); String line = null; while((line=bufr.readLine())!=null){ //readLine读到回车符才会返回数据 if("over".equals(line)) break; out.println(line); //换行 // bufOut.write(line); // bufOut.newLine(); // bufOut.flush(); String str =bufIn.readLine(); System.out.println("server:"+str); } bufr.close(); s.close(); } } class TransServer{ public static void main(String[] args) throws Exception{ ServerSocket ss = new ServerSocket(10005); Socket s = ss.accept(); String ip = s.getInetAddress().getHostAddress(); System.out.println(ip+"....connected"); //读取socket读取流中的数据。 BufferedReader bufIn =new BufferedReader(new InputStreamReader(s.getInputStream())); //目的。socket输出流。将大写数据写入到socket输出流,并发送给客户端。 //BufferedWriter bufOut = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); PrintWriter out = new PrintWriter(s.getOutputStream(),true); String line = null; while((line=bufIn.readLine())!=null){ System.out.println(line); out.println(line.toUpperCase()); // bufOut.write(line.toUpperCase()); // bufOut.newLine(); // bufOut.flush(); } s.close(); ss.close(); } }
运行结果:
现象:客户端和服务端都在莫名的等待.因为客户端和服务端都有阻塞式readLine方法。这些方法么没有读到结束标记。那么就一直等而导致两端,都在等待。
例4:客户端上传一个文件到服务器端
class TextClient{ public static void main(String[] args) throws Exception{ Socket s = new Socket("192.168.1.254",10006); BufferedReader bufr = new BufferedReader(new FileReader("IPDemo.java")); BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream())); PrintWriter out = new PrintWriter(s.getOutputStream(),true); String line = null; while((line=bufr.readLine())!=null){ //能结束,键盘录入的话不能结束,需要在下面判断 out.println(line); } s.shutdownOutput();//关闭客户端的输出流。相当于给流中加入一个结束标记-1.否则服务器端会一直一直等待 String str = bufIn.readLine(); bufr.close(); s.close(); //socket关闭,它中的流也随着关闭 } } class TextServer{ public static void main(String[] args) throws Exception{ ServerSocket ss = new ServerSocket(10006); Socket s = ss.accept(); String ip = s.getInetAddress().getHostAddress(); System.out.println(ip+"....connected"); BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream())); PrintWriter out = new PrintWriter(new FileWriter("server.txt"),true); PrintWriter pw = new PrintWriter(s.getOutputStream(),true); String line = null; while((line=bufIn.readLine())!=null){ //if("over".equals(line)) break; //这种方式如果文件中存在over字符会出错 out.println(line); } pw.println("上传成功"); out.close(); s.close(); ss.close(); } }
例5:上传一个图片到服务器端(单线程)
public class UploadImgClient { public static void main(String[] args) throws UnknownHostException, IOException { Socket socket = new Socket("10.0.247.209",9100); FileInputStream fis = new FileInputStream(new File("C:/Users/145019/Desktop/bbb.png")); OutputStream os = socket.getOutputStream(); InputStream is = socket.getInputStream(); byte[] b = new byte[1024]; int l=0; while((l=fis.read(b))!=-1){//写到b缓存区里 os.write(b, 0, l); } socket.shutdownOutput(); int ll=is.read(b); String str = new String(b,0,ll); System.out.println(str); fis.close(); socket.close(); } }
public class TestTCPServer { public static void main(String[] args) throws IOException { ServerSocket ss = new ServerSocket(9100); while(true){//这种用法只能一个个从队列中取client提供服务 Socket s=ss.accept(); System.out.println(s.getInetAddress()); InputStream is = s.getInputStream(); FileOutputStream fos= new FileOutputStream(new File("D:/server.gif")); OutputStream os = s.getOutputStream(); byte[] b = new byte[1024]; int l=0; while((l=is.read(b))!=-1){ fos.write(b, 0, l); } os.write("bao cun cheng gong!".getBytes()); fos.close(); } } }
例6:客户端并发上传图片到服务器
public class UploadImgClient { public static void main(String[] args) throws UnknownHostException, IOException { 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 socket = new Socket("10.0.247.209",9100); FileInputStream fis = new FileInputStream(file); OutputStream os = socket.getOutputStream(); InputStream is = socket.getInputStream(); byte[] b = new byte[1024]; int l=0; while((l=fis.read(b))!=-1){//写到b缓存区里 os.write(b, 0, l); } socket.shutdownOutput(); int ll=is.read(b); String str = new String(b,0,ll); System.out.println(str); fis.close(); socket.close(); } }
/* 服务端 上例中服务端有个局限性。当A客户端连接上以后。被服务端获取到。服务端执行具体流程。 这时B客户端连接,只有等待。因为服务端还没有处理完A客户端的请求,还有循环回来执行下次accept方法。所以暂时获取不到B客户端对象。 为了可以让多个客户端同时并发访问服务端。服务端最好就是将每个客户端封装到一个单独的线程中,这样,就可以同时处理多个客户端请求。 */ class Handle implements Runnable{ private Socket s; public Handle(Socket s) { this.s=s; } public void run(){ InetAddress ip = s.getInetAddress(); InputStream is=null; FileOutputStream fos=null; OutputStream os=null; try{ int count=1; System.out.println(ip+"连接成功!"); is = s.getInputStream(); File dir = new File("d:\\pic"); File file = new File(dir,ip+"("+(count)+")"+".jpg"); while(file.exists()) file = new File(dir,ip+"("+(count++)+")"+".jpg"); fos= new FileOutputStream(file); os = s.getOutputStream(); byte[] b = new byte[1024]; int l=0; while((l=is.read(b))!=-1){ fos.write(b, 0, l); } os.write("bao cun cheng gong!".getBytes()); }catch(Exception e){ throw new RuntimeException(ip+"上传失败!"); }finally{ try { fos.close(); s.close(); } catch (IOException e) {} } } } public class TestTCPServer { public static void main(String[] args) throws IOException { ServerSocket ss = new ServerSocket(9100); ExecutorService es = Executors.newFixedThreadPool(10);/此处其实和在ServerSocket中直接指定效果一样 while(true){ Socket s=ss.accept(); es.execute(new Handle(s)); } } }
例7:客户端通过键盘录入用户名,服务端对这个用户名进行校验,最多登录3次
/* 如果该用户存在,在服务端显示xxx,已登陆,并在客户端显示 xxx,欢迎光临。 如果该用户存在,在服务端显示xxx,尝试登陆,并在客户端显示 xxx,该用户不存在。*/ class LoginClient{ public static void main(String[] args) throws Exception{ Socket s = new Socket("192.168.1.254",10008); 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 x=0; x<3; x++){ String line = bufr.readLine(); if(line==null) break; out.println(line); String info = bufIn.readLine(); System.out.println("info:"+info); if(info.contains("欢迎")) break; } bufr.close(); s.close(); } } 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); String line = null; boolean flag = false; while((line=bufr.readLine())!=null){ if(line.equals(name)){ flag = true; break; } } if(flag){ System.out.println(name+",已登录"); out.println(name+",欢迎光临"); break; } else{ System.out.println(name+",尝试登录"); out.println(name+",用户名不存在"); } } s.close(); } catch (Exception e){ throw new RuntimeException(ip+"校验失败"); } } } class LoginServer{ public static void main(String[] args) throws Exception{ ServerSocket ss = new ServerSocket(10008); while(true){ Socket s = ss.accept(); new Thread(new UserThread(s)).start(); } } }
例8、自定义浏览器客户端和Tomcat服务器端
public class MyTomcat { public static void main(String[] args) throws IOException { ServerSocket ss=new ServerSocket(9000); Socket s = ss.accept(); InputStream is = s.getInputStream(); byte []b = new byte[1024]; int l=is.read(b); System.out.println(new String(b,0,l)); } }
如上是简易服务器,通过浏览器地址栏输入localhost:9000,可看到控制台输出如下信息,也就是浏览器发送给tomcat的数据
GET / HTTP/1.1 //此处显示要请求的资源路径 Host: 10.0.247.209:9000 //请求主机IP和端口 Connection: keep-alive //功能使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,避免了重新建立连接 Cache-Control: max-age=0 //缓存时间为0,也就是不缓存,下次从新向服务器发送请求 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8//客户端可接收数据类型 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Accept-Encoding: gzip, deflate, sdch //可以接收经过压缩的数据,以减少网络带宽 Accept-Language: zh-CN,zh;q=0.8 //接收的数据语言
如下是按照浏览器发送的内容自定义客户端去访问apachetomcat服务器,可在控制台看到如下信息
public class MyIE { public static void main(String[] args)throws Exception { Socket s = new Socket("10.0.16.114",8070); PrintWriter out = new PrintWriter(s.getOutputStream(),true); out.println("GET /index.html HTTP/1.1"); out.println("Host: 10.0.247.209:9000"); out.println("Connection: keep-alive"); out.println("Cache-Control: max-age=0"); out.println("Accept: text/html,application/xhtml+xml,application/xml;*/*;q=0.8"); out.println("User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36"); out.println("Accept-Encoding: gzip, deflate, sdch"); out.println("Accept-Language: zh-CN,zh;q=0.8"); out.println(); BufferedReader bufr = new BufferedReader(new InputStreamReader(s.getInputStream())); String line = null; while((line=bufr.readLine())!=null){ System.out.println(line); } s.close(); } }
HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Accept-Ranges: bytes ETag: W/"512-1422437486000" Last-Modified: Wed, 28 Jan 2015 09:31:26 GMT Content-Type: text/html;charset=utf-8 Content-Length: 512 Date: Fri, 18 Sep 2015 07:15:51 GMT <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <link href="styles/bootstrap.css" rel="stylesheet" type="text/css" /> </head> <body> <div class="jumbotron"> <h1>F2C</h1> <p></p> <p> <a href="admin/welcome.wss" class="btn btn-primary btn-lg">asdfa</a> <a href="seller/welcome.wss" class="btn btn-primary btn-lg">asdfasf</a> </p> </div> </body> </html>
八、URL和URLConnection
socket是传输层编程,URL是应用层编程,经过封包拆包的过程,在打印内容中看不到http请求头和服务器返回头。
public class TestUrl { public static void main(String[] args) throws URISyntaxException, IOException { URL url = new URL("http://10.0.16.114:8070/index.html"); /*URLConnection con = url.openConnection(); InputStream is = con.getInputStream();*/ InputStream is = url.openStream(); //上面兩句的簡寫 byte[]b=new byte[1024]; int l = is.read(b); System.out.println(new String(b,0,l)); } }
九、ServerSocket(int port,int backlog)构造方法,backlog表示传入连接的最大队列长度,如果队列已满,则拒绝连接。和拿到socket以后指定固定大小的线程连接池来处理也能实现相同效果。
十、域名解析的过程,首先查host文件,如果查到就直接返回,如果没有查到就会查找DNS服务器,可通过host文件来屏蔽网站。