0%

Java Socket 编程

Java Socket 编程

基础

首先需要了解两个类:Socket和SocketServer。

SocketServer

SocketServer实现了服务器套接字。 服务器套接字会等待通过网络进入的请求。 用户可以根据该请求执行某些操作,然后将结果返回给请求者(也可以选择不返回)。

构造方法

1
public ServerSocket (int port, int backlog,InetAddress bindAddr)

创建具有指定端口的服务器,监听backlog和要绑定的本地IP地址。

参数

  • port - 绑定的本地端口号。 端口号0表示从临时端口范围自动分配端口号,可以通过调用getLocalPort来查看此端口号。
  • backlog - 传入连接指示(连接请求)的最大队列长度,如果队列已满时连接指示到达,则拒绝连接。
  • bindAddr - 服务器将绑定到的本地InetAddress, 如果bindAddr为null,它将默认接受任何/所有本地地址上的连接。

常用方法

  • accept()侦听对此套接字的连接并接受它。 该方法将阻塞,直到建立连接,成功建立连接后会返回一个新的Socket

  • close()关闭此套接字

Socket

该类实现客户端套接字(一般简称为“套接字”)。 套接字是两台机器之间通信的端点。

构造方法

1
public Socket(String host,int port) throws UnknownHostException,IOException

创建流套接字并将其连接到指定主机上的指定端口号。

常用方法

  • getInputStream()返回此套接字的输入流,注意关闭返回的InputStream将关闭关联的套接字。

  • getOutputStream()返回此套接字的输出流,关闭返回的OutputStream将关闭关联的套接字。

  • close()关闭此套接字,关闭此套接字也将关闭套接字InputStreamOutputStream 。一旦套接字关闭,进一步的网络连接就需要创建一个新的套接字。

  • shutdownInput()将此套接字的输入流设置为“流结束”。 发送到套接字输入流端的任何数据都会被确认,然后以静默方式丢弃。套接字输入流read方法将返回-1 (流结束)。

  • shutdownOutput()禁用此套接字的输出流。 对于TCP套接字,将发送任何先前写入的数据,然后发送TCP的正常连接终止序列

  • isConnected()返回套接字的连接状态。

  • setOption(SocketOption<T> name, T value)设置套接字选项的值。

通信基本示例

服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@Slf4j
public class SocketServer {
public static void main(String[] args) throws Exception {
// 创建具有指定端口的服务器套接字
final int PORT = 8081;
//注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
final String CHARSET="UTF-8";
ServerSocket server = new ServerSocket(PORT);
SocketAddress serverAddress=server.getLocalSocketAddress();


// accept() 会阻塞进程,直到建立新连接,成功建立连接后会返回一个新的Socket。
log.info("Waiting for new connection(Listening on : {})",serverAddress);
Socket socket = server.accept();


// 建立好连接后,从socket中获取输入、输出流、客户端信息
InputStream inputStream = socket.getInputStream();
OutputStream outputStream=socket.getOutputStream();
SocketAddress clientAddress=socket.getRemoteSocketAddress();
log.info("There is a new connection {}",socket);

//建立缓冲区用于读取或写入
byte[] bytes = new byte[1024];
int len;
StringBuilder sb = new StringBuilder();
log.info("Waiting for message from client {} ",clientAddress);

//read()会阻塞进程,直到有输入,返回-1代表流结束
while ((len = inputStream.read(bytes)) != -1) {
//注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
sb.append(new String(bytes, 0, len,CHARSET));
}
log.info("Get a message '{}' from client {}" ,sb,socket.getRemoteSocketAddress());

//关闭连接和服务器
inputStream.close();
socket.close();
server.close();
}
}
1
2
3
4
13:49:01.451 [main] INFO cn.lbs.socket.base.SocketServer - Waiting for new connection(Listening on : 0.0.0.0/0.0.0.0:8081)
13:49:06.413 [main] INFO cn.lbs.socket.base.SocketServer - There is a new connection Socket[addr=/127.0.0.1,port=4463,localport=8081]
13:49:06.413 [main] INFO cn.lbs.socket.base.SocketServer - Waiting for message from client /127.0.0.1:4463
13:49:06.427 [main] INFO cn.lbs.socket.base.SocketServer - Get a message 'Hello World' from client /127.0.0.1:4463

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Slf4j
public class SocketClient {
public static void main(String args[]) throws Exception {
// 设置连接的服务端IP地址和端口
final String HOST = "127.0.0.1";
final int PORT = 8081;
//注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
final String CHARSET="UTF-8";

// 与服务端建立连接
Socket socket = new Socket(HOST, PORT);
// 建立连接后获得输出、输出流、连接信息
OutputStream outputStream = socket.getOutputStream();
InputStream inputStream=socket.getInputStream();
SocketAddress localAddress=socket.getLocalSocketAddress();
SocketAddress remoteAddress=socket.getRemoteSocketAddress();


log.info("The client {} successfully connects to the server {}",localAddress,remoteAddress);

String message="Hello World";
log.info("The client {} sends a message '{}' to the server ",localAddress,message);
socket.getOutputStream().write(message.getBytes(CHARSET));

//关闭连接
outputStream.close();
socket.close();
}
}
1
2
13:49:06.416 [main] INFO cn.lbs.socket.base.SocketClient - The client /127.0.0.1:4463 successfully connects to the server /127.0.0.1:8081
13:49:06.426 [main] INFO cn.lbs.socket.base.SocketClient - The client /127.0.0.1:4463 sends a message 'Hello World' to the server

进阶

双向通信基础

服务端

当读取完客户端的消息后,打开输出流,将指定消息发送回客户端。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@Slf4j
public class SocketServer {
public static void main(String[] args) throws Exception {
// 创建具有指定端口的服务器套接字
final int PORT = 8081;
//注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
final String CHARSET="UTF-8";
ServerSocket server = new ServerSocket(PORT);
SocketAddress serverAddress=server.getLocalSocketAddress();


// accept() 会阻塞进程,直到建立新连接,成功建立连接后会返回一个新的Socket。
log.info("Waiting for new connection(Listening on : {})",serverAddress);
Socket socket = server.accept();


// 建立好连接后,从socket中获取输入、输出流、客户端信息
InputStream inputStream = socket.getInputStream();
OutputStream outputStream=socket.getOutputStream();
SocketAddress clientAddress=socket.getRemoteSocketAddress();
log.info("There is a new connection {}",socket);

//建立缓冲区用于读取或写入
byte[] bytes = new byte[1024];
int len;
StringBuilder sb = new StringBuilder();
log.info("Waiting for message from client {} ",clientAddress);

//read()会阻塞进程,直到有输入,返回-1代表流结束
while ((len = inputStream.read(bytes)) != -1) {
//注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
sb.append(new String(bytes, 0, len,CHARSET));
}
log.info("Get a message '{}' from client {}" ,sb,socket.getRemoteSocketAddress());



String respStr= "Hello,I get the message";
log.info("Response message '{}' to Client {} ",respStr,clientAddress);
outputStream.write(respStr.getBytes(CHARSET));
socket.shutdownOutput();

//关闭连接和服务器
inputStream.close();
socket.close();
server.close();
}
}
1
2
3
4
5
13:58:51.050 [main] INFO cn.lbs.socket.base.SocketServer - Waiting for new connection(Listening on : 0.0.0.0/0.0.0.0:8081)
13:58:58.706 [main] INFO cn.lbs.socket.base.SocketServer - There is a new connection Socket[addr=/127.0.0.1,port=4554,localport=8081]
13:58:58.706 [main] INFO cn.lbs.socket.base.SocketServer - Waiting for message from client /127.0.0.1:4554
13:58:58.719 [main] INFO cn.lbs.socket.base.SocketServer - Get a message 'Hello World' from client /127.0.0.1:4554
13:58:58.719 [main] INFO cn.lbs.socket.base.SocketServer - Response message 'Hello,I get the message' to Client /127.0.0.1:4554

客户端

