Java,在Windows平台上使用Socket.sendUrgentData() 来检查连接有效性是不可靠的

在Windows平台上使用TCP紧急数据功能可能因不同操作系统对RFC793和RFC1122的不同解释而变得不可靠。此外,Windows仅支持单字节的带外数据,这可能导致数据丢失。本文探讨了这些限制及其实现上的复杂性。

在Windows平台上使用Socket.sendUrgentData() 来检查连接有效性是不可靠的。除非使用OOBInline。


原文:http://www.serverframework.com/asynchronousevents/2011/10/out-of-band-data-and-overlapped-io.html

TCP Urgent Data

In TCP out of band data is implemented in terms of 'urgent data' using the URG bit and the Urgent Pointer, see here, however, there are two conflicting descriptions of how this works, RFC 793 which details TCP says that the Urgent Pointer indicates the byte that follows the urgent data but RFC 1122 corrects this and states that the Urgent Pointer indicates the final byte of urgent data. This leads to interoperability issues if one peer uses the RFC 793 definition and the other uses the RFC 1122 definition. The Windows documentation for the standard TCP Winsock Provider claims that it operates as BSD does however this can be changed using the TCP_EXPEDITED_1122 socket option if the Winsock provider supports it. There's more compatibility complexity in that Windows only supports a single byte of out of band data whereas RFC 1122 specifies that TCP MUST support sequences of urgent data bytes of any length. Windows also doesn't specify how or if it will buffer subsequent out of band data, so if you are slow in reading a byte of urgent data and another byte of urgent data arrives then one of the bytes may be lost; though our tests have shown that Windows does buffer urgent data. This all makes the use of out of band signalling using urgent data somewhat unreliable on Windows with TCP.

The unreliability shows itself if you're using separate OOB reads using MSG_OOB. In this instance you may get the 'wrong' byte of OOB data if you are dealing with a peer that implements the altenative RFC. This wouldn't be a problem if the byte hadn't been removed from the normal data stream and so you end up with an incorrect byte in the normal data stream (the intended OOB byte) and a missing byte, which has been treated as OOB data. This doesn't cause problems for the two traditional users of TCP Urgent Data, Telnet and Rlogin as they simply use the Urgent data notification to alert the server that urgent data is present in the data stream and then read and discard all of the normal data in the stream until they can read the urgent data. See here for how this facility is used in the Telnet protocol's synch function. It's practically impossible to implement this functionality with overlapped I/O as you don't want the urgent data removed from the data stream, so you need to operate in SO_OOBINLINE mode and yet when in that mode you will never be notified that urgent data exists. To implement the telnet model with overlapped I/O you pretty much need to always read all inbound data and buffer it in your server rather than allowing it to buffer in the TCP stack and allowing TCP flow control to prevent the client from sending more until you're ready.

So, if you want to use out of band data with TCP with overlapped I/O you need to remember five things:
  • Out of band data in TCP on Windows when interoperating with other, non-Windows, operating systems is likely to be unreliable due to differences between RFC 793 and RFC 1122.
  • Expecting to send more than a single byte of out of bound data is likely not to work.
  • Your out of band data may get "lost" if you send out of band data faster than the receiver is processing it.
  • To read out of band data using overlapped I/O you need to post a special WSARecv() with MSG_OOB set in the flags.
  • Real out of band communication using TCP is better achieved with a separate 'control' connection rather than using TCP's Urgent data function via MSG_OOB.
Despite all of this, The Server Framework will support out of band data in the next major release. By default OOB data will be disabled and we'll abort connections that use it. You can also decide to enable OOB inline which effectively disables OOB data from the receiver's point of view and simply keeps the out of band data in the main data stream. Or, you can enable async OOB which will read OOB data using an optional special purpose overlapped read with MSG_OOB set.

