服务端代码:html
package com.example.admin.server;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
public class MyClass {
public static void main(String[] arugs) throws IOException {
try {
call();
} catch (IOException e) {
System.out.print("io failed"); }
}
public static void call() throws IOException {
InputStream inputStream = null;
Socket socket=null;
ServerSocket serverSocket=null;
try {
serverSocket = new ServerSocket(5050);
InetAddress address = InetAddress.getLocalHost();
String ip = address.getHostAddress();
System.out.println("服务端ip地址:" + ip);
socket = serverSocket.accept();
inputStream = socket.getInputStream();
StringBuilder builder = new StringBuilder();
byte[] bytes = new byte[1024];
int length;
while ((length = inputStream.read(bytes)) != -1) {
builder.append(new String(bytes, 0, length, "UTF-8"));
}
System.out.println(builder.toString());
} catch (UnknownHostException e) {
System.out.print("null localHost");
} finally {
if (inputStream != null) {
inputStream.close();
}if (socket!=null){
socket.close();
}if (serverSocket!=null){
serverSocket.close(); }
}
}
}
控制台输出: 服务端ip地址:xxx.xxx.x.x
客户端代码:
首先添加网络权限
<uses-permission android:name="android.permission.INTERNET"/>
package com.example.admin.socket;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button=findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread() {
@Override
public void run() {
super.run();
try {
call();
}catch (IOException e){
Log.e("io","io close failed"); }
}
}.start();
}
});
}
public void call()throws IOException{
Socket socket=null;
OutputStream out=null;
try {
socket = new Socket("xxx.xxx.x.x", 5050);
out = socket.getOutputStream();
out.write("Hello World".getBytes( "UTF-8"));
}catch (UnknownHostException e){
Log.e("socket", "create socket failed");
} catch (IOException e) {
Log.e("io","outputstream problem ");
}finally {
if (out!=null)
out.close();
}
if (socket!=null){
socket.close();
}
}
}
点击按钮后,控制台输出:服务端ip地址:xxx.xxx.x.x
Hello World
服务端代码:java
package com.example.admin.server;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
public class MyClass {
public static void main(String[] arugs) throws IOException {
try {
call();
} catch (IOException e) {
System.out.print("io failed"); }
}
public static void call() throws IOException {
InputStream inputStream = null;
OutputStream outputStream=null;
Socket socket=null;
ServerSocket serverSocket=null;
try {
serverSocket = new ServerSocket(5050);
InetAddress address = InetAddress.getLocalHost();
String ip = address.getHostAddress();
System.out.println("服务端ip地址:" + ip);
socket = serverSocket.accept();
inputStream = socket.getInputStream();
StringBuilder builder = new StringBuilder();
byte[] bytes = new byte[1024];
int length;
while ((length = inputStream.read(bytes)) != -1) {
builder.append(new String(bytes, 0, length, "UTF-8"));
}
System.out.println(builder.toString());
outputStream=socket.getOutputStream();
outputStream.write("Welcome to the new world!".getBytes("UTF-8"));
} catch (UnknownHostException e) {
System.out.print("null localHost");
} finally {
if (inputStream != null) {
inputStream.close();
}if (outputStream!=null){
outputStream.close();
}
if (socket!=null){
socket.close();
}if (serverSocket!=null){
serverSocket.close();
}
}
}
}
客户端代码:
package com.example.admin.socket;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
public class MainActivity extends AppCompatActivity {
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView=findViewById(R.id.textview);
Button button=findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread() {
@Override
public void run() {
super.run();
try {
call();
}catch (IOException e){
Log.e("io","io close failed");
}
}
}.start();
}
});
}
public void call()throws IOException{
final StringBuilder builder=new StringBuilder();
Socket socket=null;
OutputStream out=null;
InputStream in=null;
try {
socket = new Socket("xxx.xxx.x.x", 5050);
out = socket.getOutputStream();
out.write("Hello World".getBytes( "UTF-8"));
//经过shutdownOutput高速服务器已经发送完数据,后续只能接受数据
socket.shutdownOutput();
in=socket.getInputStream();
byte[] bytes=new byte[1024];
int length;
while ((length=in.read(bytes))!=-1){
builder.append(new String(bytes,0,length,"UTF-8"));
}
runOnUiThread(new Runnable() {
@Override
public void run() {
textView.setText(builder.toString());
}
});
}catch (UnknownHostException e){
Log.e("socket", "create socket failed");
} catch (IOException e) {
Log.e("io","outputstream problem ");
}finally {
if (out!=null){
out.close();
}if (in!=null){
in.close();
}
if (socket!=null){
socket.close();
}
}
}
}
android客户端显示 :Welcome to the new world!
其实这个问题仍是比较重要的,正常来讲,客户端打开一个输出流,若是不作约定,也不关闭它,那么服务端永远不知道客户端是否发送完消息,那么服务端会一直等待下去,直到读取超时。因此怎么告知服务端已经发送完消息就显得特别重要。android
当Socket关闭的时候,服务端就会收到响应的关闭信号,那么服务端也就知道流已经关闭了,这个时候读取操做完成,就能够继续后续工做。算法
可是这种方式有一些缺点编程
调用Socket的shutdownOutput()方法,底层会告知服务端我这边已经写完了,那么服务端收到消息后,就能知道已经读取完消息,若是服务端有要返回给客户的消息那么就能够经过服务端的输出流发送给客户端,若是没有,直接关闭Socket。数组
这种方式经过关闭客户端的输出流,告知服务端已经写完了,虽然能够读到服务端发送的消息,可是仍是有一点点缺点:缓存
这个缺点,在访问频率比较高的状况下将是一个须要优化的地方。安全
这种方式的用法,就是双方约定一个字符或者一个短语,来当作消息发送完成的标识,一般这么作就须要改造读取方法。服务器
假如约定单端的一行为end,表明发送完成,例以下面的消息,end则表明消息发送完成网络
static final String end="bye";
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); }
能够看见,服务端不只判断是否读到了流的末尾,还判断了是否读到了约定的末尾。
这么作的优缺点以下:
这个时候是否是很纠结,最大的固然是最保险的,可是真的有必要选择最大的吗,其实若是你稍微了解一点UTF-8的编码方式,那么你就应该能想到为何必定要固定表示长度字节的长度呢,咱们可使用变长方式来表示长度的表示,好比:
若是用做命名发送,两个字节就够了,若是还不放心4个字节基本就能知足你的全部要求,下面的例子咱们将采用2个字节表示长度,目的只是给你一种思路,让你知道有这种方式来获取消息的结尾:
package yiwangzhibujian.waitreceive2; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; public class SocketServer { public static void main(String[] args) throws Exception { // 监听指定的端口 int port = 55533; ServerSocket server = new ServerSocket(port); // server将一直等待链接的到来 System.out.println("server将一直等待链接的到来"); Socket socket = server.accept(); // 创建好链接后,从socket中获取输入流,并创建缓冲区进行读取 InputStream inputStream = socket.getInputStream(); byte[] bytes; // 由于能够复用Socket且能判断长度,因此能够一个Socket用到底 while (true) { // 首先读取两个字节表示的长度 int first = inputStream.read(); //若是读取的值为-1 说明到了流的末尾,Socket已经被关闭了,此时将不能再去读取 if(first==-1){ break; } int second = inputStream.read(); int length = (first << 8) + second; // 而后构造一个指定长的byte数组 bytes = new byte[length]; // 而后读取指定长度的消息便可 inputStream.read(bytes); System.out.println("get message from client: " + new String(bytes, "UTF-8")); } inputStream.close(); socket.close(); server.close(); } }
此处的读取步骤为,先读取两个字节的长度,而后读取消息,客户端为:
package yiwangzhibujian.waitreceive2; import java.io.OutputStream; import java.net.Socket; public class SocketClient { public static void main(String args[]) throws Exception { // 要链接的服务端IP地址和端口 String host = "127.0.0.1"; int port = 55533; // 与服务端创建链接 Socket socket = new Socket(host, port); // 创建链接后得到输出流 OutputStream outputStream = socket.getOutputStream(); String message = "你好 yiwangzhibujian"; //首先须要计算得知消息的长度 byte[] sendBytes = message.getBytes("UTF-8"); //而后将消息的长度优先发送出去 outputStream.write(sendBytes.length >>8); outputStream.write(sendBytes.length); //而后将消息再次发送出去 outputStream.write(sendBytes); outputStream.flush(); //==========此处重复发送一次,实际项目中为多个命名,此处只为展现用法 message = "第二条消息"; sendBytes = message.getBytes("UTF-8"); outputStream.write(sendBytes.length >>8); outputStream.write(sendBytes.length); outputStream.write(sendBytes); outputStream.flush(); //==========此处重复发送一次,实际项目中为多个命名,此处只为展现用法 message = "the third message!"; sendBytes = message.getBytes("UTF-8"); outputStream.write(sendBytes.length >>8); outputStream.write(sendBytes.length); outputStream.write(sendBytes); outputStream.close(); socket.close(); } }
客户端要多作的是,在发送消息以前先把消息的长度发送过去。
这种事先约定好长度的作法解决了以前提到的种种问题,Redis的Java客户端Jedis就是用这种方式实现的这种方式的缺点:
固然若是是须要服务器返回结果,那么也依然使用这种方式,服务端也是先发送结果的长度,而后客户端进行读取。固然如今流行的就是,长度+类型+数据模式的传输方式。
在上面的例子中,服务端仅仅只是接受了一个Socket请求,并处理了它,而后就结束了,可是在实际开发中,一个Socket服务每每须要服务大量的Socket请求,那么就不能再服务完一个Socket的时候就关闭了,这时候能够采用循环接受请求并处理的逻辑:
新手写法,有严重问题,不推荐:
package yiwangzhibujian.multiserver; import java.io.IOException; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; public class SocketServer { public static void main(String args[]) throws IOException { // 监听指定的端口 int port = 55533; ServerSocket server = new ServerSocket(port); // server将一直等待链接的到来 System.out.println("server将一直等待链接的到来"); while(true){ Socket socket = server.accept(); // 创建好链接后,从socket中获取输入流,并创建缓冲区进行读取 InputStream inputStream = socket.getInputStream(); byte[] bytes = new byte[1024]; int len; StringBuilder sb = new StringBuilder(); while ((len = inputStream.read(bytes)) != -1) { // 注意指定编码格式,发送方和接收方必定要统一,建议使用UTF-8 sb.append(new String(bytes, 0, len, "UTF-8")); } System.out.println("get message from client: " + sb); inputStream.close(); socket.close(); } } }
服务端每次接收到客户端的请求后,都会建立一个新的线程去处理,而jvm的线程数量过可能是,服务端处理速度会变慢,并且当一个请求的处理比较耗时的时候,后面的请求将被阻塞。
咱们能够用线程池解决。
线程池的优势:
线程复用,建立线程耗时,回收线程慢
防止短期内高并发,指定线程池大小,超过数量将等待,方式短期建立大量线程致使资源耗尽,服务挂掉
服务端代码以下:
package com.example.admin.server;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class thread extends Thread {
private Socket socket;
public thread(Socket socket){
this.socket=socket;
}
@Override
public void run() {
try {
System.out.println("当前线程:"+Thread.currentThread().getName());
InputStream inputStream = socket.getInputStream();
byte[] bytes;
while (true) {
int first = inputStream.read();
if (first == -1) {
break;
}
int second = inputStream.read();
int length = (first >> 8) + second;
bytes = new byte[length];
inputStream.read(bytes);
System.out.println(new String(bytes, "UTF-8"));
}
OutputStream outputStream = socket.getOutputStream();
outputStream.write("Welcome to the new world!".getBytes("UTF-8"));
inputStream.close();
outputStream.close();
} catch (IOException e) {
System.out.print("null localHost");
}
}
}
package com.example.admin.server;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyClass {
public static void main(String[] arugs) throws IOException {
//线程过多,渣电脑当心卡死...
ExecutorService service = Executors.newFixedThreadPool(3);
ServerSocket serverSocket = new ServerSocket(5050);
InetAddress address = InetAddress.getLocalHost();
String ip = address.getHostAddress();
System.out.println("服务端ip地址:" + ip);
Socket socket = null;
while (true) {
socket = serverSocket.accept();
service.execute(new thread(socket));
}
}
}
客户端代码同上
屡次点击发送
控制台输出:
服务端ip地址:xxx.xxx.x.x
当前线程:pool-1-thread-1
one
two
当前线程:pool-1-thread-2
one
two
当前线程:pool-1-thread-3
one
two
当前线程:pool-1-thread-1
one
two
体详细的解释能够参照下面。
其实若是常常看有关网络编程的源码的话,就会发现Socket仍是有不少设置的,能够学着用,可是仍是要有一些基本的了解比较好。下面就对Socket的Java API中涉及到的进行简单讲解。首先呢Socket有哪些能够设置的选项,其实在SocketOptions接口中已经都列出来了:
服务端绑定端口是能够理解的,由于要监听指定的端口,可是客户端为何要绑定端口,说实话我以为这么作的人有点2,或许有的网络安全策略配置了端口访出,使用户只能使用指定的端口,那么这样的配置也是挺2的,直接说就能够不要留面子。
固然首先要理解的是,若是没有指定端口的话,Socket会自动选取一个能够用的端口,不用瞎操心的。
可是你非得指定一个端口也是能够的,作法以下,这时候就不能用Socket的构造方法了,要一步一步来:
// 要链接的服务端IP地址和端口 String host = "localhost"; int port = 55533; // 与服务端创建链接 Socket socket = new Socket(); socket.bind(new InetSocketAddress(55534)); socket.connect(new InetSocketAddress(host, port));
这样作就能够了,可是当这个程序执行完成之后,再次执行就会报,端口占用异常:
java.net.BindException: Address already in use: connect
明明上一个Socket已经关闭了,为何再次使用还会说已经被占用了呢?若是你是用netstat 命令来查看端口的使用状况:
netstat -n|findstr "55533"
TCP 127.0.0.1:55534 127.0.0.1:55533 TIME_WAIT
简单来讲,当链接主动关闭后,端口状态变为TIME_WAIT,其余程序依然不能使用这个端口,防止服务端由于超时从新发送的确认链接断开对新链接的程序形成影响。
TIME_WAIT的时间通常有底层决定,通常是2分钟,还有1分钟和30秒的。
因此,客户端不要绑定端口,不要绑定端口,不要绑定端口。
读超时这个属性仍是比较重要的,当Socket优化到最后的时候,每每一个Socket链接会一直用下去,那么当一端由于异常致使链接没有关闭,另外一方是不该该持续等下去的,因此应该设置一个读取的超时时间,当超过指定的时间后,尚未读到数据,就假定这个链接无用,而后抛异常,捕获异常后关闭链接就能够了,调用方法为:
public void setSoTimeout(int timeout) throws SocketException
timeout - 指定的以毫秒为单位的超时值。设置0为持续等待下去。建议根据网络环境和实际生产环境选择。
这个选项设置的值将对如下操做有影响:
当须要判断一个Socket是否可用的时候,不能简简单单判断是否为null,是否关闭,下面给出一个比较全面的判断Socket是否可用的表达式,这是根据Socket自身的一些状态进行判断的,它的状态有:
socket != null && socket.isBound() && !socket.isClosed() && socket.isConnected()&& !socket.isInputShutdown() && !socket.isOutputShutdown()
建议如此使用,但这只是第一步,保证Socket自身的状态是可用的,
可是当链接正常建立后,上面的属性若是不调用本方相应的方法是不会改变的,也就是说若是网络断开、服务器主动断开,Java底层是不会检测到链接断开并改变Socket的状态,
因此,真实的检测链接状态仍是得经过额外的手段,有两种方式。
双方须要约定,什么样的消息属于心跳包,什么样的消息属于正常消息,假设你看了上面的内容就容易理解了,咱们定义前两个字节为消息的长度,那么咱们就能够定义第3个字节为消息的属性,能够指定一位为消息的类型,1为心跳,0为正常消息。那么要作的有以下:
具体的编码再也不贴出,本身实现便可。
Socket自带一种模式,那就是发送紧急数据,这有一个前提,那就是服务端的OOBINLINE不能设置为true,它的默认值是false。
OOBINLINE的true和false影响了什么:
发送紧急数据经过调用Socket的方法:
socket.sendUrgentData(0);
发送数据任意便可,由于OOBINLINE为false的时候,服务端会丢弃掉紧急数据。
当发送紧急数据报错之后,咱们就会知道链接不通了。
经过上面的两种方式已经能够判断出链接是否可用,而后咱们就能够进行后续操做,但是请你们认真考虑下面的问题:
若是你认真考虑了上面的问题,那么你就会以为发送心跳包彻底是没有必要的操做,经过发送心跳包来判断链接是否可用是经过捕获异常来判断的。那么咱们彻底能够在发送消息报出IO异常的时候,在异常中从新发送一次便可,这两种方式的编码有什么不一样呢,下面写一写伪代码。
提早检测链接是否可用:
//有一个链接中的socket Socket socket=... //要发送的数据 String data=""; try{ //发送心跳包或者紧急数据,来检测链接的可用性 }catch (Excetption e){ //打印日志,并重连Socket socket=new Socket(host,port); } socket.write(data);
直接发送数据,出异常后从新链接再次发送:
//有一个链接中的socket Socket socket=... //要发送的数据 String data=""; try{ socket.write(data); }catch (Excetption e){ //打印日志,并重连Socket socket=new Socket(host,port); socket.write(data); }
经过比较能够发现两种方式的特色,如今简单介绍下:
但愿你们认真考虑,作出本身的选择。
首先,建立Socket时,默认是禁止的,设置true有什么做用呢,Java API中是这么介绍的:
关闭 TCP 链接时,该链接可能在关闭后的一段时间内保持超时状态(一般称为 TIME_WAIT 状态或 2MSL 等待状态)。对于使用已知套接字地址或端口的应用程序而言,若是存在处于超时状态的链接(包括地址和端口),可能不能将套接字绑定到所需的 SocketAddress 上。
使用 bind(SocketAddress) 绑定套接字前启用 SO_REUSEADDR 容许在上一个链接处于超时状态时绑定套接字。
通常是用在绑定端口的时候使用,可是通过个人测试建议以下:
Java API的介绍是:启用/禁用具备指定逗留时间(以秒为单位)的 SO_LINGER。最大超时值是特定于平台的。 该设置仅影响套接字关闭。
你们都是这么说的,当调用Socket的close方法后,没有发送的数据将再也不发送,设置这个值的话,Socket会等待指定的时间发送完数据包。说实话,通过我简单的测试,对于通常数据量来讲,几十K左右,即使直接关闭Socket的链接,服务端也是能够收到数据的。
因此对于通常应用不必设置这个值,当数据量发送过大抛出异常时,再来设置这个值也不晚。那么到达逗留超时值时,套接字将经过 TCP RST 强制性 关闭。启用超时值为零的选项将当即强制关闭。若是指定的超时值大于 65,535,则其将被减小到 65,535。
综上所述,不建议绑定端口,也不必设置ReuseAddress,固然ReuseAddress的底层仍是和硬件有关系的,或许在你的机器上测试结果和我不同,如果如此和平台相关性差别这么大配置更是不建议使用了。
通常来讲当客户端想服务器发送数据的时候,会根据当前数据量来决定是否发送,若是数据量太小,那么系统将会根据Nagle 算法(暂时还没研究),来决定发送包的合并,也就是说发送会有延迟,这在有时候是致命的,好比说对实时性要求很高的消息发送,在线对战游戏等,即使数据量很小也要求当即发送,若是稍有延迟就会感受到卡顿,默认状况下Nagle 算法是开启的,因此若是不打算有延迟,最好关闭它。这样一旦有数据将会当即发送而不会写入缓冲区。
可是对延迟要求不是特别高下仍是可使用的,仍是能够提高网络传输效率的。
默认都是8K,若是有须要能够修改,经过相应的set方法。不建议修改的过小,设置过小数据传输将过于频繁。太大了将会形成消息停留。
不过我对这个通过测试后有如下结论:
虽说当设置链接链接的读超时为0,即无限等待时,Socket不会被主动关闭,可是总会有莫名其妙的软件来检测你的链接是否有数据发送,长时间没有数据传输的链接会被它们关闭掉。
所以经过设置这个选项为true,能够有以下效果:当2个小时(具体的实现而不一样)内在任意方向上都没有跨越套接字交换数据,则 TCP 会自动发送一个保持存活的消息到对面。将会有如下三种响应:
因此对于构建长时间链接的Socket仍是配置上SO_KEEPALIVE比较好。
这个异常的含义是,我正在写数据的时候,你把链接给关闭了。这个异常在通常正常的编码是不会出现这个异常的,由于用户一般会判断是否读到流的末尾了,读到末尾才会进行关闭操做,若是出现这个异常,那就检查一下判断是否读到流的末尾逻辑是否正确。
使用Socket通讯的时候,或多或少都听过拆包和黏包,若是没听过而去贸然编程那么偶尔就会碰到一些莫名其妙的问题,全部有这方面的知识仍是比较重要的,至少知道怎么发生,怎么防范。
如今先简单说明下拆包和黏包的缘由:
首先能够明确的是,大部分状况下咱们是不但愿发生拆包和黏包的(若是但愿发生,什么都去作便可),那么怎么去避免呢,下面进行详解?
首先咱们应该正确看待黏包,黏包其实是对网络通讯的一种优化,假如说上层只发送一个字节数据,而底层却发送了41个字节,其中20字节的I P首部、 20字节的T C P首部和1个字节的数据,并且发送完后还须要确认,这么作浪费了带宽,量大时还会形成网络拥堵。固然它仍是有必定的缺点的,就是由于它会合并一些包会致使数据不能当即发送出去,会形成延迟,若是能接受(通常延迟为200ms),那么仍是不建议关闭这种优化,若是由于黏包会形成业务上的错误,那么请改正你的服务端读取算法(协议),由于即使不发生黏包,在服务端缓存区也可能会合并起来一块儿提交给上层,推荐使用长度+类型+数据模式。
若是不但愿发生黏包,那么经过禁用TCP_NODELAY便可,Socket中也有相应的方法:
void setTcpNoDelay(boolean on)
经过设置为true便可防止在发送的时候黏包,可是当发送的速率大于读取的速率时,在服务端也会发生黏包,即因服务端读取过慢,致使它一次可能读取多个包。
这个问题应该引发重视,在TCP/IP详解中说过:最大报文段长度(MSS)表示TCP传往另外一端的最大块数据的长度。当一个链接创建时,链接的双方都要通告各自的 MSS。客户端会尽可能知足服务端的要求且不能大于服务端的MSS值,当没有协商时,会使用值536字节。虽然看起来MSS值越大越好,可是考虑到一些其余状况,这个值仍是不太好肯定,具体详见《TCP/IP详解 卷1:协议》。
如何应对拆包,其实在上面内容已经介绍过了,那就是如何代表发送完一条消息了,对于已知数据长度的模式,能够构造相同大小的数组,循环读取,示例代码以下:
int length=1024;//这个是读取的到数据长度,现假定1024 byte[] data=new byte[1024]; int readLength=0; while(readLength<length){ int read = inputStream.read(data, readLength, length-readLength); readLength+=read; }
这样当循环结束后,就能读取到完整的一条数据,而不须要考虑拆包了。
基于UDP协议的Socket的使用
服务端
package com.example.admin.server;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketAddress;
public class MyClass {
public static void main(String[] args) throws IOException {
DatagramSocket socket = null;
try {
socket = new DatagramSocket(8888);
System.out.println("服务器开始监听消息");
} catch (Exception e) {
e.printStackTrace();
}
while (true) {
byte data[] = new byte[1024];
DatagramPacket packet = new DatagramPacket(data, data.length);
socket.receive(packet);
String result = new String(packet.getData(), packet.getOffset(), packet.getLength());
System.out.println("客户端说: " + result);
HandleThread handleThread = new HandleThread(socket,packet);
handleThread.setPriority( 3 );
handleThread.start();
}
}
static class HandleThread extends Thread {
private DatagramSocket mSocket;
private DatagramPacket packet;
public HandleThread(DatagramSocket mSocket ,DatagramPacket packet) {
super();
this.mSocket=mSocket;
this.packet=packet;
}
@Override
public void run() {
try {
byte[] sendData = "服务端说:Welcome to the new world!".getBytes("UTF-8");
SocketAddress remoteAddress = packet.getSocketAddress();
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, remoteAddress);
mSocket.send(sendPacket);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
android客户端
package com.example.admin.socket;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class MainActivity extends AppCompatActivity {
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView=findViewById(R.id.textview);
Button button=findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread() {
@Override
public void run() {
super.run();
try {
call();
}catch (IOException e){
Log.e("io","io close failed");
}
}
}.start();}
});
}
public void call()throws IOException {
DatagramSocket mSocket=null;
try {
// 1.初始化DatagramSocket
mSocket = new DatagramSocket();
mSocket = new DatagramSocket();
InetAddress address = InetAddress.getByName("xxx.xxx.x.x");//就是前面获取的服务器IP地址
int port=8888;
String sendData = "hello world";
byte data[] = sendData.getBytes("UTF-8");
DatagramPacket packet = new DatagramPacket(data, data.length, address, 8888);
mSocket.send(packet);
final byte[] receiveData = new byte[1024];
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
mSocket.receive(receivePacket);
runOnUiThread(new Runnable() {
@Override
public void run() {
textView.setText(new String(receiveData));
}
});
} catch (Exception e) {
e.printStackTrace();
System.out.println(e.getMessage());
} finally {
if (mSocket != null) {
mSocket.close();
}
}
}
}
控制台输出:服务器开始监听消息 客户端说: hello world 客户端说: hello world文章大量内容来自:https://www.cnblogs.com/yiwangzhibujian/p/7107785.html