客户端也有相应的变化,在发送完消息时,调用关闭输出流方法,然后打开输出流,等候服务端的消息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Slf4j
public class SocketClient {
public static void main(String args[]) throws Exception {
// 设置连接的服务端IP地址和端口
final String HOST = "127.0.0.1";
final int PORT = 8081;
//注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
final String CHARSET="UTF-8";

// 与服务端建立连接
Socket socket = new Socket(HOST, PORT);
// 建立连接后获得输出、输出流、连接信息
OutputStream outputStream = socket.getOutputStream();
InputStream inputStream=socket.getInputStream();
SocketAddress localAddress=socket.getLocalSocketAddress();
SocketAddress remoteAddress=socket.getRemoteSocketAddress();


log.info("The client {} successfully connects to the server {}",localAddress,remoteAddress);

String message="Hello World";
log.info("The client {} sends a message '{}' to the server ",localAddress,message);
outputStream.write(message.getBytes(CHARSET));
socket.shutdownOutput();

//建立缓冲区用于读取或写入
byte[] bytes = new byte[1024];
int len;
StringBuilder sb = new StringBuilder();
//获取响应
log.info("Client {} is waiting for Response",localAddress);
//read()会阻塞进程,直到有输入,返回-1代表流结束
while ((len = inputStream.read(bytes)) != -1) {
//注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
sb.append(new String(bytes, 0, len,CHARSET));
}
log.info("Get response message '{}' from Server" , sb);
//关闭连接
outputStream.close();
socket.close();
}
}
1
2
3
4
13:58:58.708 [main] INFO cn.lbs.socket.base.SocketClient - The client /127.0.0.1:4554 successfully connects to the server /127.0.0.1:8081
13:58:58.719 [main] INFO cn.lbs.socket.base.SocketClient - The client /127.0.0.1:4554 sends a message 'Hello World' to the server
13:58:58.719 [main] INFO cn.lbs.socket.base.SocketClient - Client /127.0.0.1:4554 is waiting for Response
13:58:58.720 [main] INFO cn.lbs.socket.base.SocketClient - Get response message 'Hello,I get the message' from Server

发送完成告知机制

关闭Socket连接

当Socket关闭的时候,服务端就会收到响应的关闭信号,那么服务端也就知道流已经关闭了,这个时候读取操作完成,就可以继续后续工作。但是客户端Socket关闭后,将不能接受服务端发送的消息,也不能再次发送消息。如果客户端想再次发送消息,需要重现创建Socket连接

关闭输出流

调用Socket的shutdownOutput()方法(不是关闭输出流close(),因为关闭输出流会直接关闭Socket连接),底层会告知服务端我这边已经写完了,那么服务端收到消息后,就能知道已经读取完消息,如果服务端有要返回给客户的消息那么就可以通过服务端的输出流发送给客户端。这种方式通过关闭客户端的输出流,告知服务端已经写完了,虽然可以读到服务端发送的消息,但是是不能再次发送消息给服务端,如果再次发送,需要重新建立Socket连接。

约定符号

就是双方约定一个字符或者一个短语,来当做消息发送完成的标识,通常这么做就需要改造读取方法。

1
2
3
4
5
6
7
8
9
Socket socket = server.accept();
// 建立好连接后,从socket中获取输入流,并建立缓冲区进行读取
BufferedReader read=new BufferedReader(new InputStreamReader(socket.getInputStream(),"UTF-8"));
String line;
StringBuilder sb = new StringBuilder();
while ((line = read.readLine()) != null && "end".equals(line)) {
//注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
sb.append(line);
}
  • 优点:不需要关闭流,当发送完一条消息后可以再次发送新的命令消息

  • 缺点:需要额外的约定结束标志,太简单的容易出现在要发送的消息中,误被结束,太复杂的不好处理,还占带宽

指定长度

先指定后续命令的长度,然后读取指定长度的内容做为客户端发送的消息。一般,如果用作命令发送,两个字节就够了,4个字节基本就能满足所有要求。

