Java Socket

Wesley13
• 阅读 958

1. 套接字介绍

套接字是介于传输层(TCP/UDP)和应用层(HTTP/FTP)之间的一个抽象,被应用程序调用;

在java环境中套接字编程主要使用TCP/IP协议,但是套接字支持的协议族远不止这些;

在java套接字编程中有Socket和ServerSocket两个核心类,ServerSocket位于服务器端监听连接,Socket位于客户端发起连接,服务器端监听到连接后也会产生一个Socket实例来完成与对端Socket的通信,并且服务器端的Socket和客户端的Socket没有任何区别,也就是说进程间通信需要一对套接字完成;

2. API介绍

Socket和ServerSocket是java提供网络编程的门面接口,实际上都是通过SocketImpl及子类完成的;

2.1 重要类关系图

Java Socket

2.2 重要类介绍

1. InetAddress

InetAddress是java对IP地址的封装,包括IP设备的名称、ip地址,有Inet4Address和Inet6Address两个子类;

没有公开的构造方法,只能通过公开的静态方法实例化对象

方法介绍

1)getByName:实例化一个InetAddress对象,getByName的参数可以是ip地址字符串,也可以是主机名

2)anyLocalAddress:实例化一个表示任何本地地址的网络地址,通常是0.0.0.0

3)getHostAddress一般通过InetAddress的获取主机ip地址串,通过获取主机名

4)getHostName

2. SocketAddress/InetSocketAddress

套接字地址,用来表示一个套接字在网络中的位置;

SocketAddress不依赖任何协议的套接字地址;

InetSocketAddress表示一个IP套接字地址,由套接字地址的 IP地址(InetAddress)、主机名和端口号组成

3. ServerSocket

服务器套接字,在服务器端用于监听网络传入

构造方法

1)ServerSocket():创建一个不绑定任何ip和端口的服务器套接字

创建一个SocksSocketImpl赋值给ServerSocket的SocketImpl,并把自己赋值给SocketImpl的ServerSocket

2)ServerSocket(port, backlog, InetAddress):用指定的端口、连接大小、ip地址创建一个服务器套接字

同无参构造,创建SocksSocketImpl并相互赋值

根据port和InetAddress创建套接字地址InetSocketAddress(InetAddress, port),如果InetAddress为null则通过InetAddress的anyLocalAddress实例化一个IP地址,如果port不合法则抛出异常;

通过调用bind(SocketAddress, backlog)将服务器套接字和套接字地址进行绑定;

backlog说明https://blog.csdn.net/oyueyang1/article/details/80451535

方法介绍

1)bind(SocketAddress, backlog):将服务器套接字和套接字地址进行绑定,tcp accept队列大小为backlog

如果SocketAddress为null则新建一个InetSocketAddress(InetAddress.anyLocalAddress, 0),0表示让系统随机分配端口号

实际上通过SocketImpl的bind(InetAddress, port)绑定的,SocketImpl是在构造函数中创建并绑定到当前类的

有参构造方法在构造过程都进行了bind操作,所以此方法一般与无参构造方法同时使用

2)isBound、getInetAddress、getLocalPort、getLocalSocketAddress查询bind方法的绑定信息

3)accept:阻塞式的监听到此服务器套接字连接,监听到连接后会创建一个Socket套接字,服务端和客户端各有一个Socket,2个套接字可以完成不同进程间通信;

4)implAccept(Socket):为保护方法,当我们自定义ServerSocket需要复写accept方法时,需要新建一个Socket然后通过implAccept加工,accept的阻塞其实是阻塞在implAccept的加工过程

5)getChannel:获取ServerSocketChannel通道

6)close/isClose:关闭套接字/判断套接字是否已关闭

关闭服务器套接字后,阻塞在accept的线程会抛出SocketException;

ServerSocketChannel通道关闭;

注意:没有直接方法判断对端套接字是否关闭

7)getSoTimeout/setSoTimeout: 设置accept的阻塞时间,0时表示无穷大,如果超时抛出SocketTimeOutException;

间接调用SocketImple的getOption(capId)/setOption(capId,Object), capId为SO_TIMEOUT(SocketOption中定义);

8)setSocketFactory(SocketImplFactory):设置套接字实现工厂

构造方法中会创建一个SocketImpl,默认是创建SocksSocketImpl,如果已设置SocketImplFactory则会调用工厂方法创建一个

4. Socket

套接字或客户端套接字,不同进程通信的端点,服务器端会有n多个客户端套接字

构造方法

1)Socket():创建一个不绑定任何套接字地址,不连接任务服务器套接字的套接字

创建一个SocksSocketImpl赋值给Socket的SocketImpl,并把自己赋值给SocketImpl的Socket, 同ServerSocket的无参构造方法

2)Socket(SocketAddress serverAddress, SocketAddress localAddress, boolean stream):创建套接并绑定和连接到指定套接字地址

绑定到本地套接字地址localAddress,连接的服务器套接字地址为serverAddress;

如果localAddress为null则内核随机选择一个本地ip地址和端口进行绑定;

stream表示流套接字(tcp),负责数据包datagram套接字(udp,每个包有大小限制)

3)Socket(InetAddress server, int serverPort, InetAddress local, int localPort):创建套接并绑定和连接到指定套接字地址

将server和serverPort封装成服务器套接字SocketAddress,local和localPort封装成客户端套接字SocketAddress,然后调用Socket(SocketAddress, SocketAddress, stream)创建流式套接字

4)Socket(String host, int port):

将host和port封装成服务器端套接字地址SocketAddress;

本地SocketAddress为null则,本地端口随机,任意一本机ip;

调用Socket(SocketAddress, SocketAddress, stream)创建流式Socket;

方法介绍

1)connect(SocketAddress)/connect(SocketAddress, timeout): 连接到套接字地址指定的服务器套接字

有参数构造方法中都会调用connect方法,所以此方法一般与无参构造方法同时使用

2)bind(SocketAddress) :将套接字和套接字地址进行绑定

如果已绑定则异常;

如果没有绑定在connect方法中会进行绑定,所以如果要调用bind方法需要在connect前调用;

3)getInetAddress()/getPort()/getRemoteSocketAddress(): 获取 对端 套接字的IP地址、端口号、套接字地址,没有连接则返回null

4)getLocalAddress()/getLocalPort()/getLocalSocketAddress():获 此 套接字的IP地址、端口号、套接字地址

5)getChannel():返回此套接字的SocketChannel通道

6)getInputStream():返回套接字的输入流

如果连接正常,inputstream的read会一直阻塞;

关闭输入流会关闭对应的套接字;

当Socket底层的连接中断时,套接字已缓存的字节可以通过read读取,如果已经消耗了所有缓存的字节,read操作会抛出IOException,套接字上没有任何缓存字节,并且套接字没有关闭调用 available返回0

7)getOutputStream():返回套接字的输出流

关闭输出流会关闭对应的套接字

8)setSoTimeout(timeout):Socket关联的InputStream的read操作默认是阻塞的,如果设置超时值,read操作超时引发SocketTimeoutException

实际上通过SocketImpl的setOption(optId)实现, optId为SO_TIMEOUT(SocketOptions中定义)

9)close:关闭套接字

阻塞于当前套接字的IO操作会抛出SocketException;

关闭后不能重新连接和重新绑定(已建立的连接不能用),只能通过新建;

会关闭此套接字的输入输出流;

关闭关联的通道;

10)shutdownInput:丢弃发送到输入流的所有字节,读取内容返回EOF

11)shutdownOutput:禁用输出流,以前发送的字节都会被底层发送,如果关闭后执行写入会抛出IOException

12)isBound/isClose/isConnect/isInputShutdown/isOutputSHutdown:用于判断相应的操作是否结束

13)setSocketImplFactory:设置套接字实现的工厂,构造方法默认会创建一个SocksSocketImpl,如果指定工厂会通过工厂创建

3. 实例

工具类:SocketUtil

Java Socket Java Socket

 1 import java.net.InetSocketAddress;
 2 import java.net.SocketAddress;
 3 
 4 /**
 5  * 工具类
 6  */
 7 public class SocketUtil {
 8 
 9     /**
10      * 解析套接字地址中的ip和端口
11      */
12     public static String getSocketAddress(SocketAddress socketAddress) {
13 
14         if (socketAddress instanceof InetSocketAddress) {
15             InetSocketAddress inetSocketAddress = ((InetSocketAddress) socketAddress);
16             int port = inetSocketAddress.getPort();
17             String hostName = inetSocketAddress.getHostName();
18             return String.format("%s:%s", hostName, port);
19         }
20 
21         return null;
22     }
23 }

View Code

服务器类:DemoTcpServer

Java Socket Java Socket

 1 import java.io.*;
 2 import java.net.*;
 3 import java.util.*;
 4 import java.util.concurrent.ExecutorService;
 5 import java.util.concurrent.Executors;
 6 
 7 /**
 8  * 群聊服务器:启动服务器套接字,监听并处理连接
 9  */
