Java基础系列:Socket编程
2021-02-27 14:19
585 查看
俗世游子:专注技术研究的程序猿
说到前面的话
没有实战案例的理论基础都是在耍流氓,所以今天主要是想通过这里的案例能够让大家加深对之前的理解
本节我们会一步步实现一个点对点聊天小程序
Java中的Socket实现
InetAddress
InetAddress是Java对IP地址的封装,这个类是一个基础类,下面的
ServerSocket和
DatagramSocket都离不开这个类
InetAddress无法通过
new的方式来初始化,只能提供过其提供的静态方法来调用:
// 获取本地地址 InetAddress localHost = InetAddress.getLocalHost();
这里是
InetAddress的一些方法:
// 主机名:DESKTOP-ATG4KKE System.out.println("主机名:" + localHost.getHostName()); // IP地址:192.168.87.1 System.out.println("IP地址:" + localHost.getHostAddress()); // 是否正常:true System.out.println("是否正常:" + localHost.isReachable(5000));
这里是我测试时的输出,
关于
isReachable()的方法,用来检测该地址是否可以访问,由此我们可以做一些健康检查操作,比如:
// 通过主机IP或者域名来得到InetAddress对象 InetAddress inetAddress = InetAddress.getByName("192.168.87.139"); System.out.println("是否正常:" + inetAddress.isReachable(5000));
在5s之内尽最大可能尝试连接到主机,如果没有就认为主机不可用,这里受限于防火墙和服务器配置
当然,做健康检查这种方法还是low了点,生产环境中肯定不会这么干
PS: 生产环境的网络操作不会使用到这节里的东西,大部分情况下采用的都是Netty
ServerSocket
ServerSocket是服务端套接字,是基于
TCP/IP协议下的实现
初始化
通常我们这样来构建:
ServerSocket serverSocket = new ServerSocket(9999); ServerSocket serverSocket = new ServerSocket(); serverSocket.bind(new InetSocketAddress(9999));
这样就完成了服务端的初始化,并且将端口
9999绑定起来
等待连接
如果客户端想要和
ServerSocket建立连接,我们需要这么做
for(;;) { Socket socket = serverSocket.accpet(); // Socket[addr=/0:0:0:0:0:0:0:1,port=62445,localport=9999] System.out.println(socket); }
accpet()是侦听与
ServerSocket建立的连接,这个方法是一个阻塞方法,会一直等待连接接入进来
如果有连接接入进来,我们可以通过返回值来得到当前接入进来的
Socket
通信
在网络中传递数据其实也是按照
IO流的方式进行传递的,但是我们只能获取到字节流:
InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream();
InputStream读取数据,
OutputStream写出数据,这些基本操作我们在之前的IO流中都介绍过,这里就不再多说
这里我们为了能够提高效率,可以采用
包装流或者
处理流来处理,这前面也介绍过了
完整小例子
其实到这里,
ServerSocket的关键介绍也就完了,下面我们来做一个小例子:
- 当有客户端连接进来之后,给客户端返回:
Hello World
public class _ServerSocket { // 用来存储请求客户端和Socket之间的对应关系 static Map<String, Socket> MAP = new HashMap<>(); public static void main(String[] args) { try { ServerSocket serverSocket = new ServerSocket(); serverSocket.bind(new InetSocketAddress(9999)); for (; ; ) { String token = UUID.randomUUID().toString().replace("-", "").toLowerCase(); Socket socket = serverSocket.accept(); // 对应 MAP.put(token, socket); outHtml(socket); } } catch (IOException e) { e.printStackTrace(); } } public static void outHtml(Socket socket) { OutputStream outputStream = null; try { outputStream = socket.getOutputStream(); outputStream.write(("HTTP/1.1 200 OK\n\nHello World").getBytes("UTF-8")); } catch (IOException e) { e.printStackTrace(); } finally { if (null != outputStream) { try { outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
HTTP/1.1 200 OK\n\nHello World\n
这是HTTP协议下返回类型,前面是Response固定格式,
Hello World是真正返回的内容,这样我们的ServerSocket就能够通过浏览器来访问了
Socket
Socket属于客户端套接字,只有先和服务端套接字建立连接才能做其他的操作,
Socket的使用方式非常简单
建立连接
Socket socket = new Socket("127.0.0.1", 9999); // 验证是否连接成功 if (socket.isConnected()) { System.out.println("到服务端连接成功"); }
这是其中一种构造方法,更多情况下是采用这种方式
和服务端的连接建立成功之后,后续的操作就和
ServerSocket的
通信步骤一样了,这里就不再多废话了
下面用一个完整的例子来巩固一下
案例:TCP点对点聊天
服务端
public class Server { /** * 将客户端标识和socket关联起来 */ private static final Map<String, Socket> SOCKET_MAP = new HashMap<>(); /** * 反向关联,用来获取标识 */ private static final Map<Socket, String> SOCKET_TOKEN_MAP = new HashMap<>(); public static void main(String[] args) throws IOException { /** * 开启ServerSocket并监听9999端口 */ ServerSocket serverSocket = new ServerSocket(9999); for (;;) { /** * 等待客户端连接 */ Socket socket = serverSocket.accept(); /** * IO读取是阻塞式方法,所以需要开启新线程,这里可以优化成线程池 */ new Thread(() -> { try { saveToMap(socket); getClientMsg(socket); } catch (IOException e) { e.printStackTrace(); } }).start(); } } /** * 绑定SOCKET */ private static void saveToMap(Socket socket) throws IOException { String token = StringUtil.uuid(); SOCKET_MAP.put(token, socket); SOCKET_TOKEN_MAP.put(socket, token); System.out.println("---客户端连接成功,编号:" + token); System.out.println("当前用户:" + SOCKET_MAP.size()); /** * 因为没有登录,所以这里要告知客户端自己的标识 */ send(token, token, token); } /** * 获取客户端发送过来的消息,并发送出指定指定的客户端 */ private static void getClientMsg(Socket socket) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); String line = ""; while ((line = reader.readLine()) != null) { // 读取到一行以后,从这里发送出去 send(socket, line); } } /** * 发送消息 */ private static void send(Socket socket, String line) throws IOException { String[] s = line.split("#"); final String from = SOCKET_TOKEN_MAP.get(socket); send(s[0], s[1], from); } /** * 发送消息 * @param token * @param msg * @param from 这里在目标客户端展示 * @throws IOException */ private static void send(String token, String msg, String from) throws IOException { Socket sk = SOCKET_MAP.get(token); if (null == sk) return; String s = from + ":" + msg; System.out.println("---发送给客户端:" + s ); // 字符流输出 BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(sk.getOutputStream())); writer.write(s); writer.newLine(); writer.flush(); } }
客户端
public class Client { public static void main(String[] args) throws IOException { /** * 连接到服务端 */ Socket socket = new Socket("127.0.0.1", 9999); /** * 开新线程读取消息,可以优化 */ new Thread(() -> { try { BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); String line = ""; while (StringUtil.isNotBlank(line = reader.readLine())) { System.out.println(line); } } catch (IOException e) { e.printStackTrace(); } }).start(); /** * 从控制台写入消息并发送出去 */ Scanner scanner = new Scanner(System.in); while (scanner.hasNext()) { String next = scanner.next(); send(next, socket); } } private static void send(String msg, Socket socket) throws IOException { BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); writer.write(msg); writer.newLine(); writer.flush(); } }
代码已经通过测试,注释写的也非常清楚,大家可以尝试下,按照
标识#消息的格式就可以
点对点聊天了。
如果想要
群聊:
- 将Socket保存到集合中,然后循环集合就可以了,非常简单
好久没有用
Socket写聊天程序了,差点就放弃了下次改用
Netty来写,Netty比Socket方便多了
DatagramSocket
DatagramSocket是用于发送和接收数据报包的套接字,是基于
UDP协议下的实现。根据类中官方介绍:
数据报套接字是数据包传递服务的发送或接收点。 在数据报套接字上发送或接收的每个数据包都经过单独寻址和路由。 从一台机器发送到另一台机器的多个数据包可能会以不同的方式路由,并且可能以任何顺序到达
我们也能明白
UDP协议的特性。
DatagramPacket
该类表示
数据报包,在
DatagramSocket中传递和接收数据都是靠这个类来完成的,比如:
- 接收数据
byte[] buffer = new byte[1024]; DatagramPacket p = new DatagramPacket(buffer, buffer.length);
- 发送数据
DatagramPacket p = new DatagramPacket("123".getBytes(), "123".getBytes().length, InetAddress.getByName("localhost"), 9999);
发送数据出去,
DatagramPacket需要指定接收端的IP和端口,这样才能够发送出去
下面我们来看看具体如何用
初始化
DatagramSocket socket = new DatagramSocket(9999); DatagramSocket s = new DatagramSocket(null); s.bind(new InetSocketAddress(9999));
两种方式都可以完成初始化,没有什么区别
接收消息
byte[] buffer = new byte[1024]; DatagramPacket p = new DatagramPacket(buffer, buffer.length); socket.receive(p); System.out.println(new String(p.getData(), 0, p.getLength()));
根据
DatagramPacket的接收参数,构造出来一个
byte[],然后调用
receive(),这样消息就接收到了
receive()是一个阻塞方法,只有等有消息的时候才会继续执行
发送消息
DatagramPacket p = new DatagramPacket("123".getBytes(), "123".getBytes().length, InetAddress.getByName("localhost"), 9999); socket.send(p);
构造发送数据包,然后调用
send()方法就可以完成数据包的发送
UDP不需要连接,直接通过IP+PORT的方式就可以发送数据
案例:UDP聊天
public class _DatagramPacket { public static void main(String[] args) throws IOException { // 从命令行得到需要绑定的端口和发送数据的端口 DatagramSocket datagramSocket = new DatagramSocket(Integer.parseInt(args[0])); System.out.println("已启动"); new Thread(() -> { byte[] buffer = new byte[1024]; DatagramPacket p = new DatagramPacket(buffer, buffer.length); try { for (;;) { // 构建接收数据 datagramSocket.receive(p); System.out.println(p.getPort() + ":" + new String(buffer, 0, p.getLength())); } } catch (IOException e) { e.printStackTrace(); } }).start(); Scanner scanner = new Scanner(System.in); DatagramPacket p = new DatagramPacket(new byte[0], 0, new InetSocketAddress("127.0.0.1", Integer.parseInt(args[1]))); while (scanner.hasNext()) { String next = scanner.next(); // 构建发送数据包 p.setData(next.getBytes()); datagramSocket.send(p); } }
有瑕疵,空格会换行,这里交给大家去修改了
最后的话
到这里,关于
Socket编程方面的东西就聊完了,没有介绍很多的API方法,这些在用到的时候再看也是一样的。
以下是
java.net所在的目录文档:
相关文章推荐
- Java基础:三步学会Java Socket编程
- Java基础:三步学会Java Socket编程
- JAVA设计模式系列之Adapter(适配器)-Java基础-Java-编程开发
- 黑马程序员——java基础——Socket网络编程
- JAVA套接字(Socket)101七天系列—第二天【套接字基础】 .
- java socket编程基础(转)
- Java网络编程基础(二)-- 基于TCP/IP的Socket编程
- 面向基础系列之---Java网络编程---网络连接组件的使用(URL与URI) 3ff0
- 第3章 初识java开发 (阿里云大学在线视频0基础java10系列-编程入门)
- Java基础:三步学会Java Socket编程
- java网络编程基础——UDP通信之DatagramSocket
- java网络编程基础Socket通信应用
- Java基础 网络编程 Socket UDP TCP URL
- Java基础--Socket网络编程
- Java系列-Socket网络编程,TCP/IP和Http等网络协议理解
- java基础(16)- 网络编程—socket
- java网络编程基础夯实07-基于TCP/UDP的Socket编程(单线程)
- Java基础系列篇:JAVA多线程 并发编程
- 基于Socket的Java网络编程集粹-Java基础-Java-编程开发
- Java基础:三步学会Java Socket编程-Java基础-Java-编程开发