对于udp和tcp的详细解读

来源:互联网 发布:淘宝满减计入最低价吗 编辑:程序博客网 时间:2024/05/23 12:57
1.网络通信协议

   通过计算机网络可以使得多台计算机实现连接,位于同一个网络中的计算机进行连接和通信需要遵守一定的规则,在计算机网络中,这些连接和通信的规则被称为网络通信协议。


 网络通讯的三要素:
1. IP
2. 端口号。

3. 协议.


IP地址: IP地址的本质就是一个由32位的二进制数据组成的数据。
 后来别人为了方便我们记忆IP地址,就把IP地址切成了4份,每份8bit.   2^8 = 0~255
      00000000-00000000-00000000-00000000
 端口号是没有类描述的。
端口号的范围: 0~65535
从0到1023,系统紧密绑定于一些服务。 
1024~65535  我们可以使用....

IP地址 = 网络号+ 主机号。

IP地址的分类:
A类地址 = 一个网络号 + 三个主机号     2^24   政府单位
B类地址 =  两个网络号+ 两个主机号   2^16 事业单位(学校、银行..)
C类地址= 三个网络号+ 一个主机号  2^8    私人使用..
 
  目前最广泛的是tcp/ip协议,它包括TCP协议,IP协议、UDP协议ICMP协议和其它的一些协议组织。
  TCP/ip协议中包括四层:应用层,传输层,网络层,链接层。

2.InetAddress
   该类用于封装了一个ip地址,通过InetAddress对象便可以获取指定主机名,ip地址。
   InetAddress getByName(String host) 根据指定的主机名获取InetAddress对象
   InetAddress getLocalHost()  创建一个本地主机的InetAddress对象。
   String getHostAddress()  返回一个IP地址的字符串表示形式。
   String getHostName()  返回计算机的主机名。
3.UDP(User Daragram Protocol)用户数据协议
   UDP是无线连接通信协议,即在数据传输时不建立发送端和接收端的连接,简单的说就是当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在
就会发送数据,同样在接收端收到数据时也不会向发送端反馈是否收到数据   
 
   由于UDP协议消耗小,通信效率高,所以通常会用在音频、视频、普通数据的传输,因为UDP面向无连接性,不能保证数据的完整性,所以在传输重要数据的时候不建议
使用UDP,udp是一个不可靠(数据包可能会丢失)的协议

什么情况下数据包会出现丢失呢?
1.带宽不足 。
2.cpu的处理能力不足。

使用场景:
比如: 物管的对讲机, 飞Q聊天、 游戏...


4.UDP通信
  (1)DataGramPacket
    UDP通信就像是货运公司在两个码头发送货物。在码头发送货物需要集装箱,DataGramPacket类的实例对象就相当一个集装箱,用于封装UDP通信中的发送或者接收的数据
    想要创建一个DataGramPacket对象,就要想了解它的构造方法,在发送端和接收端创建的DataGrampacket对象,使用的构造方法有所不同,接收端的构造方法只需要
接收一个字符数组来存放接受的数据,而发送端的构造方法不但需要接收存放了发送数据的字符数组还需要指定发送的ip地址和端口号。
DataGramPacket(byte[]buf , int length):参数指定了封装数据的字节数组和数据大小,显然用于接收端。
DataGramPacket(byte[]buf , int length ,InetAddress addr , int port):不仅指定了封装数据的字节数组和数据的大小,还指定了数据包的目标IP地址和端口号,通常用于发送端
DataGramPacket(byte[]buf , int offset , int length):增加了一个offset参数用于指定就收到的数据放入buf缓冲数组是是从offset处开始。
DataGramPacket(byte[]buf , int offset ,int length ,InetAddress addr , int port):即从offset位置开始发送数据。

  (2)DataGramSocket
    然而运输货物只需要“集装箱”是不够的,还需要码头。在程序中需要实现通信只有DatagramPacket数据包同样也不行,DatagrmSocket类的作用就类似于码头,
    使用这个类的实例对象就可以发送和接收DataGramPacket数据包。同样在创建发送端和接收端的DataGramSocket对象所使用的构造方法是不同的,
    DataGramSocket():该构造方法并没有指定端口号,此时系统会分配一个没有被占用的端口号。
    DataGramSocket(int pocket):该构造方法既可以用户发送端也可以用接收端,在创建接收端的时候必须指定一个端口号,这样就可以监听指定的端口。
    DataGramSocket(int pocket , InetAddress addr):制定了端口号和ip地址。可以用于计算机上有多个网卡的情况。
    
    常用方法:
