俗世游子:专一技术研究的程序猿html
没有实战案例的理论基础都是在耍流氓,因此今天主要是想经过这里的案例可以让你们加深对以前的理解java
本节咱们会一步步实现一个点对点聊天小程序编程
InetAddress
是Java对IP地址的封装,这个类是一个基础类,下面的ServerSocket
和DatagramSocket
都离不开这个类小程序
InetAddress
没法经过new
的方式来初始化,只能提供过其提供的静态方法来调用:api
// 获取本地地址 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了点,生产环境中确定不会这么干oracle
PS: 生产环境的网络操做不会使用到这节里的东西,大部分状况下采用的都是Netty
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 = new Socket("127.0.0.1", 9999); // 验证是否链接成功 if (socket.isConnected()) { System.out.println("到服务端链接成功"); }
这是其中一种构造方法,更多状况下是采用这种方式
和服务端的链接创建成功以后,后续的操做就和ServerSocket
的通讯步骤
同样了,这里就再也不多废话了
下面用一个完整的例子来巩固一下
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
写聊天程序了,差点就放弃了下次改用
Netty
来写,Netty
比Socket
方便多了
DatagramSocket
是用于发送和接收数据报包的套接字,是基于UDP协议
下的实现。根据类中官方介绍:
数据报套接字是数据包传递服务的发送或接收点。 在数据报套接字上发送或接收的每一个数据包都通过单独寻址和路由。 从一台机器发送到另外一台机器的多个数据包可能会以不一样的方式路由,而且可能以任何顺序到达
咱们也能明白UDP协议
的特性。
该类表示数据报包
,在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的方式就能够发送数据
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
所在的目录文档: