本文介绍编写一个使用Socket完成Android客户端与服务器端通信的简单Demo
详细代码地址:github.com/Baolvlv/LearnAndroid/tree/master/MySocketClient
(1)Socket介绍
Socket,又称套接字,应用通过socket向网络发出请求或应答网络请求
在java中,Socket与ServerSocket位于java.net包中。ServerSocket位于服务器端,Socket位于客户端。连接成功时,两端都产生一个Socket实例,通过操作这个实例完成会话。两端的实例平等,没有差别,通过Socket与其子类完成所有操作。
(2)Sokcet链接建立过程
1.服务器监听 2.客户端发出请求 3.建立链接 4.通信
(3)Socket的特点
Socket基于Tcp链接,数据传输有保障
Socket适用于建立长时间链接
Socket编程通常应用于即时通信
Eclipse中新建java工程,包,类。
创建ServerSocket对象,自定义通信端口号(1-65535)65535最大的16位为二进制数。
// 构造函数参数为SerSocket侦听的端口,取值1-65535
//随意取值,使用较大的端口,与预留端口区分
try{//创建serverSocket对象
ServerSocket
serverSocket=newServerSocket(12345);
调用serverSocket.accept方法,侦听客户端的请求,创建socket。此方法在客户端请求之前会导致线程一直为阻塞状态。当accept方法执行,socket被赋值,则说明链接建立。
//侦听客户端的请求,会阻塞当前线程 block
//accept方法执行,socket被赋值,则说明链接建立
Socket
socket=serverSocket.accept();
建立连接后,服务器端弹出对话框,使用java swing
//建立链接后执行,弹出对话框,第一个参数为父级容器
JOptionPane.showMessageDialog(
null,"有客户端链接到了本机的12345端口");
此时运行程序,程序会一直运行,直到客户端发出请求,建立连接后弹出对话框结束
2.使用ServerSocket建立聊天服务器
会阻塞主线程的程序应该单独放在一个线程中
创建ChatSocket类继承自Thread,用于执行客户端连接后的输出操作
//声明Socket对象
Socket
socket
;
//构造函数接收外部传入的SocketpublicChatSocket(Sockets
){
this.socket=s
;
}
创建输出函数,使用输出流进行输出
publicvoidout(Stringout) {
socket.getOutputStream().write(out.getBytes("UTF-8"));
复写Thread类的run方法,执行输出操作,每隔1秒输出一次
publicvoidrun() {intcount
= 0;
//不断执行输出操作while(true
){
out(
"loop"+count
);
count
++;
try
{
//每隔1秒输出一次
sleep(1000);
}
catch(InterruptedExceptione
) {
//TODOAuto-generated catch blocke
.printStackTrace();
}
自定义ServerListener类继承自Thread,用于执行侦听客户端的请求,创建socket,为每个客户端进行输出操作
创建ServerSocket,继承自Thread类
publicclassServerListenerextendsThread {
复写run方法,规定端口,创建ServerSocket对象
ServerSocketserverSocket=newServerSocket(12345);
不断循环,每当客户端发出请求,serverSocket.accept()方法执行一次,创建一个新Socket对象,将创建的socket传入,启动输出线程
while(true){
Socketsocket=serverSocket.accept();
newChatSocket(socket).start();
}
Telnet是进行远程登录的标准协议和主要方式它为用户提供了在本地计算机上完成远程主机工作的能力。可以用telnet命令来测试端口号是否正常打开还是关闭状态。
测试命令:控制台输入 telnet localhost 12345
会在控制台不断输出请求结果
添加一个客户端发出消息,其他客户端接收消息的功能
创建ChatManager类,用于管理每个客户端对象创建的用于聊天的ChatSocket线程对象
由于每个聊天服务器只能拥有一个ChatManger类,所以使用单例模式
创建private的构造方法和private对象,public static返回对象的方法
//私有化构造方法private
ChatManager() {}
//为当前的类创建唯一的实例privatestaticfinalChatManagercm=new
ChatManager();
//创建返回唯一实例的public static方法publicstatic
ChatManager getChatManager() {
returncm
;
}
创建Vector,范型为ChatSocket,用于管理客户端的聊天线程
//创建Vector,管理每一个客户端创建的ServerSocket线程对象
Vector
vector=newVector<>();
创建向Vector中添加元素的方法
//Vector的添加方法publicvoidadd(ChatSocketcs
) {
vector.add(cs);
创建发布消息的方法,参数为发布消息的chatSocket和发布的消息
遍历Vector的中的所有元素,对不是发布消息的其他ChatSocket执行out方法
在发布消息的ChatSocket内部调用此方法
//其中任意一个线程可以调用publish方法向其他的客户端发布信息publicvoidpublish(ChatSocketcs,Stringout
) {
//遍历vector中的元素for(inti= 0;i
++){
//创建方法内部的本地对象
ChatSocket
cSocket=vector.get(i
);
//判断,不向自身发布消息if(!cSocket.equals(cs
)){
//调用输出方法,输出需要发送的信息cSocket.out(out
);
}
当客户端连接时,创建socket,创建ChatSocket,将创建的ChatSocket添加到ChatManager中
Socketsocket=serverSocket.accept();
ChatSocketchatSocket=newChatSocket(socket);chatSocket.start();//当ChatSocket线程创建时,将它添加到ChatManager中
ChatManager.getChatManager().add(
chatSocket);
当前客户端获取输出流进行输出
//向客户端发送数据
socket.getOutputStream().write(outline.getBytes("UTF-8"));
在ChatSocket中,socket获取输出流,out方法将获取的字符串输出
//通过输出流进行输出publicvoidout(Stringout
) {
String
outline=out+"\n"
;
try
{
//向客户端发送数据socket.getOutputStream().write(outline.getBytes("UTF-8"));
在ChatSocket的run方法中,将本ChatSocket的数据,通过socket获取输入流
读取本socket发送的信息,调用ChatManager的publish方法发送给其他ChatSocket
@Overridepublicvoid
run() {
try
{
//通过socket获取输入流,读取当前socket将要发送的信息
BufferedReader
reader=new
BufferedReader(
newInputStreamReader(socket.getInputStream(),"UTF-8"
));
String
line
;
//将本ChatSocket发送的数据,转发给其他客户端while((line=reader.readLine()) !=null
){
ChatManager.getChatManager().publish(
this,line
);
}
3.在Android中创建socket客户端
socket的创建不能在主线程中,新建异步线程,复写doInBackground方法,完成socket创建,连接与数据读取的操作。
//读取聊天信息的异步线程AsyncTask read =newAsyncTask() {
@OverrideprotectedVoiddoInBackground(Void... params) {
try{
//socket不能在主线程中//新建socket,参数为ip与端口号socket=newSocket(ip,12346);//通过socket的输入输出流,创建BufferedWriter与BufferedReaderwriter=newBufferedWriter(newOutputStreamWriter(socket.getOutputStream()));reader=newBufferedReader(newInputStreamReader(socket.getInputStream()));//向外发布成功的状态publishProgress("@success");}catch(IOException e) {
Toast.makeText(getApplicationContext(),"无法建立连接",Toast.LENGTH_SHORT).show();e.printStackTrace();}
try{
String line;//通过reader读取数据并向外发布while((line =reader.readLine())!=null){
publishProgress(line);}
}catch(IOException e) {
e.printStackTrace();}
return null;
复写onProgressUpdate()方法用于接收doInBackground方法向外发布的数据,更新主线程的ui
@Overrideprotected voidonProgressUpdate(String... values) {
//接收判断,如果是成功,则弹出提示if(values[0].equals("@success")){
Toast.makeText(MainActivity.this,"连接成功",Toast.LENGTH_SHORT).show();}
//在主ui线程中,更新接收到的消息tvChat.append("某人说:"+values[0]+"\n");super.onProgressUpdate(values);}
};
调用异步线程执行
//调用异步线程执行read.execute();
创建send方法,通过writer向外输出数据
public voidsend(){
try{
tvChat.append("我说:"+etSend.getText().toString()+"\n");//通过writer输出信息writer.write(etSend.getText().toString()+"\n");writer.flush();//清空ediText的内容etSend.setText("");}catch(IOException e) {
e.printStackTrace();}
button的事件监听器中调用相应的方法,添加互联网访问权限
@Overridepublic voidonClick(View v) {
switch(v.getId()){
caseR.id.btnConnect:
connect();break;caseR.id.btnSend:
send();break;}