系列文章:
Android Socket 系列更新计划Android Socket通信(一) – 初识与相遇
Android Socket通信(二) --UDP,单播,广播和多播(组播)
Android Socket通信(三) – TCP 配置和传递基础数据
Android Socket通信(四) – UDP与TCP结合传输数据
Android Socket通信(五) – 实现一个多人聊天室
工程连接 : https://github.com/LillteZheng/SocketDemo
上一章中,我们学习了 UDP 的基本概念,而第一张中,则学习了TCP的基本发送功能,这章,我们来学习 TCP 的基础配置和发送基础数据,如byte,char。
在这篇文章中,你将学习到:
- socket tcp 的配置
- tcp 传递byte,char,float 等基础数据
一、tcp 设置
首先,先看看客户端,在一般的设置中,我们可以使用 Socket socket = new Socket() 去创建客户端,而实际中个,创建 socket 可以还有配置代理,固定端口等,如下:
private static Socket createSocket() throws IOException {
/*
// 无代理模式,等效于空构造函数
Socket socket = new Socket(Proxy.NO_PROXY);
// 新建一份具有HTTP代理的套接字,传输数据将通过www.baidu.com:8080端口转发
Proxy proxy = new Proxy(Proxy.Type.HTTP,
new InetSocketAddress(Inet4Address.getByName("www.baidu.com"), 8800));
socket = new Socket(proxy);
// 新建一个套接字,并且直接链接到本地20000的服务器上
socket = new Socket("localhost", PORT);
// 新建一个套接字,并且直接链接到本地20000的服务器上
socket = new Socket(Inet4Address.getLocalHost(), PORT);
// 新建一个套接字,并且直接链接到本地20000的服务器上,并且绑定到本地20001端口上
socket = new Socket("localhost", PORT, Inet4Address.getLocalHost(), LOCAL_PORT);
socket = new Socket(Inet4Address.getLocalHost(), PORT, Inet4Address.getLocalHost(), LOCAL_PORT);
*/
Socket socket = new Socket();
// 绑定到本地20000端口,在 connect 连接服务器之前,这样 客户端的端口就不会再变动了
socket.bind(new InetSocketAddress(Inet4Address.getLocalHost(), 20000));
return socket;
}
这里讲一下 bind 的属性,比如,我们在第一章中,socket 客户端的端口是一直变动的,这是因为我们并没有制定,而端口由系统去指定;当我们使用了 bind 之后,则客户端的端口就指定好了,不会再变动了;接着继续配置 socket 的其他属性:
private static void configSocket(Socket socket) throws IOException{
// 设置读取超时时间为2秒
socket.setSoTimeout(2000);
// 是否复用未完全关闭的Socket地址,需要再 bind 之前有效
socket.setReuseAddress(true);
/**
* 是否开启 nagle 算法,这里是关闭
* tcp 每次发送数据都需要 ack 应答,如果关闭 nagle 算法,并不会每次新数据到来就立刻
* 回送 ack,可能等到第二次或第三次再返回 ack,客户端在拿到 ack 之前,会一直等待,等
* ack 回来之后,再把堆积的数据一次性发送给服务端,这样有助于弱网的情况
*/
socket.setTcpNoDelay(true);
// 是否需要在长时无数据响应时发送确认数据(类似心跳包),时间大约为2小时
socket.setKeepAlive(true);
// 对于close关闭操作行为进行怎样的处理;默认为false,0
// false、0:默认情况,关闭时立即返回,底层系统接管输出流,将缓冲区内的数据发送完成
// true、0:关闭时立即返回,缓冲区数据抛弃,直接发送RST结束命令到对方,并无需经过2MSL等待
// true、200:关闭时最长阻塞200毫秒,随后按第二情况处理
socket.setSoLinger(true, 200);
// 是否让紧急数据内敛,默认false;紧急数据通过 socket.sendUrgentData(1);发送
socket.setOOBInline(true);
// 设置接收发送缓冲器大小
socket.setReceiveBufferSize(64 * 1024 * 1024);
socket.setSendBufferSize(64 * 1024 * 1024);
// 设置性能参数:短链接,延迟,带宽的相对重要性,这里采用的权重的意思
socket.setPerformancePreferences(1, 1, 0);
}
需要注意的是 socket.setPerformancePreferences(1, 1, 0); 是权重的意思,就算设置成100💯100 也是1:1:1 的关系,这个可以根据业务需求去改,如果想要延迟低,则提高这个权重即可,其他的属性都说明得很清楚了。
需要注意的是,这些配置都需要在 connect 连接服务器之前,不然设置了无效;
接着看发送基础数据,这里采用 ByteBuffer 来实现,如下:
System.out.println("开始传递基础数据");
OutputStream outputStream = socket.getOutputStream();
InputStream inputStream = socket.getInputStream();
byte[] buffer = new byte[256];
ByteBuffer byteBuffer = ByteBuffer.wrap(buffer);
byteBuffer.put((byte) 1);
byteBuffer.putChar('a');
byteBuffer.putInt(12345);
byteBuffer.putLong(23422345);
byteBuffer.putDouble(123435465);
byteBuffer.putFloat(234.234234f);
byteBuffer.put("hello 你好".getBytes());
outputStream.write(buffer,0,byteBuffer.position()+1);
至于 ByteBuffer 的使用,建议先了解,后面讲到 NIO 的时候,我们再来详细分析。
这样客户端就写好了;
接着看服务器端的配置:
首先是创建 serverSocket:
private static ServerSocket createServerSocket() throws IOException {
// 创建基础的ServerSocket
ServerSocket serverSocket = new ServerSocket();
// 绑定到本地端口20000上,并且设置当前可允许等待链接的队列为50个
//serverSocket = new ServerSocket(PORT);
// 等效于上面的方案,队列设置为50个
//serverSocket = new ServerSocket(PORT, 50);
// 与上面等同
// serverSocket = new ServerSocket(PORT, 50, Inet4Address.getLocalHost());
return serverSocket;
}
接着配置 serversocket,这里跟 客户端差不多:
private static void configServerSocket(ServerSocket serverSocket) throws SocketException {
// 是否复用未完全关闭的地址端口
serverSocket.setReuseAddress(true);
// 设置接收buf,当大于64MB,则需要分片
serverSocket.setReceiveBufferSize(64 * 1024 * 1024);
// 设置serverSocket#accept超时时间,加入设置了2000,在 2s 内,没客户端接入,则 accept 报错
// serverSocket.setSoTimeout(2000);
// 设置性能参数:短链接,延迟,带宽的相对重要性,这里采用的权重的意思
serverSocket.setPerformancePreferences(1, 1, 1);
}
上面看到 backlog 的意思,其实就是接入丢列大小,所以一般我们可以这样配置,当然也可以直接配置:
ServerSocket serverSocket = createServerSocket();
configServerSocket(serverSocket);
//backlog 表示可以等待的队列大小,加入有第51个客户端接入,则客户端会提示错误,一般不设置
serverSocket.bind(new InetSocketAddress(InetAddress.getLocalHost(), Constans.PORT),50);
接着就是读取基础数据了:
//拿到数据流
InputStream inputStream = accept.getInputStream();
byte[] buffer = new byte[256];
int read = inputStream.read(buffer);
ByteBuffer byteBuffer = ByteBuffer.wrap(buffer,0,read);
byte b = byteBuffer.get();
char aChar = byteBuffer.getChar();
int anInt = byteBuffer.getInt();
long aLong = byteBuffer.getLong();
double aDouble = byteBuffer.getDouble();
float aFloat = byteBuffer.getFloat();
//拿到现在的 position
int pos = byteBuffer.position();
String msg = new String(buffer,pos,read - pos - 1);
结果如下: