TCP/UDP套接字 java socket编程实例
网络协议七层结构:
什么是Socket?
socket(套接字)是两个程序之间通过双向信道进行数据交换的端,可以理解为接口。使用socket编程也称为网络编程,socket只是接口并不是网络通信协议。
HTTP协议和Socket的区别
http协议是应用层,其模式是请求-应答,客户端发送请求,服务器端进行响应。传输的数据是原始格式的数据,eg :json、xml、text等数据格式。
socket不是协议是接口,socket提供TCP/UDP socket 的实例,供java 或者其他语言操作数据的传输,socket是对传输层(TCP/UPD协议)的封装。
Socket通信分为两种
TCP Socket :使用流传输,提供inputStream 和 outputStream 方法对数据进行流操作。要理解TCP套接字首先要对TCP协议有所理解。
1)TCP协议是传输层的协议,他的下一层是IP协议(网络层),IP协议在网络数据传输是通过ip寻址,将源地址和目的地址进行连接。TCP协议是在IP协议上多加一层端口寻址,光只通过IP寻址只能定位到主机,tcp通过端口找到对应的应用程序。
2)TCP 建立连接需要三次握手,将源应用程序和目的应用程序之间搭建一个连接,所以源应用和目的应用程序之间必须是one by one。IP 协议只管数据的传输,不保证数据是否丢失,重复传,顺序是否正确,TCP会对这些问题做一些补偿机制,丢失数据重传,用队列保证数据的顺序。
3) TCP 缺点:因为每个客户端和服务器端传输数据都要建立连接,三次握手是不传输数据并且有耗时,当有大量短连接的时候并且对数据的正确性要求不高的时候,将会占用带宽。
UDP Socket:使用数据报文进行传输,创建UDP socket 发送和接收数据报文。
1)UDP协议同TCP协议一样都是应用层协议,也是通过端口寻址,找到对应的应用程序。
2)UDP传输数据报文不需要和目的应用程序建立连接,他在数据报文中指定目的主机和目的端口号,发送出的数据自动寻址到对应的主机和端口号。因为不用和目的主机建立连接,所以一个源应用程序可以以广播的形式将数据报文传输给多主机。因为不用建立连接,耗时和带宽占用量都比TCP协议更优秀
3)UDP缺点:数据有可能丢失,丢失的数据不会重传
java socket 实例
TCP Socket client
package socket.transmission.tcp; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; //TCP 套接字 客户端负责发送请求 public class TcpClient { private static final int BUF_SIZE=32; /** * TCP客户端发送一个请求要有三个步骤: * 1.创建一个socket的实例,创建一个指向server主机ip和端口号的TCP连接 * 2.通过套接字的输入和输出流进行通信 * 3.使用socket close关闭 */ public static void main(String[] args){ String ip="192.168.197.1"; int port=8080; try { // 创建一个socket实例 Socket socket=new Socket(ip,port); // 1 设置TCP SOCKET,初始化目的主机ip和端口号,建立和目的主机的连接,若目的主机没有开启服务,则会弹出server refused System.out.println("创建一个socket连接"); InputStream inputStream=socket.getInputStream(); // 2 获取回馈服务器的输入流 OutputStream outputStream=socket.getOutputStream(); // 3 将要传输的数据数据写入到输出流中,传输给目的主机 //向socket中写入数据 outputStream.write("this is a word".getBytes()); // 4 传输数据到目的主机 int totalByrecive=0; //到目前为止接收到的数据 byte[] readBuff=new byte[BUF_SIZE]; int lastReadByte; //最后接收的字节 System.out.println("从服务器中接收的数据:"); int receiveMsgSize; while ((receiveMsgSize=inputStream.read(readBuff))!=-1){ // 5.从回馈服务器中获取数据, System.out.println(new String(readBuff)); } socket.close(); //关闭 } catch (IOException e) { e.printStackTrace(); } } }
tcp sokect server
package socket.transmission.tcp; //TCP 服务器端进行接收请求 import sun.java2d.pipe.OutlineTextRenderer; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketAddress; /** * TCP服务器对客户端发送的请求会进行以下处理 * 1.创建serverSocket实例并且指定本机端口,功能:监听指定端口发送过来的连接 * 2.重复执行: * 1).调用的serverSocket 的accept() 监听客户端发送过来的请求,并创建socket * 2).使用socket的inputStream 和 outputStream 进行通讯 * 3).通信完使用socket.close() 方法将连接关闭 */ public class TcpServer { private static final int BUF_SIZE=32; public static void main(String[] args){ int port=8080; Socket socket = null; InputStream inputStream = null; OutputStream outputStream = null; try { ServerSocket serverSocket=new ServerSocket(port);//创建一个socket实例用于监听客户端发送的连接,指定本服务器的端口号 System.out.println("创建serverSocket 实例"); int reviceMsgSize; // 接收msg的大小 byte[] receiveBuf=new byte[BUF_SIZE]; //创建一个信息接收的缓冲区 System.out.println("开始处理接收的数据"); while (true) { socket = serverSocket.accept(); //接收客户端的连接,每接收一个数据都会创建一个连接,当没有数据的接收的时候会阻塞 SocketAddress socketAddress = socket.getRemoteSocketAddress(); // System.out.println("访问的地址:" + socketAddress); inputStream = socket.getInputStream(); outputStream = socket.getOutputStream(); while ((reviceMsgSize = inputStream.read(receiveBuf)) != -1) { System.out.println(new String(receiveBuf)); outputStream.write("aaaaa".getBytes(), 0, 4); } outputStream.flush(); socket.close(); } } catch (IOException e) { e.printStackTrace(); }finally { try { if(socket!=null){ socket.close(); } if(inputStream!=null){ inputStream.close(); } if(outputStream!=null){ outputStream.close(); } } catch (IOException e) { e.printStackTrace(); } } } }
UDP Socket client
package socket.transmission.udp; import java.io.IOException; import java.io.InterruptedIOException; import java.net.*; //UDP 套接字传输的是数据报文 /** * UDP 客户端发送数据报文的步骤: * 1.创建UPD 套接字 DataGramSocket ,使用UDP协议通信是不需要和服务器端创建的连接的,UPD协议只是在IP协议上 * 多加了一层端口寻址,设置超时 * 2.创建发送的数据报文实例DataGramPacket,若是单播的则要指明目的端的ip地址和port,IP地址通过创建InetAddress 的实例 * 3.创建接收数据报文实例DataGramPacket ,指定接收数据的缓冲区 * 4.调用socket的send方法将数据报文发送出去 * 5.循环接收数据报文,当数据报文丢失的时候,发起重试。否则设置响应标志位true,将数据打印 */ public class UdpClient { /** * 当客户端发送给server端信息,收到回馈信息的时候,通过read读取数据,当没有数据返回(数据丢失) * read 方法会发生阻塞,若没有设置超时重发,则程序会一直阻塞 */ private static final int TIME_OUT=2000; //设置超时重发时间 private static final int MAX_RENTRY=3; // 设置最大重试次数 public static void main(String[] args) throws IOException { try { int serverPort=8080; // 指定 byte[] sendMsg="this is a test".getBytes(); DatagramSocket socket=new DatagramSocket(); //创建一个数据报文 socket.setSoTimeout(TIME_OUT); //设置read阻塞超时时间 byte[] ipByte={10,1,1,100}; /** * "10.1.1.100".getbytes()的方式不能正确的创建server端,调用InetAddress.getByAddress() 方法将会做两个长度判断,IPV4 的入参长度要==4 * IPV6 的长度要== 16 而通过10.1.1.100 getbytes的方式获取的长度是10 抛出违法的长度 */ InetAddress inetAddress= InetAddress.getByAddress(ipByte); //创建server主机的ip地址 DatagramPacket sendPacket=new DatagramPacket(sendMsg,sendMsg.length,inetAddress,8080); //发送的数据报文 DatagramPacket receivePacket=new DatagramPacket(new byte[sendMsg.length],sendMsg.length); //接收的数据报文 int tryTimes=0; //数据报文可能丢失,设置重试计数器 Boolean receiveResponse=false; socket.send(sendPacket); //将数据报文发送出去 do{ try { socket.receive(receivePacket); //尝试去循环接收数据报文 if (!receivePacket.getAddress().equals(inetAddress)) { //检查回馈过来的数据报文 throw new IOException("未知的Server端数据报文"); } receiveResponse=true; }catch (InterruptedIOException e){ //数据报文中断异常 tryTimes++; System.out.println("超时还有"+(MAX_RENTRY-tryTimes)+"次重试机会"); } }while(!receiveResponse&&(tryTimes<MAX_RENTRY)); if(receiveResponse){ System.out.println("从服务器端获取的数据:"+new String(receivePacket.getData())); }else{ System.out.println("没有获取到数据"); } socket.close(); //关闭套接字 } catch (SocketException e) { e.printStackTrace(); } } }
UPD Soceket server
package socket.transmission.udp; import java.io.IOException; import java.io.InterruptedIOException; import java.net.*; //UDP 套接字接收客户端的数据报文 public class UdpServer { private static final int ECHO_MAX=255; //设置缓冲区的长度 public static void main(String[] args) { try { DatagramSocket datagramSocket=new DatagramSocket(8080); DatagramPacket reveiveMsg=new DatagramPacket(new byte[ECHO_MAX],ECHO_MAX); while(true){ datagramSocket.receive(reveiveMsg); System.out.println("从客户端接收的来数据:"+new String(reveiveMsg.getData())); //在服务器端将发送的信息修改 byte[] newData="啦啦啦啦".getBytes(); // reveiveMsg=new DatagramPacket(newData,newData.length); //将转化后的数据发送 datagramSocket.send(reveiveMsg); /** * 重置接收包的长度,因为接收数据的时候已经接收包的长度设置为接收信息的长度,当下次再接收数据的时候, * 新数据的长度大于上一次数据的长度时,多出的数据将被截断,所以要重置接收包缓冲区的长度 */ reveiveMsg.setLength(ECHO_MAX); } } catch (UnknownHostException e) { e.printStackTrace(); }catch (SocketException se){ se.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
上面的代码还有一些未补足的:要在finally 中将所有的流关闭。