概念
网络编程分为BIO(传统IO)、NIO、AIO。Socket编程属于BIO这种传统IO。
InetAddress
java.net.InetAddress是JAVA中管理IP地址的类,常用
public static void main(String[] args) throws UnknownHostException { InetAdressDemo.getLocalHost(); System.out.println("---------------------------"); getHostByName("Lenovo-Autumn"); } /** * 获取主机ip和主机名 * @throws UnknownHostException */ public static void getLocalHost() throws UnknownHostException { //根据InetAddress获取主机名和主机ip InetAddress localHost = InetAddress.getLocalHost(); System.out.println(localHost); //打印:Lenovo-Autumn/192.168.56.1 //根据getLocalHost()返回的值获取ip和主机名 String hostName = localHost.getHostName(); String hostAddress = localHost.getHostAddress(); System.out.println(hostName); //打印 Lenovo-Autumn System.out.println(hostAddress); //打印 192.168.56.1 //根据切割获取主机名和ip String[] str = localHost.toString().split("/"); System.out.println(str[0]); //打印 Lenovo-Autumn System.out.println(str[1]); //打印 192.168.56.1 } /** * 根据主机名称获取ip地址 * @param otherName 主机名(可以是局域网中的机器名或者是域名或者是ip) * @throws UnknownHostException */ public static void getHostByName(String otherName) throws UnknownHostException { InetAddress otherHost = InetAddress.getByName(otherName); String hostName = otherHost.getHostName(); String hostAddress = otherHost.getHostAddress(); System.out.println(hostName); //打印 Lenovo-Autumn System.out.println(hostAddress); //打印 192.168.56.1 System.out.println(otherHost); //打印:Lenovo-Autumn/192.168.56.1 }
UDP
发送数据时必须指定接收端的IP地址和端口号,就好像发送货物的集装箱上面必须标明接收人的地址一样。
接收端不需要明确知道数据的来源,只需要接收到数据即可。
java.net.DatagramPackage
构造函数:
第一种是用来接受的数据包,不需要指定IP和端口,第二种是用来发送的数据包需要指定ip和端口
方法:
获取ip地址,获取服务器ip或者客户端ip
返回端口号,获取发送方或者
返回数据缓冲区
返回要发送或接受的数据包大小
java.net.DatagramSocket
DatagramPacket数据包的作用就如同是“集装箱”,可以将发送端或者接收端的数据封装起来。然而运输货物只有“集装箱”是不够的,还需要有码头。在程序中需要实现通信只有DatagramPacket数据包也同样不行,为此JDK中提供的一个DatagramSocket类。DatagramSocket类的作用就类似于码头,使用这个类的实例对象就可以发送和接收DatagramPacket数据包,发送数据的过程如下图所示。
构造函数:
用来创建发送端的DatagramSocket对象
用来创建接收端的DatagramSocket对象
方法:
发送数据报包
接收数据报包
发送端
1, 创建DatagramSocket对象
DatagramSocket()
2,创建DatagramPacket对象,封装数据,并指定ip和端口。
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
3,发送数据
socket.send(DatagramPacket dp)
4,释放流资源
ds.close();
接收端
1,创建DatagramSocket对象,只需要指定端口
DatagramSocket(port)
2,创建DatagramPacket对象
DatagramPacket(byte[] data, int length)
3,接收数据存储到DatagramPacket对象中
receive(DatagramPackage dp)
4,获取DatagramPacket对象的内容
new String(data,0,dp.getLength());
5,释放流资源
ds.close();
/** * 实现udp发送端 * 用java.net.DatagramPackage封装数据 * 用java.net.DatagramSocket发送数据 * * 实现步骤 * 1.用DatagramPackage对象,封装数据,接受的地址和端口 * 2.创建DatagramSocket * 3.调用DatagramSocket对象send方法,发送数据 * 4.关闭资源 * * DatagramPackage构造函数 * DatagramPacket(byte[] buf, int length, InetAddress address, int port) * DatagramSocket构造函数 * DatagramSocket() * 方法:send(DatagramPacket d) * Created by Autumn on 2018/2/5. */ public class UdpSend { public static void main(String[] args) throws Exception { Scanner scanner = new Scanner(System.in); //获取地址 InetAddress inet = InetAddress.getByName("127.0.0.1"); //创建DatagramSocket,负责接受和发送数据 DatagramSocket ds = new DatagramSocket(); while(true){ String msg = scanner.nextLine(); //创建数据包对象对象 byte[] data = msg.getBytes(); //封装数据,接受的地址和端口 DatagramPacket dp = new DatagramPacket(data,data.length,inet,6000); //发送数据包 ds.send(dp); if(msg.equals("exit")){ break; } } //关闭 ds.close(); } } /** * 实现udp接收端 * 用java.net.DatagramPackage 接受数据 * 用java.net.DatagramSocket 接受数据包 * * 步骤 * 1.创建DatagramSocket对象,绑定端口号(要和发送端端口一致) * 2.创建字节数组用来接受数据 * 3.创建数据对象包DatagramPackage * 4.创建DatagramSocket * receive(DatagramPackage dp)接受数据,将数据封装如dp中 * 5.拆包 * 发送端的ip地址(DatagramPackage.get) * 接受到的字节数组 * 发送的端口号 * 6.关闭资源 * Created by Autumn on 2018/2/5. */ public class UdpReceive { public static void main(String[] args) throws IOException { //创建数据包传输的对象,并绑定端口号 DatagramSocket ds = new DatagramSocket(6000); //创建字节数组 byte[] data = new byte[1024]; while(true){ //创建数据包对象,传递字节数组 DatagramPacket dp = new DatagramPacket(data,data.length); //调用ds对象的receive接受数据包,receive()有线程阻塞效果会一直等待接受数据 ds.receive(dp); //获取数据包大小 int len = dp.getLength(); //获取发送端的ip地址 InetAddress sendAddress = dp.getAddress(); String sendHostAddress = sendAddress.getHostAddress(); //System.out.println(sendHostAddress); //获取发送端端口号 int port = dp.getPort(); //System.out.println(port); //System.out.println(new String(data)); //直接打印1024个字节的字符串,有很多空格 System.out.println(sendHostAddress+":"+port+" "+new String(data,0,len)); //这样打印没有多余的空格 if(new String(data,0,len).equals("exit")){ break; } } //关闭 ds.close(); } }
TCP
ServerSocket类,用于表示服务器端,Socket类,用于表示客户端。建立连接后用流进行输入和输出
ServerSocket
实例化一个ServerSocket类,指定端口号
监听并接受此套接字的连接
返回此服务器套接字的本地地址
Socket
实例化一个Socket并指定ip和端口
返回一个输出流,用于客户端发送数据
返回一个输入流,用于服务器端接受数据
返回ip地址(服务器端的地址)
返回端口号
客户端
1,创建客户端的Socket对象,指定服务器IP和端口号
Socket(String host, int port)
2,获取Socket的输出流对象
getOutputStream();
3,写数据给服务器
out.write(“服务器数据”.getBytes());
4,关闭流资源
socket.close();
服务器端
1,创建服务器端ServerSocket对象,指定服务器端端口号
ServerSocket(int port)
2,开启服务器,等待着客户端Socket对象的连接,如有客户端连接,返回客户端的Socket对象
accept()
3,通过客户端的Socket对象,获取客户端的输入流,为了实现获取客户端发来的数据
socket.getInputStream();
4,通过客户端的输入流,获取流中的数据
byte[] data = new byte[1024];
int len = inputStream.read(data);
System.out.println(new String(data,0,len));
5,通过客户端的Socket对象,获取客户端的输出流,为了实现给客户端反馈信息
6,通过客户端的输出流,写数据到流中
7,关闭流资源
socket.close();
serverSocket.close();
/** * 实现TCP服务器程序 * 表示服务器程序的类java.net.ServerSocket * 构造方法: * ServerSocket(int port); 传递端口号 * * Important:必须获得客户端的套接字(Socket) * 方法:Socket accept() * 服务器可以获取到客户端的套接字 * Created by Autumn on 2018/2/5. */ public class TCPServer { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(8888); //调用服务器套接字对象accept()获取客户端套接字,具有线程等待效果 Socket socket = serverSocket.accept(); //这里会阻塞等待连接接入 cmd中telnet 127.0.0.1 8888即可连接 //根据获得的客户端的socket获取输入流 InputStream inputStream = socket.getInputStream(); //根据输入流将数据读入到data中 byte[] data = new byte[1024]; int len = inputStream.read(data); //这里会阻塞,等待数据 cmd中ctrl+]进入到telnet操作模式send value发送数据 System.out.println(new String(data,0,len)); socket.close(); serverSocket.close(); } } /** * 实现TCP客户端,连接到服务器 * 和服务器实现数据交换 * 实现TCP客户端程序的类 java.net.Socket * * 构造方法: * Socket(String host, int port)传递服务器IP和端口号 * 注意:构造方法只要运行,就会和服务器进行连接,连接时报,抛出异常 * * OutputStream getOutputStream() 返回套接字的输出流 * 作用:将数据输出,输出到服务器 * InputStream getInputStream() 返回套接字的输入流 * 作用:从服务器端读取数据 * * 客户端服务器数据交换,必须使用套接字对象Socket中的获取的IO刘,自己new的流不行 * * Created by Autumn on 2018/2/5. */ public class TCPClient { public static void main(String[] args) throws IOException { //创建Socket对象,连接服务器 Socket socket = new Socket("127.0.0.1",8888); //通过客户端的套接字对象Socket方法,获取字节输出流,将数据写向服务器 OutputStream out = socket.getOutputStream(); out.write("这是一条来客户端的数据".getBytes()); socket.close(); } }
用cmd命令telnet实现和SocketServer互动
import java.io.IOException; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; public class TCPServer { /*同步阻塞*/ public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(8888); System.out.println("服务端启动成功..."); while(true){ /*一次只能处理一个连接,在一个连接没关闭前无法接收第二个连接,在这里开一个框发送数无问题,开放两个框时会出现第二个无反应*/ Socket socket = serverSocket.accept(); //这里会阻塞等待连接接入 cmd中telnet 127.0.0.1 8888即代表连接 System.out.println("新客户端连接成功...."); InputStream inputStream = socket.getInputStream(); while(true) { byte[] data = new byte[1024]; System.out.println("正在等待数据..."); int len = inputStream.read(data); //这里会阻塞,等待数据,如果直接关闭cmd窗口会因为关闭socket通道导致len返回-1 cmd中ctrl+]进入到telnet操作模式send value发送数据 if (len != -1){ System.out.println(new String(data, 0,len, "GBK")); //用GBK是因为CMD窗口命令发送的数据是GBK编码 }else{ break; } } } //socket.close(); //serverSocket.close(); //System.out.println("服务器端关闭...."); } }
连接服务端
向服务端发送数据
再开一个cmd然后telnet发送数据,发现无反应。必须关闭第一个cmd才能连接成功。
线程池改进,能并发访问
用ExecutorService线程池实现每一个连接创建一个新的线程
import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class TCPServer { /*异步阻塞*/ public static void main(String[] args) throws IOException { ExecutorService threadPool = Executors.newCachedThreadPool(); //线程池 ServerSocket serverSocket = new ServerSocket(8888); System.out.println("服务端启动成功..."); while(true){ /*一次只能处理一个连接,在一个连接没关闭前无法接收第二个连接,在这里开一个框发送数无问题,开放两个框时会出现第二个无反应*/ System.out.println("等待客户端连接..."); final Socket socket = serverSocket.accept(); //这里会阻塞等待连接接入 cmd中telnet 127.0.0.1 8888即代表连接 threadPool.execute(new Runnable() { //启动一个线程 public void run() { try { System.out.println("新客户端连接成功...."); InputStream inputStream = socket.getInputStream(); while(true) { byte[] data = new byte[1024]; System.out.println("正在等待数据..."); int len = inputStream.read(data); //这里会阻塞,等待数据,如果直接关闭cmd窗口会因为关闭socket通道导致len返回-1 cmd中ctrl+]进入到telnet操作模式send value发送数据 if (len != -1){ System.out.println(Thread.currentThread()+new String(data, 0,len, "GBK")); //用GBK是因为CMD窗口命令发送的数据是GBK编码 }else{ System.out.println("break循环"); break; } } System.out.println("一次Socket连接关闭"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }); } //socket.close(); //serverSocket.close(); //System.out.println("服务器端关闭...."); } }
优点:传输质量好(所以BIO适合传输连接少但是数据量大的请求)
缺点:每一个连接都占用一个线程,很占用系统资源。
tip:所有的调优都关联到系统资源(IO、存储、内存、CPU)
原创文章,作者:Maggie-Hunter,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/17826.html