void receive(DataGramPacket p):用于接收到的数据填充到DataGremPacket数据包中,在接收到数据之前会一直处于阻塞状态,只有当接收到数据包时,该方法才返回。
void send(DataGramPacket p):用于发送数据包。
    void close() :关闭socket
  
 (3)UDP通讯协议的特点:
1. 将数据极封装为数据包,面向无连接。
2. 每个数据包大小限制在64K中
3.因为无连接,所以不可靠
4. 因为不需要建立连接,所以速度快
5.udp 通讯是不分服务端与客户端的,只分发送端与接收端。
  
 (4)模拟udp协议:

发送端的使用步骤:
1. 建立udp的服务。
2. 准备数据,把数据封装到数据包中发送。 发送端的数据包要带上ip地址与端口号。
3. 调用udp的服务,发送数据。
4. 关闭资源。
   
   接收端的使用步骤
  1. 建立udp的服务
  2. 准备空的数据包接收数据。
  3. 调用udp的服务接收数据。

  4. 关闭资源

 /*     * 接收端     */@Testpublic void test() throws IOException{//创建一个数组byte[] buf=new byte[1024];//创建DatagramSocket对象监听端口9999DatagramSocket socket=new DatagramSocket(9999);//建立DatagramPacket对象,传入数组和大小DatagramPacket packet=new DatagramPacket(buf, 1024);System.out.println("等待数据");socket.receive(packet);//打印接收的数据System.out.println("内容:"+packet.getData().toString()+"   发送端ip:"+packet.getAddress().getHostAddress()+"  发送端端口:"+packet.getPort());//关闭资源socket.close();}
/* * 发送端 */@Testpublic void test2() throws SocketException, IOException{String s="hello udp";byte[] buf=s.getBytes();DatagramPacket packet=new DatagramPacket(buf, buf.length,InetAddress.getLocalHost(),9999);//在发送端可以不指定 端口号,如果指定端口号是为了让接收端的getPort()方法返回值都是一致的,否则发送端的端口号有系统自动分配。DatagramSocket socket=new DatagramSocket(3000);socket.send(packet);System.out.println("发送信息");socket.close();}
注:在创建一个发送端程序的DatagramPacket对象时需要指定目标的ip和端口号这样调用DaragramSocket的send方法才能将发送的数据送到对应的接收端。

使用udp通信协议进行群聊

//群聊发送端public class chatSender extends Thread {     @Override //开启多线程    public void run() {     try {InputStream input=System.in;  //键盘录入//创建一个BufferedReader字符流进行一行一行的读取,所以这时就需要一个转换流InputStreamReaderBufferedReader reader=new BufferedReader(new InputStreamReader(input)); DatagramPacket packet=null;DatagramSocket socket=new DatagramSocket();String len=null;while((len=reader.readLine())!=null){          //使用的广播地址packet=new DatagramPacket(len.getBytes(),len.getBytes().length,InetAddress.getByName("255.255.255.255"),9000);socket.send(packet);System.out.println("信息已经发送");}socket.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}    }}
//接收端public class chatRecive extends Thread {    @Override    public void run() {        try {    byte[] buf=new byte[1024];DatagramSocket socket=new DatagramSocket(9000);    DatagramPacket packet=new DatagramPacket(buf,buf.length);    boolean flag=true;    while(flag){    socket.receive(packet);    System.out.println(packet.getAddress().getHostName()+":说"+new String(buf,0,packet.getLength()));    }    socket.close();    }catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}    }}
//开启群聊,可以再控制台进行输入public class chatMain {public static void main(String[] args) {chatSender s=new chatSender();    s.start();        chatRecive r=new chatRecive();    r.start();}}


5.TCP(Transminssion Control Protocol)协议
    (1) TCP协议是面向连接的通信协议,即在传输数据前实现发送端和接收端建立连接,然后在传输数据,他提供了两台计算机之间无差错的数据传输。
  在TCP连接中必须明确客户端和服务端,由于客户端向服务端发出链接请求,每次连接都需要经过三次握手。(看图解)

 在通信时,必须由客户端去连接服务端才能实现通信,服务端不能主动连接客户端,并且服务端程序要事先开启,等待客户端的连接。


    (2)在jdk中提供两个类实现TCP程序,一个是ServerSocket类,用于表示服务器端,一个Socket类,用于表示客户端。通信时首先建立服务器端的ServerSocket对象,
  等待客户端的连接,然后创建客户端的Socket对象向服务器端发送链接,服务器端相应请求,两者之间建立连接。
  
    (3)ServerSocket(java.net包下,实现服务器端程序)
       该类提供了多个构造方法:
ServerSocket():  
该方法创建对象并没有绑定端口号,不能直接使用,还需要继续调用bind(SocketAddress endpoint)方法绑定端口号,才能使用。

ServerSocket(int port):   ***常用***
使用该构造方法创建对象时就可以绑定指定的端口号(参数port就是端口号),端口号可以为零,此系统会分配一个还没有被其他程序占用的端口号。
由于客户端需要指定端口号进行访问服务端,所以一般就会指定一个端口号。
 
ServerSocket(int port,int backlog):
该方法就是在上一个构造方法的基础上增加了一个backlog参数,该参数用于指定再服务器忙时,可以与之保持请求的客户端数量,如果没有指定默认为50。

ServerSocket(int port,int backlog,InetAddress addr):
该构造方法还指定了一个ip相关的地址,这种情况适用于计算机上有多网卡和多个ip的情况下,
  
       ServerSocket的常用方法:
Socket accept():
用于等待客户端的连接,在客户端连接之前一直处于阻塞状态,如果有客户端连接返回一个对应的Socket对象。
  
InetAddress getInetAddress():
返回一个InerAddress对象,该对象中封装了ServerSocket绑定的ip地址

boolean isClosed():
判断ServerSoket对象是否处于关闭状态。

void bind(SocketAddress endpoint):
用于将ServerSocket对象绑定到指定的端口和ip地址,endpoint参数封装了ip地址和端口号

    (4)Socket(实现客户端程序)
  常用的构造方法:
Socket():
该方法在创建Socket对象时并没有指定端口号和ip地址,并不能连接服务端,还需要调用connect(SocketAddress endpoint)方法,endpoint参数用于封装ip地址和端口号

Socket(String host,int port):
host参数接收一个字符串类型的ip地址。会根据指定的ip地址和端口号连接运行的服务器。

Socket(InetAddress addr,int port):
参数addr用于接收InetAddress对象,用于封装ip。

        常用方法:
 int getport():返回一个int类型的对象该对象是客户端和服务器端连接的端口号。
InetAddress getLocalAddress():获取Socket对象绑定的ip地址封装在InetAddress中
void close():关闭socket连接
IputStream getInputStream(): 读取客户端发送的数据或者读取服务端发送的数据。
OutputStream getOutputStream():向客户端发送数据或者向服务端发送数据。

(5)TCP通讯协议特点:
1. tcp是基于IO流进行数据 的传输 的,面向连接。
2. tcp进行数据传输的时候是没有大小限制的。
3. tcp是面向连接,通过三次握手的机制保证数据的完整性。 可靠协议。
4. tcp是面向连接的,所以速度慢。
5. tcp是区分客户端与服务端 的。

比如: 打电话、 QQ\feiQ的文件传输、 迅雷下载....

(6)模拟tcp协议:

tcp的客户端使用步骤:
1. 建立tcp的客户端服务。
2. 获取到对应的流对象。
3.写出或读取数据
4. 关闭资源。

java.net.BindException:   端口被占用。

    tcp的服务端的使用步骤:
1. 建立tcp服务端 的服务。
2. 接受客户端的连接产生一个Socket.
3. 获取对应的流对象读取或者写出数据。
4. 关闭资源。

//tcp服务端public class TcpServer { public static void main(String[] args)throws IOException {//建立tcp服务端并监听一个端口ServerSocket server=new ServerSocket(8880);//接受客户端的连接Socket socket  =  serverSocket.accept(); //accept()  接受客户端的连接 该方法也是一个阻塞型的方法,没有客户端与其连接时,会一直等待下去。//获取输入流对象,读取客户端发送的内容。InputStream inputStream = socket.getInputStream();byte[] buf = new byte[1024];int length = 0;length = inputStream.read(buf);System.out.println("服务端接收:"+ new String(buf,0,length));//获取socket输出流对象,想客户端发送数据OutputStream outputStream = socket.getOutputStream();outputStream.write("客户端你好啊!".getBytes());//关闭资源serverSocket.close();} }
//客户端public class TcpClient {public static void main(String[] args) throws IOException {//创建客服端连接Scoket,指定ip和端口号Socket socket=new Socket(InetAddress.getLocalHost(),8880);//获取到Socket的输出流对象OutputStream outputStream = socket.getOutputStream();//利用输出流对象把数据写出即可。outputStream.write("服务端你好".getBytes());//获取到输入流对象,读取服务端回送的数据。InputStream inputStream = socket.getInputStream();byte[] buf = new byte[1024];int length = inputStream.read(buf);System.out.println("客户端接收到的数据:"+ new String(buf,0,length));//关闭资源socket.close();}}
注:本人在模拟tcp协议的时候遇到一个错误在获取输入输出流是必须要遵循“一收一发”,也就是如果客户端先获取输出流对象OutputStream
在获取输入流对象InputStream,那么服务端就必须先获取输入流对象给客户端一个相应,然后在获取输出流对象(可以省略)。
    下面是错误的代码:

//客户端public class TcpClient {public static void main(String[] args) throws IOException {//创建客服端连接Scoket,指定ip和端口号Socket socket=new Socket(InetAddress.getLocalHost(),8880);//获取到Socket的输出流对象OutputStream outputStream = socket.getOutputStream();//利用输出流对象把数据写出即可。outputStream.write("服务端你好".getBytes());//获取到输入流对象,读取服务端回送的数据。InputStream inputStream = socket.getInputStream();byte[] buf = new byte[1024];int length = inputStream.read(buf);System.out.println("客户端接收到的数据:"+ new String(buf,0,length));//关闭资源socket.close();}}        //tcp服务端public class TcpServer { public static void main(String[] args)throws IOException {//建立tcp服务端并监听一个端口ServerSocket server=new ServerSocket(8880);//接受客户端的连接Socket socket  =  serverSocket.accept(); //accept()  接受客户端的连接 该方法也是一个阻塞型的方法,没有客户端与其连接时,会一直等待下去。//获取socket输出流对象,想客户端发送数据OutputStream outputStream = socket.getOutputStream();outputStream.write("客户端你好啊!".getBytes());//获取输入流对象,读取客户端发送的内容。InputStream inputStream = socket.getInputStream();byte[] buf = new byte[1024];int length = 0;length = inputStream.read(buf);System.out.println("服务端接收:"+ new String(buf,0,length));//关闭资源serverSocket.close();} }
问题:读到这里大家有没有想过问什么ServerSocket不设计一个getInputStream与getOutputStream 方法呢?(图解)
   答:假如现在有两个客户端分别是“张三(Socket)”和“李四(Socket)”,
    张三(Socket)问服务端(调用getOutputStream方法),吃饭了吗?
李四(Socket)同时也问服务端(调用getOutputStream方法),打球去好吗?
服务器(ServerSocket)肯定要发送响应,如果ServerSocket有getInputStream方法,回复一个不好。
张三(socket)说这个服务器肯定崩溃了。
李四收到响应。
 
这时问题就出现了,如果ServerSocket有getInputStream方法,可以响应但是并不知道他们的ip和端口号所以出现了混乱。
所以ServerSocket会有accept()方法,获取对应客户端的socket,然后在获取输入流发出响应。


 
(6)多线程的TCP通信
    很多服务器端都允许被多个应用程序访问的,例如门户网站,可以被多个用户同时访问。

模拟使用TCP进行客户端与服务端一对一聊天

/* * TCP进行聊天 * 客户端 * * 1.如果使用BuffrerdReader的readline方法一定要加上\r\n才把数据写出或者使用newLine()。         * 2.使用字符流一定要调用flush方法数据才会写出。 * 3.需要注意的是,由于包装流内部使用缓冲区,在循环调用中使用BufferedWriter的writer()方法进行写字符时,这些字符首相会被写入缓冲区 *   ,当缓冲区写满时或调用close()方法时,缓冲区中的字符才会被写入带目标文件。因此再循环结束时一定要调用close()方法。 */ public class ChatClient {public static void main(String[] args) throws IOException{//创建一个Socket并连接到给定的ip和端口号  Socket socket=new Socket(InetAddress.getLocalHost(),9090);  //获取输出流对象,向服务端发送数据  OutputStream outputeSream=socket.getOutputStream();   //使用bufferedWriter字符流给服务端进行发送信息,需要进行流之间的转换  BufferedWriter socketWriter=new BufferedWriter(new OutputStreamWriter(outputeSream));  //得到一个输入流对象  InputStream inputStream=socket.getInputStream();  //将输入流转化为字符输入流  BufferedReader socketReader=new BufferedReader(new InputStreamReader(inputStream));  //使用键盘录入  //转化为字符输入流使用readline进行一行一行的读取键盘信息,需要进行流之间的转换  BufferedReader keyReader=new BufferedReader(new InputStreamReader(System.in));  String len;  while((len=keyReader.readLine())!=null){  socketWriter.write(len);  //换行  socketWriter.newLine();//刷新  socketWriter.flush();  //读取服务端的数据  len=socketReader.readLine();  System.out.println("服务端响应的信息:"+len);  }keyReader.close();socketWriter.close();  //关闭资源  socket.close();}}
/* * TCP服务端 */public class ChatServer {   public static void main(String[] args) throws Exception {ServerSocket serverSocket=new ServerSocket(9090);Socket socket=serverSocket.accept();//获取输入流对象BufferedReader socketReader=new BufferedReader(new InputStreamReader((socket.getInputStream())));//获取输出流对象BufferedWriter socketwriter=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));//键盘录入BufferedReader keyReader=new BufferedReader(new InputStreamReader(System.in));String len;while((len=socketReader.readLine())!=null){//打印客户端发送的数据System.out.println("客户端:"+len);//服务端发送数据len=keyReader.readLine();socketwriter.write(len);socketwriter.newLine();socketwriter.flush();//向客户端发送信息}keyReader.close();socketwriter.close();socket.close();}}


下面模拟多线程的服务端:/* * 多线程服务端程序 */public class ThreadServer  {  public static void main(String[] args)throws Exception{      //建立tcp的服务端  ServerSocket serverSocket=new ServerSocket(10001);  //不断的接受客户端的连接  while(true){  TcpServer1 tcp=new TcpServer1(serverSocket.accept());  tcp.start();  }  }}  class TcpServer1 extends Thread {  Socket socket;  public TcpServer1(Socket socket){  this.socket=socket;  }  //开启多线程  public void run() {    try {   //获取输入流对象  byte[] b=new byte[1024];InputStream socketin=socket.getInputStream();int len=socketin.read(b);//输出客户端发送的数据System.out.println(Thread.currentThread()+"说"+new String(b,0,len));socket.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}  }}

可以运行前面写的客户端进行验证。