package com.intl.SmartWeather; import android.app.Activity; import android.app.AlertDialog; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.widget.Button; import android.widget.CheckBox; import android.widget.EditText; import android.widget.TextView; import java.text.DecimalFormat; import java.util.Timer; import java.util.TimerTask; public class MainActivity extends Activity { private static final String TAG = "MainActivity"; private static final int MAXBUFFLEN = 1024; private static final int INTERVAL4PROCESSINGBUFFDATA = 100; private Timer mTimer; private Handler mHandler; private boolean bDataLock = false; private int iDataIn = 0; private int iDataOut = 0; private byte[] bytesDataRecBuff = new byte[MAXBUFFLEN]; private byte bFanIndex = (byte) 0x01; private CheckBox cb_temp_fan; private EditText edit_temp_fan; private boolean fanStatus = false; private Service_Socket mService; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mService = ((Service_Socket.LocalBinder) service).getService(); Service_Socket.strMessageFor = TAG; //切换串口 new Timer().schedule(new TimerTask() { public void run() { switchSerial(1, 115200); } }, 100); //切换模块 new Timer().schedule(new TimerTask() { public void run() { switchModule(1); } }, 200); } @Override public void onServiceDisconnected(ComponentName name) { mService = null; } }; private ServiceMsgReceiver mServiceMsgRecv; public class ServiceMsgReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { ********************补全*********************** } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initEnv(); bindService(new Intent(this, Service_Socket.class), mConnection, Context.BIND_AUTO_CREATE); cb_temp_fan = (CheckBox) findViewById(R.id.cb_link_temp_fan); edit_temp_fan = (EditText) findViewById(R.id.edit_link_temp_fan); Button btnFanOn = (Button) findViewById(R.id.btn_fan_on); Button btnFanOff = (Button) findViewById(R.id.btn_fan_off); btnFanOn.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { FanOn(); } }); btnFanOff.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { FanOff(); } }); } @Override public void onResume() { Service_Socket.strMessageFor = TAG; super.onResume(); } @Override public void onDestroy() { if (mTimer != null) { mTimer.cancel(); mTimer = null; } super.onDestroy(); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { new AlertDialog.Builder(MainActivity.this).setIcon(R.mipmap.ic_launcher).setTitle("退出").setMessage("确认退出吗?") .setPositiveButton("确认", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialoginterface, int i) { finish(); } }).setNegativeButton("取消", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialoginterface, int i) { } }).show(); return true; } return super.onKeyDown(keyCode, event); } //初始化 private void initEnv() { //初始化数据缓冲标志 bDataLock = false; iDataIn = 0; iDataOut = 0; // 注册广播接收器******************补全************************ mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case 1: processingData(); break; } super.handleMessage(msg); } }; mTimer = new Timer(); mTimer.schedule(new TimerTask() { @Override public void run() { Message msg = mHandler.obtainMessage(); msg.what = 1; mHandler.sendMessage(msg); } }, 500, INTERVAL4PROCESSINGBUFFDATA); } //接收数据到数据缓冲区内 private void receiveData(byte[] bRecData) { //Luo Test //Log.i(TAG + ":Data", bytes2HexString(bRecData)); int i; int iDataLen = bRecData.length; if (bDataLock == false) { bDataLock = true; if (iDataIn + iDataLen <= MAXBUFFLEN) { for (i = 0; i < iDataLen; i++) { bytesDataRecBuff[iDataIn + i] = bRecData[i]; } iDataIn += iDataLen; } else { for (i = iDataIn; i < MAXBUFFLEN; i++) { bytesDataRecBuff[i] = bRecData[i - iDataIn]; } for (i = 0; i < iDataLen - MAXBUFFLEN + iDataIn; i++) { bytesDataRecBuff[i] = bRecData[i + MAXBUFFLEN - iDataIn]; } iDataIn = iDataLen - MAXBUFFLEN + iDataIn; } bDataLock = false; } } //读取当前缓冲区中的数据 private int validReceiveLen() { if (iDataOut < iDataIn) { return (iDataIn - iDataOut); } else if (iDataOut > iDataIn) { return (MAXBUFFLEN - iDataOut + iDataIn); } return 0; } //返回后面第iNum有效数据的位置 private int dataOutAdd(int iNum) { int ret = 0; if (iDataOut + iNum < MAXBUFFLEN) { ret = iDataOut + iNum; } else if (iDataOut + iNum > MAXBUFFLEN) { ret = iDataOut + iNum - MAXBUFFLEN; } return ret; } //从缓冲区内读出有效数据 private void processingData() { if (bDataLock == false) { bDataLock = true; int iPacketLen = 0; int i, iValidLen, iReadPos; while (iDataIn != iDataOut) { if (bytesDataRecBuff[iDataOut] == (byte) 0xAA) {// 判断是否为包头 iValidLen = validReceiveLen();// 包含有效数据长度 if (iValidLen < 8) { // 有效长度太短 bDataLock = false; return; } if (iDataOut + 1 >= MAXBUFFLEN) { iPacketLen = bytesDataRecBuff[iDataOut + 1 - MAXBUFFLEN] & 0xFF; } else { iPacketLen = bytesDataRecBuff[iDataOut + 1] & 0xFF; } if (iValidLen < iPacketLen) { // 包完整 bDataLock = false; return; } if (iPacketLen > 7 && iPacketLen < 40) { // 数据长度正常 iReadPos = iDataOut; byte[] buf = new byte[iPacketLen]; for (i = 0; i < iPacketLen; i++) {// 读出包并进行校验和计算 buf[i] = bytesDataRecBuff[iReadPos++]; if (iReadPos >= MAXBUFFLEN) iReadPos = 0; } if (checkSummationVerify(buf)) { iDataOut = iReadPos; processingPacket(buf); bDataLock = false; return; } } } iDataOut++; if (iDataOut >= MAXBUFFLEN) { iDataOut = 0; } } bDataLock = false; } } //处理接收的数据包 private void processingPacket(byte[] Packet) { ***************************补全********************************************* } private void ShowAp(byte[] bytes) { TextView txt_ap = (TextView) findViewById(R.id.txt_ap); int iValue1 = ((bytes[1] & 0xFF) * 256 + (bytes[2] & 0xFF)) * 256 * 256 + (bytes[3] & 0xFF) * 256 + (bytes[4] & 0xFF); String str_ap = "大气压力:" + String.valueOf(iValue1) + " Pa"; txt_ap.setText(str_ap); } private void ShowRain(byte[] bytes) { TextView txt_rain = (TextView) findViewById(R.id.txt_rain); int iValue = bytes[1] & 0xFF; if (iValue == 0) { txt_rain.setText("无雨雪"); } else if (iValue == 1) { txt_rain.setText("有雨雪"); } } private void ShowRain2(byte[] bytes) { TextView txt_rain = (TextView) findViewById(R.id.txt_rain); int iValue = bytes[5] & 0xFF; if (iValue == 0) { txt_rain.setText("无雨雪"); } else if (iValue == 1) { txt_rain.setText("有雨雪"); } } private void ShowIllumination(byte[] bytes) { TextView txt_illu = (TextView) findViewById(R.id.txt_illu); int iValue = (bytes[1] & 0xFF) * 256 + (bytes[2] & 0xFF); String str = "光照强度:" + String.valueOf(iValue) + " Lux"; txt_illu.setText(str); } private void ShowTempHumi(byte[] bytes) { //*******************补全对温度数据的解析************************************* if (cb_temp_fan.isChecked()) { String strEditTemp = edit_temp_fan.getText().toString().trim(); if (!strEditTemp.equals("")) { int iEdittemp = Integer.parseInt(strEditTemp); if (iTemp / 100.0 > iEdittemp) { if (fanStatus == false) { FanOn(); } } else { if (fanStatus == true) { FanOff(); } } } } } private void FanOn() { sendControlCmd();//*************补全要传递的参数************************************* fanStatus = true; } private void FanOff() { sendControlCmd();//*************补全要传递的参数************************************* fanStatus = false; } //获取校验结果******************补全********************** private boolean checkSummationVerify(byte[] bytes) { } //设置校验位********************补全**************************** private void setSummationVerify(byte[] bytes) { } //二进制数组转字符串 private String bytes2HexString(byte[] bytes) { String ret = ""; for (int i = 0; i < bytes.length; i++) { String hex = Integer.toHexString(bytes[i] & 0xFF); if (hex.length() == 1) { hex = '0' + hex; } ret += hex.toUpperCase(); } return ret; } //切换串口设置 public void switchSerial(int iWhich, int iBaudrate) { byte[] bytes = new byte[4]; bytes[0] = (byte) 0x4C; bytes[1] = (byte) 0x4B; bytes[2] = (byte) iWhich; switch (iBaudrate) { case 4800: bytes[3] = (byte) 0x00; break; case 9600: bytes[3] = (byte) 0x01; break; case 38400: bytes[3] = (byte) 0x02; break; case 57600: bytes[3] = (byte) 0x03; break; case 115200: bytes[3] = (byte) 0x04; break; } mService.socketSend(bytes); } //切换模块连接 public void switchModule(int iWhich) { byte[] bytes = new byte[7]; bytes[0] = (byte) 0xAA; bytes[1] = (byte) bytes.length; bytes[2] = (byte) 0x01; bytes[3] = (byte) 0x00; bytes[4] = (byte) 0x01; bytes[5] = (byte) iWhich; setSummationVerify(bytes); mService.socketSend(bytes); } //发送控制命令(带一个参数) private void sendControlCmd(byte bWsnType, byte bCtrlerType, byte bIndex, byte bValue) { byte[] bytes = new byte[16]; bytes[0] = (byte) 0xAA; bytes[1] = (byte) bytes.length; bytes[2] = (byte) 0x02; bytes[3] = (byte) 0x00; bytes[4] = (byte) 0x0F; bytes[5] = bWsnType; bytes[6] = (byte) 0x00; bytes[7] = bCtrlerType; bytes[8] = bIndex; bytes[9] = (byte) 0x01; bytes[10] = bValue; setSummationVerify(bytes); mService.socketSend(bytes); } //发送控制命令(带两个参数) private void sendControlCmd(byte bWsnType, byte bCtrlerType, byte bIndex, byte bValue1, byte bValue2) { byte[] bytes = new byte[13]; bytes[0] = (byte) 0xAA; bytes[1] = (byte) bytes.length; bytes[2] = (byte) 0x02; bytes[3] = (byte) 0x00; bytes[4] = (byte) 0x0F; bytes[5] = bWsnType; bytes[6] = (byte) 0x00; bytes[7] = bCtrlerType; bytes[8] = bIndex; bytes[9] = (byte) 0x02; bytes[10] = bValue1; bytes[11] = bValue2; setSummationVerify(bytes); mService.socketSend(bytes); } //发送控制命令(带三个参数) private void sendControlCmd(byte bWsnType, byte bCtrlerType, byte bIndex, byte bValue1, byte bValue2, byte bValue3) { byte[] bytes = new byte[14]; bytes[0] = (byte) 0xAA; bytes[1] = (byte) bytes.length; bytes[2] = (byte) 0x02; bytes[3] = (byte) 0x00; bytes[4] = (byte) 0x0F; bytes[5] = bWsnType; bytes[6] = (byte) 0x00; bytes[7] = bCtrlerType; bytes[8] = bIndex; bytes[9] = (byte) 0x03; bytes[10] = bValue1; bytes[11] = bValue2; bytes[12] = bValue3; setSummationVerify(bytes); mService.socketSend(bytes); } } package com.intl.SmartWeather; import android.app.Service; import android.content.Intent; import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.StrictMode; import android.widget.Toast; import java.io.DataInputStream; import java.io.DataOutputStream; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Service_Socket extends Service { private static final String strIPAddr = "127.0.0.1"; private static final int iPort = 8899; private static final int INTERVAL4TESTSOCKET = 7000; public static Socket socket; public static DataInputStream in; public static DataOutputStream out; public static boolean bSocketflag; public static String strMessageFor; public byte[] recvbuffer = new byte[1024]; private boolean IsRun = true; private byte[] dataSend; private ExecutorService mThreadPool; private Handler mHandler; private Timer mTimer; private final IBinder mBinder = new LocalBinder(); public class LocalBinder extends Binder { public Service_Socket getService() { return Service_Socket.this; } } @Override public IBinder onBind(Intent intent) { return mBinder; } @Override public void onCreate() { //Android 4.0+ the socket communication must be added StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectDiskReads().detectDiskWrites().detectNetwork().penaltyLog().build()); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectLeakedSqlLiteObjects().penaltyLog().penaltyDeath().build()); bSocketflag = false; strMessageFor = "MainActivity"; //create threadpool mThreadPool = Executors.newCachedThreadPool(); socketConnect(); socketRecv(); mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case 1: socketTest(); break; case 2: Toast.makeText(getApplicationContext(), "Connected Succeed", Toast.LENGTH_SHORT).show(); break; case 3: Toast.makeText(getApplicationContext(), "Connected Failed", Toast.LENGTH_SHORT).show(); break; } } }; mTimer = new Timer(); mTimer.schedule(new TimerTask() { @Override public void run() { Message msg = mHandler.obtainMessage(); msg.what = 1; mHandler.sendMessage(msg); } }, 500, INTERVAL4TESTSOCKET); } @Override public void onDestroy() { super.onDestroy(); socketDisconnect(); if (mTimer != null) { mTimer.cancel(); mTimer = null; } IsRun = false; bSocketflag = false; } //Broadcast the socket data public void sendMsgtoActivty(String msg) { // TODO: 实现广播发送功能*************************补全***************************** // 提示:需要创建Intent、设置Action、添加数据、发送广播 } // Thread for disconnect socket public void socketDisconnect() { mThreadPool.execute(new Runnable() { @Override public void run() { if (bSocketflag) { try { in.close(); out.close(); socket.close(); } catch (Exception e) { } } } }); } // Thread for connect socket public void socketConnect() { socketDisconnect(); mThreadPool.execute(new Runnable() { @Override public void run() { try { //*********************补全************************************* // 提示:需要创建Socket、设置超时、连接服务器、获取输入输出流 } catch (Exception e) { bSocketflag = false; Message msg = mHandler.obtainMessage(); msg.what = 3; mHandler.sendMessage(msg); } } }); } public void socketReconnect() { socketDisconnect(); mThreadPool.execute(new Runnable() { @Override public void run() { try { Thread.sleep(100); // TODO: 实现Socket连接逻辑***************补全********************* // 提示:需要创建Socket、设置超时、连接服务器、获取输入输出流 bSocketflag = true; Message msg = mHandler.obtainMessage(); msg.what = 2; mHandler.sendMessage(msg); } catch (Exception e) { bSocketflag = false; Message msg = mHandler.obtainMessage(); msg.what = 3; mHandler.sendMessage(msg); } } }); } // Thread for test socket public void socketTest() { mThreadPool.execute(new Runnable() { @Override public void run() { if (bSocketflag) { try { socket.sendUrgentData(0xFF);// connected test } catch (Exception e) { socketReconnect(); } } } }); } // Thread for send socket public void socketSend(byte[] data) { dataSend = data; socketTest(); mThreadPool.execute(new Runnable() { @Override public void run() { if (bSocketflag) { try { // TODO: 实现数据发送逻辑**************补全***************************** // 提示:将字节数组写入输出流 } catch (Exception e) { } } } }); } // Thread for receive socket public void socketRecv() { mThreadPool.execute(new Runnable() { @Override public void run() { while (IsRun) { try { if (bSocketflag) { int iCount = in.read(recvbuffer); if (iCount != -1) { // TODO: 实现数据接收逻辑*******************补全********************** // 提示:从输入流读取数据,处理接收到的字节数组 } } Thread.sleep(100); } catch (Exception e) { } } } }); } } 对每一部分要补全的代码进行补全,然后哪里补全的进行一下提示,最后输出一下完整代买
最新发布
09-29
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值