10 public class DemoTcpServer {
11 
12     static final List<Socket> ONLINE_SOCKET = new ArrayList();
13 
14     public static void main(String[] args) throws IOException {
15 
16         // 创建服务器套接字,并绑定到本机的41440端口监听
17         ServerSocket serverSocket = new ServerSocket(41440, 50, InetAddress.getByName("192.168.1.101"));
18         System.out.println("服务器started...");
19 
20         // 创建线程池用于处理已连接的socket
21         ExecutorService executorService = Executors.newFixedThreadPool(10);
22         while (true) {
23             // 等待socket接入,有连接接入后会创建一个Socket与对端Socket通信,此Socket地址ip为本机、端口随机
24             Socket socket = serverSocket.accept();
25             String socketAddress = SocketUtil.getSocketAddress(socket.getRemoteSocketAddress());
26             System.out.println(socketAddress + " 上线了...");
27             // 加入到在线的socket列表,用于群发消息
28             ONLINE_SOCKET.add(socket);
29             // 单独起一个线程监听消息,可以改成nio的Selectors实现
30             executorService.submit(new SocketHandler(socket));
31         }
32     }
33 }
34 
35 /**
36  * 处理Socket的任务
37  */
38 class SocketHandler implements Runnable {
39 
40     private Socket socket;
41 
42     public SocketHandler(Socket socket) {
43         this.socket = socket;
44     }
45 
46     @Override
47     public void run() {
48         try {
49             String clientFlag = SocketUtil.getSocketAddress(this.socket.getRemoteSocketAddress());
50             byte[] input = new byte[1024];
51             // 循环监听消息
52             while (true) {
53                 // 阻塞式读取消息
54                 int read = this.socket.getInputStream().read(input);
55                 // 服务器记录发送的所有消息
56                 System.out.println(clientFlag + " " + new String(input, 0, read, "utf-8"));
57                 // 群发消息
58                 sendMsg(this.socket, clientFlag, new String(input, 0, read, "utf-8"));
59             }
60         } catch (IOException e) {
61             System.out.println(SocketUtil.getSocketAddress(socket.getRemoteSocketAddress()) + "下线...");
62             DemoTcpServer.ONLINE_SOCKET.remove(socket);
63         }
64     }
65 
66     /**
67      * 群发消息
68      */
69     private void sendMsg(Socket ignoreSocket, String clientFlag, String msg) {
70 
71         Iterator<Socket> iterator = DemoTcpServer.ONLINE_SOCKET.iterator();
72         while (iterator.hasNext()) {
73 
74             Socket currentSocket = iterator.next();
75 
76             // 不需要向此Socket对端发送消息,只有一个客户端时需要注解此判断
77             if (currentSocket.equals(ignoreSocket)) {
78                 continue;
79             }
80 
81             // 发送消息到客户端
82             try {
83                 currentSocket.getOutputStream().write((clientFlag + System.lineSeparator() + msg).getBytes("utf-8"));
84             } catch (Exception e) {
85                 System.out.println(SocketUtil.getSocketAddress(currentSocket.getRemoteSocketAddress()) + "下线...");
86                 iterator.remove();
87             }
88         }
89     }
90 }

View Code

客户端类:

Java Socket Java Socket

 1 import java.io.IOException;
 2 import java.net.InetSocketAddress;
 3 import java.net.Socket;
 4 import java.util.Scanner;
 5 
 6 /**
 7  * 客户端
 8  */
 9 public class DemoTcpClient {
10     public static void main(String[] args) throws Exception {
11 
12         //以下3步可以通过有参构造方法一次指定
13         // 创建客户端socket
14         Socket socket = new Socket();
15         // 绑定到本地的ip和端口号
16         socket.bind(new InetSocketAddress("192.168.1.101", 41441));
17         // 连接到远程的套接字服务器
18         socket.connect(new InetSocketAddress("192.168.1.101", 41440), 0);
19         System.out.println("已上线...");
20 
21         // 新启动线程用于回显消息
22         new Thread(() -> {
23             try {
24                 byte[] input = new byte[1024];
25                 while (true) {
26                     int read = socket.getInputStream().read(input);
27                     System.out.println(new String(input, 0, read, "utf-8"));
28                 }
29             } catch (IOException e) {
30                 e.printStackTrace();
31             }
32         }).start();
33 
34         //处理输入
35         Scanner scanner = new Scanner(System.in);
36         while (scanner.hasNext()) {
37             socket.getOutputStream().write(scanner.nextLine().getBytes("utf-8"));
38             socket.getOutputStream().flush();
39         }
40     }
41 }

View Code


参考文献:

1. https://droidyue.com/blog/2015/03/08/sockets-programming-in-java/
  2. https://my.oschina.net/leejun2005/blog/104955
  3. https://www.cnblogs.com/w-wfy/p/6415840.html

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
Java如何在指定端口创建套接字?
在Java编程中,如何在指定端口创建套接字并连接到指定服务器的端口?下面的例子演示了Socket类的Socket构造函数,并且使用getLocalPort(),getLocalAddress(),getInetAddress()以及getPort()方法获取Socket细节。packagecom.yiibai;
Stella981 Stella981
3年前
2018.4.28 基于java的聊天系统(带完善)
Java聊天系统1.Socket类Socket(InetAddressaddress,intport)创建一个流套接字并将其连接到指定IP地址的指定端口号。Socket(Stringhost,intport)创建一个流套接字并将其连接到指定主机上的指定端口号。
Stella981 Stella981
3年前
Python网络编程—TCP客户端和服务器
Python网络编程—TCP客户端和服务器客户端importsocket'''客户端创建步骤:1、创建网络套接字2、连接到目标IP地址和端口3、收发数据4、关闭套接字'''IPso
可莉 可莉
3年前
2018.4.28 基于java的聊天系统(带完善)
Java聊天系统1.Socket类Socket(InetAddressaddress,intport)创建一个流套接字并将其连接到指定IP地址的指定端口号。Socket(Stringhost,intport)创建一个流套接字并将其连接到指定主机上的指定端口号。
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这