快站的优惠券怎么发布的发稿平台
一、Socket通信基本示例
package org.llyf;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
/**
* 客户端通过ip和端口,连接到指定的server,然后通过Socket获得输出流,并向其输出内容,
* 服务器会获得消息。最终服务端控制台打印如下:
* @author 123456
*
*/
public class SocketClient {
public static void main(String[] args) throws Exception, IOException {
int port=8888;
String host="127.0.0.1";
//与服务器及建立连接
Socket socket = new Socket(host,port);
//及案例连接后获取输出流
OutputStream outputStream = socket.getOutputStream();
String message="草泥马";
outputStream.write(message.getBytes("UTF-8"));
outputStream.close();
socket.close();
}
}
package org.llyf;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketServer {
/**
* 服务端监听一个端口,等待连接的到来。
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
int port=8888;
ServerSocket server = new ServerSocket(port);
System.out.println("等待连接的到来");
Socket accept = server.accept();
//建立好连接后,从socket中读取数据,accept.getInputStream()是用来读数据的
InputStream inputStream = accept.getInputStream();
byte[] b= new byte[1024];
int len;
StringBuilder sb = new StringBuilder();
while((len=inputStream.read(b)) !=-1) {
sb.append(new String(b,0,len,"utf-8"));
}
//注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
System.out.println("从客户端获取的信息为:"+sb);
inputStream.close();
accept.close();
server.close();
}
}
二、消息通信优化
2.1 双向通信,发送消息并接受消息
package org.llyf;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/**
* 客户端通过ip和端口,连接到指定的server,然后通过Socket获得输出流,并向其输出内容,
* 服务器会获得消息。最终服务端控制台打印如下:
* @author 123456
*
*/
public class SocketClient {
public static void main(String[] args) throws Exception, IOException {
int port=8888;
String host="127.0.0.1";
//与服务器及建立连接
Socket socket = new Socket(host,port);
//及案例连接后获取输出流
OutputStream outputStream = socket.getOutputStream();
String message="草泥马";
outputStream.write(message.getBytes("UTF-8"));
//如果不关闭这个输出流,服务器就会一直等待下去,直到等待超时,此时服务器不能做接下来的发送消息的任务其实这个问题还是比较重要的,正常来说,客户端打开一个输出流,如果不做约定,也不关闭它,那么服务端永远不知道客户端是否发送完消息,那么服务端会一直等待下去,直到读取超时。所以怎么告知服务端已经发送完消息就显得特别重要。
- 客户端Socket关闭后,将不能接受服务端发送的消息,也不能再次发送消息
- 如果客户端想再次发送消息,需要重现创建Socket连接
socket.shutdownOutput();
//加入下面这些东西就可以双向通信,发送消息并接受消息
InputStream inputStream = socket.getInputStream();
StringBuilder sb = new StringBuilder();
byte[] b = new byte[1024];
int len;
while((len=inputStream.read(b)) !=-1) {
sb.append(new String(b,0,len,"utf-8"));
}
System.out.println("收到服务器响应的数据为:"+sb);
inputStream.close();
outputStream.close();
socket.close();
}
}
package org.llyf;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketServer {
/**
* 服务端监听一个端口,等待连接的到来。
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
int port=8888;
ServerSocket server = new ServerSocket(port);
System.out.println("等待连接的到来");
Socket accept = server.accept();
//建立好连接后,从socket中读取数据,accept.getInputStream()是用来读数据的
InputStream inputStream = accept.getInputStream();
byte[] b= new byte[1024];
int len;
StringBuilder sb = new StringBuilder();
while((len=inputStream.read(b)) !=-1) {
sb.append(new String(b,0,len,"utf-8"));
}
//注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
System.out.println("从客户端获取的信息为:"+sb);
OutputStream outputStream = accept.getOutputStream();
outputStream.write("你好客户端,我已经收到你的消息了".getBytes("UTF-8"));
inputStream.close();
outputStream.close();
accept.close();
server.close();
}
}
2.3 如何告知对方已发送完命令
其实这个问题还是比较重要的,正常来说,客户端打开一个输出流,如果不做约定,也不关闭它,那么服务端永远不知道客户端是否发送完消息,那么服务端会一直等待下去,直到读取超时。所以怎么告知服务端已经发送完消息就显得特别重要。
2.3.1 通过Socket关闭
这个是第一章介绍的方式,当Socket关闭的时候,服务端就会收到响应的关闭信号,那么服务端也就知道流已经关闭了,这个时候读取操作完成,就可以继续后续工作。
但是这种方式有一些缺点
- 客户端Socket关闭后,将不能接受服务端发送的消息,也不能再次发送消息
- 如果客户端想再次发送消息,需要重现创建Socket连接
2.3.2 通过Socket关闭输出流的方式
这种方式调用的方法是:
socket.shutdownOutput();
而不是(outputStream为发送消息到服务端打开的输出流):
outputStream.close();
如果关闭了输出流,那么相应的Socket也将关闭,和直接关闭Socket一个性质。
调用Socket的shutdownOutput()方法,底层会告知服务端我这边已经写完了,那么服务端收到消息后,就能知道已经读取完消息,如果服务端有要返回给客户的消息那么就可以通过服务端的输出流发送给客户端,如果没有,直接关闭Socket。
这种方式通过关闭客户端的输出流,告知服务端已经写完了,虽然可以读到服务端发送的消息,但是还是有一点点缺点:
- 不能再次发送消息给服务端,如果再次发送,需要重新建立Socket连接
这个缺点,在访问频率比较高的情况下将是一个需要优化的地方
2.3.3 通过约定符号
这种方式的用法,就是双方约定一个字符或者一个短语,来当做消息发送完成的标识,通常这么做就需要改造读取方法。
假如约定单端的一行为end,代表发送完成,例如下面的消息,end则代表消息发送完成:
hello yiwangzhibujian
end
那么服务端响应的读取操作需要进行如下改造:
package org.llyf;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketServer {
/**
* 服务端监听一个端口,等待连接的到来。
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
int port=8888;
ServerSocket server = new ServerSocket(port);
System.out.println("等待连接的到来");
/*Socket accept = server.accept();
//建立好连接后,从socket中读取数据,accept.getInputStream()是用来读数据的
InputStream inputStream = accept.getInputStream();
byte[] b= new byte[1024];
int len;
StringBuilder sb = new StringBuilder();
while((len=inputStream.read(b)) !=-1) {
sb.append(new String(b,0,len,"utf-8"));
}
//注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
System.out.println("从客户端获取的信息为:"+sb);*/
Socket accept = server.accept();
BufferedReader reader=new BufferedReader(new InputStreamReader(accept.getInputStream(),"UTF-8"));
String line;
StringBuilder sb = new StringBuilder();
//约定单端的一行为end,如果新的一行为end则读取
while((line=reader.readLine()) !=null && "end".equals(line) ) {
sb.append(line);
}
System.out.println("从客户端获取的信息为:"+sb);
OutputStream outputStream =null;
if(sb.length()>0) {
outputStream = accept.getOutputStream();
outputStream.write("你好客户端,我已经收到你的消息了".getBytes("UTF-8"));
}else {
System.out.println("从客户端获取的信息为空");
}
reader.close();
outputStream.close();
accept.close();
server.close();
}
}
可以看见,服务端不仅判断是否读到了流的末尾,还判断了是否读到了约定的末尾。
这么做的优缺点如下:
- 优点:不需要关闭流,当发送完一条命令(消息)后可以再次发送新的命令(消息)
- 缺点:需要额外的约定结束标志,太简单的容易出现在要发送的消息中,误被结束,太复杂的不好处理,还占带宽
经过了这么多的优化还是有缺点,难道就没有完美的解决方案吗,答案是有的,看接下来的内容。
2.3.4 通过指定长度
如果你了解一点class文件的结构(后续会写,敬请期待),那么你就会佩服这么设计方式,也就是说我们可以在此找灵感,就是我们可以先指定后续命令的长度,然后读取指定长度的内容做为客户端发送的消息。
现在首要的问题就是用几个字节指定长度呢,我们可以算一算:
- 1个字节:最大256,表示256B
- 2个字节:最大65536,表示64K
- 3个字节:最大16777216,表示16M
- 4个字节:最大4294967296,表示4G
- 依次类推
这个时候是不是很纠结,最大的当然是最保险的,但是真的有必要选择最大的吗,其实如果你稍微了解一点UTF-8的编码方式(字符编码后续会写,敬请期待),那么你就应该能想到为什么一定要固定表示长度字节的长度呢,我们可以使用变长方式来表示长度的表示,比如:
- 第一个字节首位为0:即0XXXXXXX,表示长度就一个字节,最大128,表示128B
- 第一个字节首位为110,那么附带后面一个字节表示长度:即110XXXXX 10XXXXXX,最大2048,表示2K
- 第一个字节首位为1110,那么附带后面二个字节表示长度:即110XXXXX 10XXXXXX 10XXXXXX,最大131072,表示128K
- 依次类推
上面提到的这种用法适合高富帅的程序员使用,一般呢,如果用作命名发送,两个字节就够了,如果还不放心4个字节基本就能满足你的所有要求,下面的例子我们将采用2个字节表示长度,目的只是给你一种思路,让你知道有这种方式来获取消息的结尾:
https://www.cnblogs.com/yiwangzhibujian/p/7107785.html#q1点击开链接