socket实现客户端与服务端通信(二)客户端

Android客户端app由3个activity组成,如下图


目录结构如图


menifest.xml文件

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.ouling.Ex_AcontrolPC_A"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" />

    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.INTERNET" >
    </uses-permission>

    <application
        android:allowBackup="true"
        android:icon="@drawable/icon"
        android:label="@string/app_name" >
        <activity
            android:name=".ScanActivity"
            android:theme="@android:style/Theme.Dialog" >
             <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".ReScanActivity"
            android:icon="@drawable/icon"
            android:label="@string/app_name"
            android:theme="@android:style/Theme.Dialog" >
        </activity>
        <activity
            android:name=".MainActivity"
            android:icon="@drawable/icon"
            android:label="@string/app_name"
            android:theme="@android:style/Theme.Dialog" >
        </activity>
    </application>
</manifest>
可以看出,首先进到的是ScanActivity,这个activity是用来扫描当前连接的所在网段内所有设备,看是否有开启30000端口的服务。核心代码如下

<span style="white-space:pre">		</span>class ScanIPThread extends Thread{
			@Override
			public void run() {
				serverIP = myWifi.getServerIp();
				int t = serverIP.lastIndexOf(".")+1;
				resultIP = serverIP.substring(0, t);
				boolean flag = false;
				for(int i=1;i<255;i++){
					try {
						Socket socket = new Socket();
						InetSocketAddress s = new InetSocketAddress(resultIP+i, 30000);
						socket.connect(s,50);
						Message message = new Message();
						message.what=1000;
						message.obj=resultIP+i;
						handler.sendMessage(message);
						flag =true;
						socket.close();
						break;
					} catch (IOException e) {
						handler.sendEmptyMessage(i);
					}
				}
				if(!flag){
					handler.sendEmptyMessage(2000);
				}
				super.run();
			}
<span style="white-space:pre">		</span>}

这个线程就是用来扫描网段内哪些IP开启了30000端口的。为什么要开个子线程?因为主线程是用于图形界面交互的,而这里的操作是阻塞的,如果占用主线程会挂掉。android4.0以上的版本都不允许在主线程中添加耗时操作了。

在Socket中有个方法connect(SocketAddress endpoint,int timeout),endpoint是服务器地址,timeout是超时值,在建立连接或超时之前,连接一直处于阻塞状态。因为要扫描的地址不多,所以可以用for来遍历,如果扫描的地址过多,可能得另想办法了。这里采用的思路是如果连接上了,就把当前服务器的地址记录下来并结束扫描,当连接超时或者连接出异常则继续尝试连接下个地址。看起来貌似没有问题,但是,那么问题来了,怎么知道要扫描那个网段呢??

这就要用到android中的WiFi服务了。WifiManager可以get到DhcpInfo的实例,DhcpInfo里面有个serverAddress保存着连接主机的ip地址,但是这个ip地址是int型的,需要用以下方法解析成*.*.*.*格式

public String FormatString(int value){
		String strValue="";
		byte[] ary = intToByteArray(value);
		for(int i=ary.length-1;i>=0;i--){
			strValue+=(ary[i]&0xFF);
			if(i>0){
				strValue+=".";
			}
		}
		return strValue;
	}
	public byte[] intToByteArray(int value){
		byte[] b=new byte[4];
		for(int i=0;i<4;i++){
			int offset = (b.length-1-i)*8;
			b[i]=(byte) ((value>>>offset)&0xFF);
		}
		return b;
	}

当所以地址都扫描完后还没有找到可连接的地址,则跳到ReScanActivity,可以重新扫描或者退出,上面的图中可以看到。如果找到了就用handler机制将IP发送回来,再用intent传递IP跳转到MainActivity。

<span style="white-space:pre">				</span>case 1000:
				<span style="white-space:pre">	</span>Toast.makeText(ScanActivity.this, "找到可连接PC", Toast.LENGTH_SHORT).show();
				<span style="white-space:pre">	</span>Intent intent = new Intent(ScanActivity.this,MainActivity.class);
				<span style="white-space:pre">	</span>//将可以连接的ip发过去
				<span style="white-space:pre">	</span>intent.putExtra("ip", (String)msg.obj);
				<span style="white-space:pre">	</span>startActivity(intent);
				<span style="white-space:pre">	</span>finish();
				break;

在MainActivity这边接收传过来的IP

Intent intent = getIntent();
connIP = intent.getStringExtra("ip");
有了这个IP,就可以和服务端建立连接了。

开一个线程,与服务端建立连接,然后就一直循环来维持与服务端的持续通信。循环时,首先拿到客户端的输入输出流,当data_putput和发送的内容都不为空时,就将内容写出去,然后用data_input获取服务端发过来的信息,当服务端发来的信息不为空时,就将信息发送回UI主线程,并提示给用户

			try {
				client_socket = new Socket(ip, port);
				while(true){
					data_output = new DataOutputStream(client_socket.getOutputStream());
					data_input = new DataInputStream(client_socket.getInputStream());
					String msg="";
					if ((data_output != null) && (!content.equals(""))) {
						data_output.writeUTF(content);
					}
					msg = data_input.readUTF();
					System.out.println(msg);
					if(msg!=null&&!"".equals(msg)){
						Message message = new Message();
						message.what=1;
						message.obj=msg;
						handler.sendMessage(message);
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
			}finally{
				try {
					if(data_output!=null){
						data_output.close();
					}
					if(data_input!=null){
						data_input.close();
					}
					if(client_socket!=null){
						client_socket=null;
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
				super.run();
			
在handleMessage(Message msg)中的操作,用于提示给用户

			switch (msg.what) {
			case 1:
				Toast.makeText(MainActivity.this, (String)msg.obj, Toast.LENGTH_SHORT).show();
				break;
			default:
				break;
			}
			super.handleMessage(msg);
		
至于发送给服务端的内容,在判断获取用户点击的按钮时就可以设置了

case R.id.btnShutdown:
	final String shutdown = "shutdown";
	if(connThread!=null){
		connThread.interrupt();
	}
	connThread = new ConnThread(connIP,30000,shutdown);
	connThread.start();
break;

到这里,客户端与服务端就可以连通了,但是还有许多需要优化的地方,就先这样吧,希望能给大家带来些灵感。

有几点需要注意的:

1.android模拟器启动应用会报错,用真机测试
2.有时会出现,明明开了服务端,但是客户端扫描不到,原因可能是网络阻塞,可以把扫描时的延时时间调大点,但这样也就增加了扫描耗时


代码下载
app下载,下下来直接可以用

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值