浅谈android Socket 通信及自建ServerSocket服务端常见问题
摘 要:TCP/IP通信协议是可靠的面向连接的网络协议,它在通信两端各建立一个Socket,从而在两端形成网络虚拟链路,进而应用程序可通过可以通过虚拟链路进行通信。Java对于基于TCP协议的网络通信提供了良好的封装,使用Socket对象代表两端的通信接口,通过Socket产生I/O流进行网络通信。
自建ServerSocket服务端时可能因PC与手机平板终端未接入同一路由器,因此无法访问服本地IP,可以尝试以下两种方式解决
关键词: Socket; ServerSocket;本地IP; address
1 Socket与ServerSocket的交互过程
2 Socket涉及到的异常类型
UnknownHostException:主机名或IP错误
ConnectException:服务器拒绝连接、服务器没有启动、超出队列数
SocketTimeoutException:连接超时
BindException:Socket对象无法与制定的本地IP地址或端口绑定
3 ServerSocket创建TCP服务器端
3.1 构造函数
ServerSocket()throws IOException
ServerSocket(int port)throws IOException
ServerSocket(int port, int backlog)throws IOException
ServerSocket(int port, int backlog, InetAddress bindAddr)throws IOException
3.2 注意
1) port服务端要监听的端口;backlog客户端连接请求的队列长度;bindAddr服务端绑定IP
2) 如果端口被占用或者没有权限使用某些端口会抛出BindException错误。譬如1~1023的端口需要管理员才拥有权限绑定。
3)如果设置端口为0,则系统会自动为其分配一个端口;
4) bindAddr用于绑定服务器IP,为什么会有这样的设置呢,譬如有些机器有多个网卡。
5) ServerSocket一旦绑定了监听端口,就无法更改。ServerSocket()可以实现在绑定端口前设置其他的参数。
3.3 示例
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class SimpleServer {
public static void main (String [] args) throws IOException
{
ServerSocket ss= new ServerSocket(30000,10,InetAddress.getByName ("172.18.85.60"));
System.out.println(ss.getInetAddress());
while (true){
Socket s = ss.accept();
OutputStream os = s.getOutputStream();
os.write("您好,你已成功连接了服务器!\n".getBytes("utf-8"));
os.close();
s.close();
}
}
}
4 Socket进行通信
4.1 构造函数
Socket()
Socket(InetAddress address, int port)throws UnknownHostException, IOException
Socket(InetAddress address, int port, InetAddress localAddress, int localPort)throws IOException
Socket(String host, int port)throws UnknownHostException, IOException
Socket(String host, int port, InetAddress localAddress, int localPort)throws IOException
除去第一种不带参数的之外,其它构造函数会尝试建立与服务器的连接。如果失败会抛出IOException错误。如果成功,则返回Socket对象。
InetAddress是一个用于记录主机的类,其静态getHostByName(String msg)可以返回一个实例,其静态方法getLocalHost()也可以获得当前主机的IP地址,并返回一个实例。Socket(String host, int port, InetAddress localAddress, int localPort)构造函数的参数分别为目标IP、目标端口、绑定本地IP、绑定本地端口。
Address:此处IP为开启的虚拟WiFi IP,如下图(2)中的192.168.191.1,因为此方法可保证PC与手机终端接入同一路由器(如果手机终端开的虚拟机则本机IP:172.20.9.4(图3)与虚拟WiFi IP:192.168.191.1都可);
4.2 Socket方法
getInetAddress(); 远程服务端的IP地址
getPort(); 远程服务端的端口
getLocalAddress() 本地客户端的IP地址
getLocalPort() 本地客户端的端口
getInputStream(); 获得输入流
getOutStream(); 获得输出流
值得注意的是,在这些方法里面,最重要的就是getInputStream()和getOutputStream()了。
4.3 Socket状态
isClosed(); //连接是否已关闭,若关闭,返回true;否则返回false
isConnect(); //如果曾经连接过,返回true;否则返回false
isBound(); //如果Socket已经与本地一个端口绑定,返回true;否则返回false
如果要确认Socket的状态是否处于连接中,下面语句是很好的判断方式。
4.4 示例
public class SimpleClient extends Activity
{
private final static String HOST = "localhost";
EditText show;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
show = (EditText) findViewById(R.id.show);
new Thread()
{
@Override
public void run()
{
show.setText("lianjie");
try
{
// 建立连接到远程服务器的Socket
Socket socket = new Socket("172.18.85.60", 30000); //①
//socket.setSoTimeout(10000);
Log.i("本机地址",socket.getLocalAddress().toString());
String string = socket.getRemoteSocketAddress().toString();
System.out.println(string);
// 将Socket对应的输入流包装成BufferedReader
try{
BufferedReader br = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
// 进行普通IO操作
String line = br.readLine();
show.setText("来自服务器的数据:" + line);
// 关闭输入流、socket
br.close();
socket.close();
}
catch(SocketTimeoutException ex)
{
System.out.println("TimeOut!!!");
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
}.start();
}
}
5 多线程示例
1)Server端:
Class 1:
public class MyServer
{
// 定义保存所有Socket的ArrayList
public static ArrayList<Socket> socketList
= new ArrayList<Socket>();
public static void main(String[] args)
throws IOException
{
ServerSocket ss = new ServerSocket(30000);
while(true)
{
// 此行代码会阻塞,将一直等待别人的连接
Socket s = ss.accept();
socketList.add(s);
// 每当客户端连接后启动一条ServerThread线程为该客户端服务
new Thread(new ServerThread(s)).start();
}
}
}
Class 2:
public class ServerThread implements Runnable
{
// 定义当前线程所处理的Socket
Socket s = null;
// 该线程所处理的Socket所对应的输入流
BufferedReader br = null;
public ServerThread(Socket s)
throws IOException
{
this.s = s;
// 初始化该Socket对应的输入流
br = new BufferedReader(new InputStreamReader(
s.getInputStream() , "utf-8")); //②
}
public void run()
{
try
{
String content = null;
// 采用循环不断从Socket中读取客户端发送过来的数据
while ((content = readFromClient()) != null)
{
// 遍历socketList中的每个Socket,
// 将读到的内容向每个Socket发送一次
for (Socket s : MyServer.socketList)
{
OutputStream os = s.getOutputStream();
os.write((content + "\n").getBytes("utf-8"));
}
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
// 定义读取客户端数据的方法
private String readFromClient()
{
try
{
return br.readLine();
}
// 如果捕捉到异常,表明该Socket对应的客户端已经关闭
catch (IOException e)
{
// 删除该Socket。
MyServer.socketList.remove(s); //①
}
return null;
}
}
2)Socket客户端:
Class 1:
public class MultiThreadClient extends Activity
{
// 定义界面上的两个文本框
EditText input;
TextView show;
// 定义界面上的一个按钮
Button send;
Handler handler;
// 定义与服务器通信的子线程
ClientThread clientThread;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
input = (EditText) findViewById(R.id.input);
send = (Button) findViewById(R.id.send);
show = (TextView) findViewById(R.id.show);
handler = new Handler() //①
{
@Override
public void handleMessage(Message msg)
{
// 如果消息来自于子线程
if (msg.what == 0x123)
{
// 将读取的内容追加显示在文本框中
show.append("\n" + msg.obj.toString());
}
}
};
clientThread = new ClientThread(handler);
// 客户端启动ClientThread线程创建网络连接、读取来自服务器的数据
new Thread(clientThread).start(); //①
send.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
try
{
// 当用户按下发送按钮后,将用户输入的数据封装成Message,
// 然后发送给子线程的Handler
Message msg = new Message();
msg.what = 0x345;
msg.obj = input.getText().toString();
clientThread.revHandler.sendMessage(msg);
// 清空input文本框
input.setText("");
}
catch (Exception e)
{
e.printStackTrace();
}
}
});
}
}
Class 2:
public class ClientThread implements Runnable
{
private Socket s;
// 定义向UI线程发送消息的Handler对象
private Handler handler;
// 定义接收UI线程的消息的Handler对象
public Handler revHandler;
// 该线程所处理的Socket所对应的输入流
BufferedReader br = null;
OutputStream os = null;
public ClientThread(Handler handler)
{
this.handler = handler;
}
public void run()
{
try
{
s = new Socket("192.168.191.1", 30000); //此处IP若为开启的虚拟WiFi IP,如下图(2)中的//192.168.191.1,因为此方法可保证PC与手机终端接入同一路由器(如果手机终端开的虚拟机则本机IP与虚//拟WiFi IP都可);
br = new BufferedReader(new InputStreamReader(
s.getInputStream()));
os = s.getOutputStream();
// 启动一条子线程来读取服务器响应的数据
new Thread()
{
@Override
public void run()
{
String content = null;
// 不断读取Socket输入流中的内容。
try
{
while ((content = br.readLine()) != null)
{
// 每当读到来自服务器的数据之后,发送消息通知程序界面显示该数据
Message msg = new Message();
msg.what = 0x123;
msg.obj = content;
handler.sendMessage(msg);
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
}.start();
// 为当前线程初始化Looper
Looper.prepare();
// 创建revHandler对象
revHandler = new Handler()
{
@Override
public void handleMessage(Message msg)
{
// 接收到UI线程中用户输入的数据
if (msg.what == 0x345)
{
// 将用户在文本框内输入的内容写入网络
try
{
os.write((msg.obj.toString() + "\r\n")
.getBytes("utf-8"));
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
};
// 启动Looper
Looper.loop();
}
catch (SocketTimeoutException e1)
{
System.out.println("网络连接超时!!");
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
6 常见问题及解决方法:
6.1 本地IP设置错误
查看本机IP:win + R -> cmd ->ipconfig
IPv4为你的IP (图 1)
6.2 本由于未手机终端与PC服务端未接入同一路由器
解决方法:
1)windows搭建无线局域网,手机接入局域网,new Socket(局域网IP,端口号)
(1)以管理员身份运行命令提示符:
快捷键win+R→输入cmd→回车
(2)启用并设定虚拟WiFi网卡:
运行命令:netsh wlan set hostednetwork mode=allow ssid=laozhang key=12345678
此命令有三个参数,mode:是否启用虚拟WiFi网卡,改为disallow则为禁用。
ssid:无线网名称,最好用英文(以wuminPC为例)。
key:无线网密码,八个以上字符(以wuminWiFi为例)。
以上三个参数可以单独使用,例如只使用mode=disallow可以直接禁用虚拟Wifi网卡。
(3)开启成功后,网络连接中会多出一个网卡为“Microsoft Virtual WiFi Miniport Adapter”的无线连接2,为方便起见,将其重命名为虚拟WiFi。若没有,只需更新无线网卡驱动就OK了。
(4)设置Internet连接共享:
在“网络连接”窗口中,右键单击已连接到Internet的网络连接,选择“属性”→“共享”,勾上“允许其他······连接(N)”并选择“虚拟WiFi”。
(5)开启无线网络:
继续在命令提示符中运行:netsh wlan start hostednetwork
netsh wlan stop hostednetwork
(将start改为stop即可关闭该无线网,以后开机后要启用该无线网只需再次运行此命令即可)查看网卡是否支持虚拟WIFI:netsh wlan show drivers)
(图 2) PC连接无线网
(图 3) PC连接有线网
2)使用猎豹WiFi快速共享网络,完成连接
6.3 怎么知道自己的电脑IP是内网还是外网:可以ping本机IP(cmd+ipconfig),如果IP是10.x.x.x;172.x.x.x; 192.168.x.x。 基本判定为内网,其它形式一般为外网。
6.4
6.4.1 如何访问内网的服务器:如果是通过路由上网,外部访问这个主机时,通过外网访问的只是路由的外网 IP,路由器里面需要进行路由端口映射(将主机的IP和端口填入路由的端口映射表中),这个主机才能被访问到。校园网也类似,但是一般的用户是没有权限设置的。如果在校园网内网里面做服务器,希望被外网访问,需慎重。
6.4.2 需要注意的是:比如我的本机IP是100.82.121.40,这是一个外网IP,但是实际上我是不 能通过该IP地址访问服务器的,因为这是移动网的IP,而移动网是通过NAT(网络地址转换)形式的,所以在外网通过这个IP是不能本机的服务器的(可能需要类似于路由器的映射那样的转换才行)。而与此同时,经过测试,联通和电信的网的IP是一个完整的外网IP(可以通过该IP地址访问服务器)---这是个人测试得到的一些结论,可能会有错误。
6.4.3 区域网内测试:手机和电脑在同一个区域网内(通过wifi连接),这时候客户端可以通过连接电脑的内网 地址(区域网内的IP,一般是192.168.1.1类似的)连接PC服务器(端口任意),也可以通过直接连接电脑的外网地址(普通的IP4地址,注意不 是内网的形式),其实本质只是通过内网连接(比如我外网是移动网,wifi模式下客户端可以通过外网IP连接诶服务器,但是断开Wifi,就会找不到服务器,所以这其实只是一种假象,就像本机客户端通过外网IP连接本机服务器一样)
6.4.4 外网测试:手机通过外网IP访问PC端的服务器,经测试,只能是联通网和电信网才行,移动网是NAT形式不能访问。
参考文献:
[1] 疯狂Android讲义,李刚著,电子工业出版社
[2] cnblogs.com android问题分析
浅谈android Socket 通信及自建ServerSocket服务端常见问题的更多相关文章
- [转]浅谈Flash Socket通信安全沙箱
用过Flash socket的同学都知道,Flash socket通讯有安全沙箱问题.就是在Flash Player发起socket通信时,会向服务端获取安全策略,如果得不到服务端响应,flash将无 ...
- 浅谈android代码保护技术_ 加固
浅谈android代码保护技术_加固 导语 我们知道Android中的反编译工作越来越让人操作熟练,我们辛苦的开发出一个apk,结果被人反编译了,那心情真心不舒服.虽然我们混淆,做到native层,但 ...
- 浅谈Android应用保护(一):Android应用逆向的基本方法
对于未进行保护的Android应用,有很多方法和思路对其进行逆向分析和攻击.使用一些基本的方法,就可以打破对应用安全非常重要的机密性和完整性,实现获取其内部代码.数据,修改其代码逻辑和机制等操作.这篇 ...
- 安卓开发_浅谈Android动画(四)
Property动画 概念:属性动画,即通过改变对象属性的动画. 特点:属性动画真正改变了一个UI控件,包括其事件触发焦点的位置 一.重要的动画类及属性值: 1. ValueAnimator 基本属 ...
- 浅谈Android应用性能之内存
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 文/ jaunty [博主导读]在Android开发中,不免会遇到许多OOM现象,一方面可能是由于开 ...
- 浅谈Android五大布局
Android的界面是有布局和组件协同完成的,布局好比是建筑里的框架,而组件则相当于建筑里的砖瓦.组件按照布局的要求依次排列,就组成了用户所看见的界面.Android的五大布局分别是LinearLay ...
- Android Socket通信详解
一.Socket通信简介 Android与服务器的通信方式主要有两种,一是Http通信,一是Socket通信.两者的最大差异在于,http连接使用的是“请求—响应方式”,即在请求时建立连接通道,当客 ...
- [转]浅谈Android五大布局(二)——RelativeLayout和TableLayout
在浅谈Android五大布局(一)中已经描述了LinearLayout(线性布局).FrameLayout(单帧布局)和AbsoulteLayout(绝对布局)三种布局结构,剩下的两种布局Relati ...
- [转]浅谈Android五大布局(一)——LinearLayout、FrameLayout和AbsoulteLayout
Android的界面是有布局和组件协同完成的,布局好比是建筑里的框架,而组件则相当于建筑里的砖瓦.组件按照布局的要求依次排列,就组成了用户所看见的界面.Android的五大布局分别是LinearLay ...
随机推荐
- Vue项目部署遇到的问题及解决方案
写在前面 Vue-Router 有两种模式,默认是 hash 模式,另外一种是 history 模式. hash:也就是地址栏里的 # 符号.比如 http://www.example/#/hello ...
- Apache Maven(一):快速入门
Maven 是什么? Maven 是一个项目管理和整合工具.Maven 为开发者提供了一套完整的构建生命周期框架.开发团队几乎不用花多少时间就能够自动完成工程的基础构建配置,因为 Maven 使用了一 ...
- 在ubuntu上安装subline
Sublime Text is a most popular, lightweight and smart cross-platform text and source code editor wit ...
- PAT (Basic Level) Practice 1004 成绩排名
个人练习 读入n名学生的姓名.学号.成绩,分别输出成绩最高和成绩最低学生的姓名和学号. 输入格式:每个测试输入包含1个测试用例,格式为\ 第1行:正整数n 第2行:第1个学生的姓名 学号 成绩 第3行 ...
- 顺序链表的C风格实现
//头文件 #ifndef _SEQLIST_H_ #define _SEQLIST_H_ //定义数据类型 typedef void SeqList; typedef void SeqListNod ...
- PHP.30-TP框架商城应用实例-后台6-商品会员价格删除-外键,级联操作
商品会员价格删除 需求:当删除一件商品时,这件商品对应的会员价格也应该从会员价格表{price,level_id,goods_id}中删除掉. 有两种删除方法 1.在钩子函数_before_delet ...
- 15 Django组件-中间件
中间件 中间件的概念 中间件顾名思义,是介于request与response处理之间的一道处理过程,相对比较轻量级,并且在全局上改变django的输入与输出.因为改变的是全局,所以需要谨慎实用,用不好 ...
- [原]sencha touch之表单二(注册页面)
接着上一篇的登陆页面,来一个最简单的注册页面,几乎包含了常用的field Ext.application({ id:'itKingApp', launch:function(){ var formPa ...
- KVO的底层实现原理?如何取消系统默认的KVO并手动触发?
KVO是基于runtime机制实现的 当某个类的属性对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类(该类的子类),在这个派生类中重写基类中任何被观察属性的setter 方法.派生类在被 ...
- NSThread那些事儿
NSThread 哎呀,它面向对象,再去看看苹果提供的API,对比一下Pthreads,简单明了,人生仿佛又充满了阳光和希望,我们先来一看一下系统提供给我们的API自然就知道怎么用了,来来来,我给你注 ...