作者:zskingking 博客:https://www.jianshu.com/p/b04930d2b85e 传输层协议TCP、UDP,但它们毕竟只是协议,看不见摸不着,那我们怎们通过TCP、和UDP进行实际传输呢?不用着急,等看完这篇文章你一定会明白的。 Socket中文意思为插座的意思,专业术语称之为套接字,它把TCP/IP封装成了调用接口供开发者调用,也就是说开发者可以通过调用Socket相关API来实现网络通讯。在Java中也存在Socket相关API,主要分为两个,分别是基于UDP传输协议的Socket和基于TCP传输协议的Socket,本篇文章会对基于这两种传输协议的Socket进行详细描述。 通过上节的内容我们知道UDP是无连接的,只要提供对方的IP地址和端口号就能进行数据的传输,其中IP负责定位主机端口负责定位应用。知道了目标IP和目标端口号通过Java中的UDP Socket就能进行IO传输,我们来看一下具体的代码体现 ** * 发送方UDP */ public class UDPSocketSend { public static void main(String[] args) throws IOException {
System.out.println('Sender Start...');
//1.创建socket服务 DatagramSocket ds = new DatagramSocket();
//2.封装数据 String str = 'Did you recite words today'; byte[] bytes = str.getBytes(); //地址 InetAddress address =InetAddress.getByName('192.168.31.137'); //参数:数据、长度、地址、端口 DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,6666);
//3.发送数据包 ds.send(dp);
//4.关闭socket服务 ds.close(); }
/** * 接收方UDP */ public class UDPSocketReceive{ public static void main(String[] args) throws IOException {
System.out.println('Receiver Start...');
//1.创建udp的socket服务,并声明端口号 DatagramSocket ds = new DatagramSocket(6666);
//2.创建接收数据的数据包 byte[] bytes = new byte[1024]; DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
//3.将数据接收到数据包中,为阻塞式方法 ds.receive(dp);
//4.解析数据 InetAddress address = dp.getAddress();//发送方IP int port = dp.getPort();//发送方端口 String content = new String(dp.getData(),0,dp.getLength()); System.out.println('address:'+address+'---port:'+port+'---content:'+content);
//关闭服务 ds.close(); } }
分别启动发送方和接收方,我们来看一下打印结果 发送方:
接收方: Receiver Start... address:/192.168.31.137---port:65037---content:Did you recite words today
成功接收到消息,并打印出发送方IP和端口,下面我来个大家撸一遍步骤
发送方: 接收方: 整个UDP发送数据的流程就是这样 注意点: 把上面的例子进行一些小改动就可以实现聊天功能 public class UDPSocket1 { public static void main(String[] args) { try { new Thread(new Runnable() { @Override public void run() { try { receive(); } catch (IOException e) { e.printStackTrace(); } } }).start(); send(); } catch (IOException e) { e.printStackTrace(); } }
//接收消息方法 private static void receive() throws IOException { System.out.println('UDOSocket1 Receiver Start...');
//1.创建udp的socket服务,并声明端口号 DatagramSocket ds = new DatagramSocket(6666); //无限循环,一直处于接收状态 while (true) { //2.创建接收数据的数据包 byte[] bytes = new byte[1024]; DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
//3.将数据接收到数据包中 ds.receive(dp);
//4.解析数据 String content = new String(dp.getData(), 0, dp.getLength()); System.out.println('UDPSocket1 Receive:' + content); } }
private static void send() throws IOException { //1.创建socket服务 DatagramSocket ds = new DatagramSocket();
//将键盘输入的信息转换成输入流再放入到缓冲区 BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String line = null; while ((line=br.readLine())!=null){ //2.封装数据 byte[] bytes = line.getBytes(); //地址 InetAddress address =InetAddress.getByName('192.168.31.137'); //参数:数据、长度、地址、端口 DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,7777);
//3.发送数据包 ds.send(dp); }
//4.关闭socket服务 ds.close(); } }
public class UDPSocket2 { public static void main(String[] args){ try { new Thread(new Runnable() { @Override public void run() { try { receive(); } catch (IOException e) { e.printStackTrace(); } } }).start(); send(); } catch (IOException e) { e.printStackTrace(); } }
//接收消息方法 private static void receive() throws IOException { System.out.println('UDOSocket2 Receiver Start...');
//1.创建udp的socket服务,并声明端口号 DatagramSocket ds = new DatagramSocket(7777); //无限循环,一直处于接收状态 while (true) { //2.创建接收数据的数据包 byte[] bytes = new byte[1024]; DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
//3.将数据接收到数据包中 ds.receive(dp);
//4.解析数据 String content = new String(dp.getData(), 0, dp.getLength()); System.out.println('UDPSocket2 Receive:' + content); } //关闭服务 // ds.close(); }
private static void send() throws IOException { //1.创建socket服务 DatagramSocket ds = new DatagramSocket();
//将键盘输入的信息转换成输入流再放入到缓冲区 BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String line = null; while ((line=br.readLine())!=null){ //2.封装数据 byte[] bytes = line.getBytes(); //地址 InetAddress address =InetAddress.getByName('192.168.31.137'); //参数:数据、长度、地址、端口 DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,6666);
//3.发送数据包 ds.send(dp); }
//4.关闭socket服务 ds.close(); } }
在接收方方法写一个无限循环让其处于持续接收状态,发送发通过对键盘录入加回车进行消息的发送,并且两个端点都具备发送和接收功能。需要注意的是receive()会执行一个无限循环,所以receive()和send()必须位于不同线程,否则会导致无法发送消息从而也接收不到消息。 来看打印结果: UDPSocket1 UDPSocket Receiver Start... UDPSocket Receive:hello udp1 heelo udp2
UDPSocket2 UDPSocket2 Receiver Start... hello udp1 UDPSocket2 Receive:hello udp2
我在UDPSocket1 的控制台中输入了:“hello udp2”、在UDPSocket2 的控制台中输入了:“hello udp1”,接收内容和发送内容完全一致,一个简单的聊天功能就实现了,希望不熟悉的朋友也可以敲一遍代码练一遍。 TCP基于client-server是面向连接的可靠传输,上篇文章我们也讲解了TCP安全传输机制,可谓是相当复杂,如果需要个人对TCP协议进行封装显然大多数开发者是很难实现的,所以Java也为开发者提供了基于TCP的Socket,不同于UDP,TCP Socket分为Socket和ServerSocket对应着client和server,下面我来用代码实现一个简单的TCP通讯功能: 客户端:
//客户端 public class TCPClient { public static void main(String[] args) throws IOException {
//1.创建TCP客户端Socket服务 Socket client = new Socket(); //2.与服务端进行连接 InetSocketAddress address = new InetSocketAddress('192.168.31.137',10000); client.connect(address); //3.连接成功后获取客户端Socket输出流 OutputStream outputStream = client.getOutputStream(); //4.通过输出流往服务端写入数据 outputStream.write('hello server'.getBytes()); //5.关闭流 client.close(); } }
首先创建一个Socket和InetSocketAddress ,然后通过Socket的connect()方法进行连接,连接成功后可以获取到输出流,通过该输出流就可以向服务端传输数据。 服务端: public class TCPServer { public static void main(String[] args) throws IOException { //1.创建服务端Socket并明确端口号 ServerSocket serverSocket = new ServerSocket(10000); //2.获取到客户端的Socket Socket socket = serverSocket.accept(); //3.通过客户端的Socket获取到输入流 InputStream inputStream = socket.getInputStream(); //4.通过输入流获取到客户端传递的数据 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String line = null; while ((line = bufferedReader.readLine())!=null){ System.out.println(line); }
//5.关闭流 socket.close(); serverSocket.close(); }
首先创建一个服务端Socket并明确端口号,通过accept()方法获取到链接过来的客户端Socket,从客户端Socket中获取输入流,最后由输入流读取客户端传输来的数据。 我们来看看服务端的打印结果:
成功接收到客户端发来的数据 注意点: 流程:客户端上传一个文件到服务端,服务端收到文件后进行保存,保存成功后给客户端一个响应。 客户端代码
public class TCPUploadClient {
public static void main(String[] args) throws IOException {
//1.创建TCP客户端Socket服务 Socket client = new Socket();
//2.与服务端进行连接 InetSocketAddress address = new InetSocketAddress('192.168.31.137',10001); client.connect(address);
//3.读取客户端文件 FileInputStream fis = new FileInputStream('E://girl.jpg');
//4.获取输出流 OutputStream outputStream = client.getOutputStream();
//5.将文件写入到服务端 byte[] bytes = new byte[1024]; int len = 0; while ((len = fis.read(bytes))!=-1){ outputStream.write(bytes,0,len); }
//6.通知服务器数据写入完毕 client.shutdownOutput();
//7.读取服务端响应的数据 InputStream inputStream = client.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)); String line = br.readLine(); System.out.println(line);
//8.关闭流 inputStream.close(); fis.close(); client.close(); } }
创建Socket并连接,读取本地文件输入流,将文件写入到服务端,写入成功后读取服务端返回的数据。 服务端代码 public class TCPUploadServer { public static void main(String[] args) throws IOException {
//1.创建客户端Socket ServerSocket serverSocket = new ServerSocket(10001); //2.获取到客户端Socket Socket socket = serverSocket.accept(); //3.通过客户端Socket获取到输入流 InputStream is = socket.getInputStream(); //4.将流以文件的形式写入到磁盘 File dir = new File('F://tcp'); //如果文件夹不存在就创建文件夹 if(!dir.exists()){ dir.mkdirs(); } File file = new File(dir,'girl.jpg'); FileOutputStream fos = new FileOutputStream(file); byte[] bytes = new byte[1024]; int len = 0; while ((len = is.read(bytes))!=-1){ fos.write(bytes,0,len); } //5.通知客户端文件保存完毕 OutputStream os = socket.getOutputStream(); os.write('success'.getBytes()); //6.关闭流 fos.close(); os.close(); socket.close(); } }
创建Socket并获取到客户端Socket和输入流,在F盘的tcp目录下创建一个文件,将从客户端读取到的数据输出到文件中,保存成功后给客户端返回''success'' 这样我们就实现了一哥客户端上传文件的功能,还是比较简单的,希望大家也能够跟着代码敲一遍。 细心的同学可能已经发现,上面我们的实例中一个服务端只能接收一个客户端的一次请求,这显然是不符合实际的,那么如何使一个服务端同时服务多个客户端呢?接着撸代码 //客户端1 public class TCPClient1 { public static void main(String[] args) throws IOException { System.out.println('TCPClient1 Start...'); //1.创建TCP客户端Socket服务 Socket client = new Socket();
//2.与服务端进行连接 InetSocketAddress address = new InetSocketAddress('192.168.31.137',10004); client.connect(address);
//3.连接成功后获取客户端Socket输出流 OutputStream outputStream = client.getOutputStream();
//4.通过输出流往服务端写入数据 outputStream.write('Hello my name is Client1'.getBytes());
//5.告诉服务端发送完毕 client.shutdownOutput();
//6.读取服务端返回数据 InputStream is = client.getInputStream(); byte[] bytes = new byte[1024]; int len = is.read(bytes); System.out.println(new String(bytes,0,len));
//7.关闭流 client.close(); } }
//客户端2 public class TCPClient2 {
public static void main(String[] args) throws IOException { System.out.println('TCPClient2 Start...');
//1.创建TCP客户端Socket服务 Socket client = new Socket();
//2.与服务端进行连接 InetSocketAddress address = new InetSocketAddress('192.168.31.137',10004); client.connect(address);
//3.连接成功后获取客户端Socket输出流 OutputStream outputStream = client.getOutputStream();
//4.通过输出流往服务端写入数据 outputStream.write('Hello my name is Client2'.getBytes());
//5.告诉服务端发送完毕 client.shutdownOutput();
//6.读取服务端返回数据 InputStream is = client.getInputStream(); byte[] bytes = new byte[1024]; int len = is.read(bytes); System.out.println(new String(bytes,0,len));
//7.关闭流 client.close(); } } //服务端 public class TCPServer { public static void main(String[] args) throws IOException { receive(); }
private static void receive() throws IOException { System.out.println('Server Start...'); //创建服务端Socket并明确端口号 ServerSocket serverSocket = new ServerSocket(10004);
while (true){ //获取到客户端的Socket Socket socket = serverSocket.accept(); //通过客户端的Socket获取到输入流 InputStream is = socket.getInputStream();
//通过输入流获取到客户端传递的数据 byte[] bytes = new byte[1024]; int len = is.read(bytes); System.out.println(new String(bytes,0,len));
//将客户端发来的数据原封不动返回 OutputStream os = socket.getOutputStream(); os.write(new String(bytes,0,len).getBytes()); //关闭连接 socket.close(); } } }
客户端1控制台 TCPClient1 Start... Hello my name is Client1
客户端2控制台 TCPClient2 Start... Hello my name is Client2
这样就可以实现一个服务端服务多个客户端 细心的同学可能又发现了,上面的写法是存在问题的,由于服务端始终都在主线程中处理请求,所以客户端的请求需要被服务端排队处理,举个例子:Client1对服务端进行了一次请求,服务端在响应Client1之前是不会接受其他请求的,显然这是不符合逻辑的,真正的服务器是要具备并发处理的。而多线程恰好能解决这个问题,我们来看修改之后的服务端代码 public class TCPServer { public static void main(String[] args) throws IOException { receive(); }
private static void receive() throws IOException { System.out.println('Server Start...');
//创建服务端Socket并明确端口号 ServerSocket serverSocket = new ServerSocket(10004); while (true){ //获取到客户端的Socket final Socket socket = serverSocket.accept(); //通过线程执行客户端请求 new Thread(new Runnable() { @Override public void run() { try { //通过客户端的Socket获取到输入流 InputStream is = socket.getInputStream();
//通过输入流获取到客户端传递的数据 byte[] bytes = new byte[1024]; int len = is.read(bytes); System.out.println(new String(bytes,0,len));
//将客户端发来的数据原封不动返回 OutputStream os = socket.getOutputStream(); os.write(new String(bytes,0,len).getBytes()); //关闭连接 socket.close(); } catch (IOException e) { e.printStackTrace(); } } }).start();
} } }
运行效果适合加线程之前是一样的,但这种方式效率更高。
本篇文章叙述了基于UDP和TCP的Socket,UDP是无连接的,所以UDP Socket在发送数据的时候只需要目标IP和端口即可发送。TCP是面向连接的并且是基于client-server模式,在传输数据前需要进行连接,可以通过多线程技术实现并发处理客户端请求。本篇文章内容还是比较简单的,希望大家能把文章中代码自己敲一遍,掌握Socket的同时也能让你自己UDP和TCP的理解更加深刻。 推荐:《Python数据分析与大数据处理从入门到精通》 编辑推荐:通过核心概念剖析、45个“新手问答”、17个章节的“实训”、3个项目综合实战、50道Python面试题精选,教你轻松玩转数据分析与大数据处理。 如何购买:点击小程序购买或阅读全文了解更多,也可坚持留言打卡获得! 如何赠送:留言集赞数大于30赞且排名第一的同学赠送一本,集赞截止时间2019.11.19 23:00,定价89RMB。 觉得本文对你有帮助?请分享给更多人
关注「全栈开发者社区」加星标,提升全栈技能 本公众号会不定期给大家发福利,包括送书、学习资源等,敬请期待吧! 如果感觉推送内容不错,不妨右下角点个在看转发朋友圈或收藏,感谢支持。 好文章,我在看❤️
|