服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class SocketServer {
public static void main(String[] args) throws Exception {
// 监听指定的端口
int port = 8081;
ServerSocket server = new ServerSocket(port);

// server将一直等待连接的到来
System.out.println("Waiting for new connection(Listening Port:"+server.getLocalPort()+")");
Socket socket = server.accept();
System.out.println("There is a new connection "+socket);
// 建立好连接后,从socket中获取输入流,并建立缓冲区进行读取
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();

byte[] receivebytes,sendBytes;
// 因为可以复用Socket且能判断长度,所以可以一个Socket用到底
while (true) {
System.out.println();
System.out.println("Waiting for Message");
// 首先读取两个字节表示的长度,read()方法会阻塞进程
int first = inputStream.read();
//如果读取的值为-1 说明到了流的末尾,Socket已经被关闭了,此时将不能再去读取
if(first==-1){
System.out.println("Close connection");
break;
}
int second = inputStream.read();
int length = (first << 8) + second;
// 然后构造一个指定长的byte数组
receivebytes = new byte[length];
// 然后读取指定长度的消息即可
inputStream.read(receivebytes);
String recStr=new String(receivebytes, "UTF-8");
System.out.println("Get message from client: " + recStr);

String respStr="Hello Client,I get the message:"+recStr;
System.out.println("Response message {"+respStr+"} to Client");
//首先需要计算得知消息的长度
sendBytes = respStr.getBytes("UTF-8");
//然后将消息的长度优先发送出去
outputStream.write(sendBytes.length >>8);
outputStream.write(sendBytes.length);
//然后将消息再次发送出去
outputStream.write(sendBytes);
outputStream.flush();
}
inputStream.close();
socket.close();
server.close();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
14:03:20.550 [main] INFO cn.lbs.socket.communication.SocketServer - Waiting for new connection(Listening on : 0.0.0.0/0.0.0.0:8081)
14:03:23.534 [main] INFO cn.lbs.socket.communication.SocketServer - There is a new connection Socket[addr=/127.0.0.1,port=4602,localport=8081]
14:03:23.535 [main] INFO cn.lbs.socket.communication.SocketServer - Waiting for message from client /127.0.0.1:4602
14:03:23.546 [main] INFO cn.lbs.socket.communication.SocketServer - Get message 'Hello World' from client /127.0.0.1:4602
14:03:23.546 [main] INFO cn.lbs.socket.communication.SocketServer - Response message 'Hello,I get the message' to Client /127.0.0.1:4602
14:03:23.547 [main] INFO cn.lbs.socket.communication.SocketServer - Waiting for message from client /127.0.0.1:4602
14:03:23.547 [main] INFO cn.lbs.socket.communication.SocketServer - Get message '你好,世界' from client /127.0.0.1:4602
14:03:23.547 [main] INFO cn.lbs.socket.communication.SocketServer - Response message 'Hello,I get the message' to Client /127.0.0.1:4602
14:03:23.547 [main] INFO cn.lbs.socket.communication.SocketServer - Waiting for message from client /127.0.0.1:4602
14:03:23.547 [main] INFO cn.lbs.socket.communication.SocketServer - Get message 'YOLO' from client /127.0.0.1:4602
14:03:23.547 [main] INFO cn.lbs.socket.communication.SocketServer - Response message 'Hello,I get the message' to Client /127.0.0.1:4602
14:03:23.547 [main] INFO cn.lbs.socket.communication.SocketServer - Waiting for message from client /127.0.0.1:4602

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class SocketClient {

public static void main(String args[]) throws Exception {
// 要连接的服务端IP地址和端口
String host = "127.0.0.1";
int port = 8081;
// 与服务端建立连接
Socket socket = new Socket(host, port);
System.out.println("Connect to "+socket.getRemoteSocketAddress()+" Success");
// 建立连接后获得输出流
OutputStream outputStream = socket.getOutputStream();
InputStream inputStream = socket.getInputStream();
String[] messages = {"Hello World","你好,世界","YOLO"};
byte[] receivebytes,sendBytes;

for(String message:messages){
System.out.println();
System.out.println("Send {"+message+"}to Server");
//首先需要计算得知消息的长度
sendBytes = message.getBytes("UTF-8");
//然后将消息的长度优先发送出去
outputStream.write(sendBytes.length >>8);
outputStream.write(sendBytes.length);
//然后将消息再次发送出去
outputStream.write(sendBytes);


System.out.println("Waiting for Response");
// 首先读取两个字节表示的长度,read()方法会阻塞进程
int first = inputStream.read();
//如果读取的值为-1 说明到了流的末尾,Socket已经被关闭了,此时将不能再去读取
if(first==-1){
System.out.println("Close connection");
break;
}
int second = inputStream.read();
int length = (first << 8) + second;
// 然后构造一个指定长的byte数组
receivebytes = new byte[length];
// 然后读取指定长度的消息即可
inputStream.read(receivebytes);
System.out.println("Get Response from Server: " + new String(receivebytes, "UTF-8"));

}
socket.shutdownOutput();
socket.close();
}
}

1
2
3
4
5
6
7
8
9
10
13:41:06.058 [main] INFO cn.lbs.socket.communication.SocketClient - The client /127.0.0.1:4364 successfully connects to the server /127.0.0.1:8081
13:41:06.065 [main] INFO cn.lbs.socket.communication.SocketClient - The client /127.0.0.1:4364 sends a message 'Hello World' to the server
13:41:06.065 [main] INFO cn.lbs.socket.communication.SocketClient - Client /127.0.0.1:4364 is waiting for Response
13:41:06.068 [main] INFO cn.lbs.socket.communication.SocketClient - Get response message 'Hello (/127.0.0.1:4364),I get the message' from Server
13:41:06.068 [main] INFO cn.lbs.socket.communication.SocketClient - The client /127.0.0.1:4364 sends a message '你好,世界' to the server
13:41:06.069 [main] INFO cn.lbs.socket.communication.SocketClient - Client /127.0.0.1:4364 is waiting for Response
13:41:06.069 [main] INFO cn.lbs.socket.communication.SocketClient - Get response message 'Hello (/127.0.0.1:4364),I get the message' from Server
13:41:06.069 [main] INFO cn.lbs.socket.communication.SocketClient - The client /127.0.0.1:4364 sends a message 'YOLO' to the server
13:41:06.069 [main] INFO cn.lbs.socket.communication.SocketClient - Client /127.0.0.1:4364 is waiting for Response
13:41:06.069 [main] INFO cn.lbs.socket.communication.SocketClient - Get response message 'Hello (/127.0.0.1:4364),I get the message' from Server

服务端并发

服务器

在实际生产中,创建的线程会交给线程池来处理,这样可以达到线程复用的效果,也可以防止短时间内高并发,new Thread短时间创建大量线程,导致资源耗尽,服务挂掉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
@Slf4j
public class Server {
//注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
final String CHARSET="UTF-8";
ExecutorService threadPool;
ServerSocket serverSocket;
SocketAddress serverAddress;

Server(int port) throws Exception {
threadPool = Executors.newCachedThreadPool();
// 创建具有指定端口的服务器套接字
serverSocket = new ServerSocket(port);
serverAddress= serverSocket.getLocalSocketAddress();
}

void start() throws Exception{
while (true) {
// accept() 会阻塞进程,直到建立新连接,成功建立连接后会返回一个新的Socket。
log.info("Waiting for new connection(Listening on : {})", serverAddress);
Socket socket = serverSocket.accept();
log.info("There is a new connection {}", socket);
threadPool.submit(new SocketTask(socket));
}
}
public static void main(String[] args) throws Exception {
new Server(8081).start();
}
}

@Slf4j
class SocketTask implements Runnable{
//注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
final String CHARSET="UTF-8";
Socket socket;
SocketTask(Socket socket){
this.socket=socket;
}
@Override
public void run() {
try {
// 建立好连接后,从socket中获取输入、输出流、客户端信息
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
SocketAddress clientAddress = socket.getRemoteSocketAddress();
//建立缓冲区用于读取或写入
byte[] receiveBytes, sendBytes;
// 因为可以复用Socket且能判断长度,所以可以一个Socket用到底
while (true) {
log.info("Waiting for message from client {} ", clientAddress);
// 首先读取两个字节表示的长度,read()方法会阻塞进程
int firstByte = inputStream.read();
//如果读取的值为-1 说明到了流的末尾,Socket已经被关闭了,此时将不能再去读取
if (firstByte == -1) {
break;
}
int secondByte = inputStream.read();
int messageLen = (firstByte << 8) + secondByte;
// 然后构造一个指定长的byte数组
receiveBytes = new byte[messageLen];
// 然后读取指定长度的消息即可
inputStream.read(receiveBytes);
log.info("Get message '{}' from client {}", new String(receiveBytes, CHARSET), clientAddress);
String respStr = "Hello,I get the message";
log.info("Response message '{}' to Client {} ", respStr, clientAddress);
//首先需要计算得知消息的长度
sendBytes = respStr.getBytes(CHARSET);
//然后将消息的长度优先发送出去
outputStream.write(sendBytes.length >> 8);
outputStream.write(sendBytes.length);
//然后将消息再次发送出去
outputStream.write(sendBytes);
outputStream.flush();
}
log.info("关闭连接{}",socket);
socket.shutdownOutput();
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}

客户端

并发模拟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
@Slf4j
public class MultiClient {
// 设置连接的服务端IP地址和端口
static final String HOST = "127.0.0.1";
static final int PORT = 8081;
static final int CLIENTSNUM=100;
public static void main(String args[]) throws Exception {
ExecutorService threadPool= Executors.newCachedThreadPool();
for(int i=0;i<CLIENTSNUM;i++){
threadPool.submit(new ClientTask(HOST,PORT));
}
}

}

@Slf4j
class ClientTask implements Runnable{
//注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
final String CHARSET="UTF-8";
final int MESSAGENUM=100;
Socket socket ;

OutputStream outputStream;
InputStream inputStream;
SocketAddress localAddress;
SocketAddress remoteAddress;

ClientTask(String host,int port) throws Exception{
// 与服务端建立连接
// 建立连接后获得输出、输出流、连接信息
socket=new Socket(host,port);
outputStream = socket.getOutputStream();
inputStream=socket.getInputStream();
localAddress=socket.getLocalSocketAddress();
remoteAddress=socket.getRemoteSocketAddress();
log.info("The client {} successfully connects to the server {}",localAddress,remoteAddress);
}

@Override
public void run() {
try {
String[] messages = {"Hello World","你好,世界","YOLO"};
byte[] receiveBytes,sendBytes;

for(int i=0;i<MESSAGENUM;i++) {
String message=RandomStringUtils.random(10, true, true);
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
log.info("The client {} sends a message '{}' to the server ", localAddress, message);
//首先需要计算得知消息的长度
sendBytes = message.getBytes(CHARSET);
//然后将消息的长度优先发送出去
outputStream.write(sendBytes.length >> 8);
outputStream.write(sendBytes.length);
//然后将消息再次发送出去
outputStream.write(sendBytes);
outputStream.flush();


//获取响应
log.info("Client {} is waiting for Response", localAddress);

// 首先读取两个字节表示的长度,read()方法会阻塞进程
int firstByte = inputStream.read();
//如果读取的值为-1 说明到了流的末尾,Socket已经被关闭了,此时将不能再去读取
if (firstByte == -1) {
break;
}
int secondByte = inputStream.read();
int messageLen = (firstByte << 8) + secondByte;
// 然后构造一个指定长的byte数组
receiveBytes = new byte[messageLen];
// 然后读取指定长度的消息即可
inputStream.read(receiveBytes);
log.info("Get response message '{}' from Server", new String(receiveBytes, CHARSET));
}
log.info("关闭连接{}",socket);
socket.shutdownOutput();
socket.close();
}
catch (Exception e){
e.printStackTrace();
}
}
}

其他

SocketOptions

  • int TCP_NODELAY = 0x0001:对此连接禁用 Nagle 算法。
  • int SO_BINDADDR = 0x000F:此选项为 TCP 或 UDP 套接字在 IP 地址头中设置服务类型或流量类字段。
  • int SO_REUSEADDR = 0x04:设置套接字的 SO_REUSEADDR。
  • int SO_BROADCAST = 0x0020:此选项启用和禁用发送广播消息的处理能力。
  • int IP_MULTICAST_IF = 0x10:设置用于发送多播包的传出接口。
  • int IP_MULTICAST_IF2 = 0x1f:设置用于发送多播包的传出接口。
  • int IP_MULTICAST_LOOP = 0x12:此选项启用或禁用多播数据报的本地回送。
  • int IP_TOS = 0x3:此选项为 TCP 或 UDP 套接字在 IP 地址头中设置服务类型或流量类字段。
  • int SO_LINGER = 0x0080:指定关闭时逗留的超时值。
  • int SO_TIMEOUT = 0x1006:设置阻塞 Socket 操作的超时值: ServerSocket.accept(); SocketInputStream.read(); DatagramSocket.receive(); 选项必须在进入阻塞操作前设置才能生效。
  • int SO_SNDBUF = 0x1001:设置传出网络 I/O 的平台所使用的基础缓冲区大小的提示。
  • int SO_RCVBUF = 0x1002:设置传入网络 I/O 的平台所使用基础缓冲区的大小的提示。
  • int SO_KEEPALIVE = 0x0008:为 TCP 套接字设置 keepalive 选项时,对于构建长时间连接的Socket还是配置上SO_KEEPALIVE比较好
  • int SO_OOBINLINE = 0x1003:置 OOBINLINE 选项时,在套接字上接收的所有 TCP 紧急数据都将通过套接字输入流接收。

Socket与操作系统

Socket实际上是归属于应用层,使用的是运输层的TCP。在三次握手操作后,系统才会将Socket连接交给应用层,ServerSocket 才知道有一个连接过来了。那么系统当接收到一个TCP连接请求后,如果上层还没有接受它(假如SocketServer循环处理Socket,一次一个),那么系统将缓存这个连接请求,缓存是有限制的,当超过指定数量后,系统将会拒绝连接。

换句话说,系统接收TCP连接请求放入缓存队列,而SocketServer从缓存队列获取Socket。

客户端为了让服务端知道自己已经发送完消息,可以选择关闭输出流**socket.shutdownOutput(),这个操作对应着四次挥手的第一次**,这些都由操作系统去执行,只是Socket隐藏底层逻辑的种种细节。

参考

ServerSocket - Java 11中文版 - API参考文档 (apiref.com)

Socket - Java 11中文版 - API参考文档 (apiref.com)

InetAddress - Java 11中文版 - API参考文档 (apiref.com)

【Socket】Java Socket编程基础及深入讲解 - 已往之不谏 - 博客园 (cnblogs.com)