Android开发之简单的聊天室(客户端与服务器进行通信)

1.使用ServerSocket创建TCP服务器端



Java中能接收其他通信实体连接请求的类是ServerSocket,
ServerSocket对象用于监听来
自客户端的Socket连接,如果没有连接,它将一直处于等待状态。ServerSocket包含一个监听来自客户端连接请求的方法。

1) Socket accept():如果接收到一个客户端Socket的连接请求,该方法将返回一个与连接客户端Socket对应的Socket;否则该方法将一直处于等待状态,线程也被阻塞。

创建ServerSocket对象,ServerSocket类提供了如下几个构造器:

2) ServerSocket(int port):用指定的端口 port
来创建一个ServerSocket。该端口应该是有一个有效的端口整数值:0?65
535。

3) ServerSocket(int port,int backlog):增加一个用来改变连接队列长度的参数backlog。

4) ServerSocket(int port.int backlog,lnetAddress
localAdd():在机器存在多个 IP
地 址的情况下,允许通过localAddr这个参数来指定将ServerSocket绑定到指定的IP地址。

注:当ServerSocket使用完毕后,应使用ServerSocket的close()方法来关闭该ServerSocket。通常情况下,服务器不应该只接收一个客户端请求,而应该不断地接收来自客户端的所有请求。如下面代码所示:


//创建一个ServerSocket,用于监听客户端的连接请求

ServerSocket ss=new
ServerSocket(1566);

//不停地从接收来自客户端的请求

while (true)
{

//每当接受一个来自客户端的Socket的请求,服务器端也对应产生一个Socket

Socket s=ss.accept();

//下面就可以使用Socket进行通信了

//..........

}

2.使用Socket进行通信



客户端通常可使用Socket的构造器来连接到指定服务器,Socket通常可使用如下两个构造器。

1) Socket(lnetAddress/String remoteAddress, int port):创建连接到指定远程主机、远程端口的Socket,该构造器没有指定本地地址、本地端口,默认使用本地主机的默认IP地址,默认使用系统动态指定的IP地址。

2) Socket(lnetAddress/String remoteAddress, int port, InetAddress localAddr, int localPort):创建连接到指定远程主机、远程端口的Socket,并指定本地IP地址
和本地端口号,适用于本地主机有多个IP地址的情形。

上面两个构造器中指定远程主机时既可使用InetAddress来指定,也可直接使用String对象来指定,但程序通常使用String对象(如211.158.6.26)来指定远程IP。当本地主机只有—个IP地址时,使用第一个方法更为简单。如:


Socket socket=new
Socket("169.254.77.36", 8888);

//下面就可以和服务器进行通信了

当程序执行上面代码中的粗体字代码时,该代码将会连接到指定服务器,让服务器端的ServerSocket的accept()方法向下执行,于是服务器端和客户端就产生一对互相连接的Socket。

当客户端、服务器端产生了对应的Socket之后,程序无须再区分服务器、客户端,而是通过各自的Socket进行通信。Socket提供如下两个方法来获取输入流和输出流:

1) InputStream
getlnputStream():返回该Socket对象对应的输入流,让程序通过该输入流从Socket中取出数据。

2) OutputStream
getOutputStream():返回该Socket对象对应的输出流,让程序通过该输出流向Socket中输出数据。

3.实例:和服务器进行简单通信:



服务器端:


public
static
void main(String[] args) {

//
TODO Auto-generated method stub

try {

//创建一个ServerSocket,用于监听客户端的连接请求

ServerSocket ss=new ServerSocket(8888);

//不停地从接收来自客户端的请求

while (true)
{

//每当接受一个来自客户端的Socket的请求,服务器端也对应产生一个Socket

Socket s=ss.accept();

//下面就可以使用Socket进行通信了

OutputStream os=s.getOutputStream();

os.write("来自服务器端的消息:你好,今天天气不错,骚年外出散散心吧!".getBytes("utf-8"));

//关闭输出流

os.close();

//关闭Socket

s.close();

}

} catch (Exception e) {

//
TODO Auto-generated catch block

e.printStackTrace();

}

}

注:上面的程序并未把OutputStream流包装成PrintStream
,然后使用
PrintStream直接输出整个字符串,这是因为该服务器端程序运行于Windows
主机上,当直接使用PrintStream输出字符串时默认使用系统平台的字符串(即 GBK )进行编码;但该程序的客户端是Android应用,运行于Linux平台(Android
是Linux内核的),因此当客户端读取网络数据时默认使用UTF-8字符集进行解码,这样势必引起乱码。为了保证客户端能正常解析到数据,此处手动控制字符串的编码,强行指定使用UTF-8字符集进行编码,这样就可以避免乱码问

客户端:


edtMsg=(EditText)findViewById(R.id.edtMsg);

//创建并启动一个新线程,向服务器发送TCP请求

new Thread(){

@Override

public
void run() {

//
TODO Auto-generated method stub

super.run();

//创建一个Socket用于向IP为169.254.77.36的服务器的8888端口发送请求

Socket s;

try {

s = new Socket();

//如果超过10s还没连接到服务器则视为超时

s.connect(new InetSocketAddress("169.254.77.36",
8888),10000);

//设置客户端与服务器建立连接的超时时长为30秒

s.setSoTimeout(30000);

//将Socket对应的输入流封装成BufferedReader对象

BufferedReader br=new BufferedReader(new

InputStreamReader(s.getInputStream()));

String msg=br.readLine();

edtMsg.setText(msg);

br.close();

s.close();

//捕捉SocketTimeoutException异常

}catch (SocketTimeoutException e) {

//
TODO Auto-generated catch block

e.printStackTrace();

}catch (Exception e) {

//
TODO: handle exception

e.printStackTrace();

}

}

}.start();

最后别忘记为程序添加访问网络的权限:


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

程序运行效果图:

4.异常和捕捉



上面的程序为了突出通过ServerSocket和Socket建立连接并通过底层
IO流进行通信的主题,程序没有进行异常处理,也没有使用finally块来关闭资源。

实际应用中,程序可能不想让执行网络连接、读取服务器数据的进程一直阻塞,而是希
望当网络连接、读取操作超过合理时间之后,系统自动认为该操作失败,这个合理时间就是
超时时长。Socket对象提供了一个setSoTimeout(int timeout)来设置超时时长,如下面的代码
片段所示:


//设置客户端与服务器建立连接的超时时长为30秒

s.setSoTimeout(30000);

为Socket对象指定了超时时长之后,如果使用Socket进行读、写操作完成之前已经超出了该时间限制,那么这些方法就会抛出SocketTimeoutException异常,程序可以对该异常进行捕捉,并进行适当处理,如以下代码所示:


Socket s;

try {

s = new Socket();

//如果超过10s还没连接到服务器则视为超时

s.connect(new InetSocketAddress("169.254.77.36",
8888),10000);

//设置客户端与服务器建立连接的超时时长为30秒

s.setSoTimeout(30000);

//将Socket对应的输入流封装成BufferedReader对象

BufferedReader br=new BufferedReader(new

InputStreamReader(s.getInputStream()));

String msg=br.readLine();

edtMsg.setText(msg);

br.close();

s.close();

//捕捉SocketTimeoutException异常

}catch (SocketTimeoutException e) {

//进行异常处理

}

假设程序需要为Socket连接服务器时指定超时时长:即经过指定时间后,如果该Socket
还未连接到远程服务器,则系统认为该Socket连接超时。但Socket的所有构造器里都没有提供指定超时时长的参数,所以程序应该先创建一个无连接Socket,再调用Socket的connect()
方法来连接远程服务器,connect()方法就可以接受一个超时时长参数。如以下代码所示:


//创建一个无连接的Socket

Socket s=
new Socket();

//如果超过10s还没连接到服务器则视为超时

s.connect(new
InetSocketAddress("169.254.77.36", 8888),10000);

5.加入多线程




前面服务器端和客户端只是进行了简单的通信操作:服务器接收到客户端连接之后,服
务器向客户端输出一个字符串,而客户端也只是读取服务器的字符串后就退出了。实际应用
中的客户端则可能需要和服务器端保持长时间通信,即服务器需要不断地读取客户端数据, 并向客户端写入数据;客户端也需要不断地读取服务器数据,并向服务器写入数据。

当使用传统BufferedReader的readLine()方法读取数据时,当该方法成功返回之前,线程被阻塞,程序无法继续执行。考虑到这个原因,服务器应该为每个Socket单独启动一条线程,每条线程负责与一个客户端进行通信。

客户端读取服务器数据的线程同样会被阻塞,所以系统应该单独启动一条线程,该线程
专门负责读取服务器数据。

下面考虑实现一个简单的C/S聊天室应用,服务器端则应该包含多条线程,每个Socket
对应一条线程,该线程负责读取Socket对应输入流的数据(从客户端发送过来的数据),并
将读到的数据向每个Socket输出流发送一遍(将一个客户端发送的数据“广播”给其他客户
端),因此需要在服务器端使用List来保存所有的Socket。

下面是服务器端的实现代码,程序为服务器提供了两个类,一个是创建ServerSocket监
听的主类,另一个是负责处理每个Socket通信的线程类。

代码清单:

服务器端:

ServerSocket监听的主类:


import java.io.IOException;

import java.net.ServerSocket;

import java.net.Socket;

import java.util.ArrayList;

/**

* Description:

*
创建ServerSocket监听的主类

*
@author 
jph

*/

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();

}

}

}

负责处理每一个Socket通信的线程类:


import java.io.BufferedReader;

import java.io.IOException;

import java.io.InputStreamReader;

import java.io.OutputStream;

import java.net.Socket;

/**

* Description:

*
负责处理每一个Socket通信的线程类

*
@author 
jph

*/

//
负责处理每个线程通信的线程类

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;

}

}

上面的服务器端线程类不断读取客户端数据,程序使用readFromCHent()方法来读取客户端数据,如果读取数据过程中捕获到IOException异常,则表明该Socket对应的客户端Socket
出现了问题(到底什么问题我们不管,反正不正常),程序就将该Socket从socketList中删除,
如readFromClient()方法中①号代码所示。

当服务器线程读到客户端数据之后,程序遍历socketList集合,并将该数据向socketList
集合中的每个Socket发送一次一该服务器线程将把从Socket中读到的数据向socketList中
的每个Socket转发一次,如run()线程执行体中的粗体字代码所示。

注:

上面的程序中②号粗体字代码将网络的字节榆入流转换为字符输入流时,指定了转换所用的字符串:UTF-8,这也是由于客户端写过来的数据是采用UTF-8
字符集进行编码的,所以此处的服务器端也要使用UTF-8字符集进行解码。当需        
要编写跨平台的网络通信程序时,使用UTF-8字符集进行编码、解码是一种较好的解决方案。

每个客户端应该包含两条线程:一条负责生成主界面,并响应用户动作,并将用户输入
的数据写入Socket对应的输出流中:另一条负责读取Socket对应输入流中的数据(从服务器
发送过来的数据),并负责将这些数据在程序界面上显示出来。

客户端:

客户端程序同样是一个Android应用,因此需要创建一个Android项目,这个Android
应用的界面中包含两个文本框:一个用于接收用户输入,另一个用于显示聊天信息:界面中
还有一个按钮,当用户单击该按钮时,程序向服务器发送聊天信息。该程序的界面布局代码
如下。


/**

*
客户端:

* */

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();

}

}

});

}

}

代码分析:

当用户单击该程序界而中的“发送”按钮之后,程序将会把input输入框中的的内容发
送该clientThread的revHandler对象,clientThread将负责将用户输入的内容发送给服务器。

为了避免UI线程被阻塞,该程序将建立网络连接、与网络服务器通信等工作都交给
ClientThread线程完成。因此该程序在①号代码处启动ClientThread线程。

由于Android不允许子线程访问界面组件,因此上面的程序定义了一个Handler来处理
来自子线程的消息,如程序中②号粗体字代码所示。

ClientThread子线程负责建立与远程服务器的连接,并负责与远程服务器通信,读到数
据之后便通过Handler对象发送一条消息:当ClientThread子线程收到UI线程发送过来的消
息(消息携带了用户输入的内容)之后,还负责将用户输入的内容发送给远程服务器。该子
线程代码如下:


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

{

//192.168.191.2为本机的ip地址,30000为与MultiThreadServer服务器通信的端口

s =
new Socket("192.168.191.2",
30000);

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();

}

}

}

实例分析:

上面线程的功能也非常简单,它只是不断获取Socket输入流中的内容,当读到Socket
输入流中的内容后,便通过Handler对象发送一条消息,消息负责携带读到数据,除此之外,该子线程还负责读取UI线程发送的消到消息之后,该子线程负责将消息中携带的数据发送给远程服务器。

先运行上面程序中的MyServer类,该类运行后只是作为服务器,看不到任何输出。接
着可以运行Android客户端一相当于启动聊天室客户端登录该服务器,接着可以看到在任
何一个Android客户端输入一些内容后单击“发送”按钮,将可看到所有客户端(包括自己)
都会收到他刚刚输入的内容,如上图所示,这就粗略实现了一个C/S结构聊天室的功能。

时间: 2024-10-05 09:31:17

Android开发之简单的聊天室(客户端与服务器进行通信)的相关文章

Socket编程(简易聊天室客户端/服务器编写、CocoaAsyncSocket)

Socket编程(简易聊天室客户端/服务器编写.CocoaAsyncSocket) 一.Socket 1.1 Socket简介 Socket就是为网络服务提供的一种机制.网络通信其实就是Socket间的通信,通信的两端都是Socket,数据在两个Socket间通过IO传输. 在Web服务大行其道的今天,调用Web服务的代价是高昂的,尤其是仅仅是抓取少量数据的时候尤其如此.而使用Socket,可以只传送数据本身而不用进行XML封装,大大降低数据传输的开销.Socket允许使用长连接,允许应用程序运

玩转Node.js(四)-搭建简单的聊天室

玩转Node.js(四)-搭建简单的聊天室 Nodejs好久没有跟进了,最近想用它搞一个聊天室,然后便偶遇了socket.io这个东东,说是可以用它来简单的实现实时双向的基于事件的通讯机制.我便看了一些个教程使用它来搭建一个超级简单的聊天室. 初始化项目 在电脑里新建一个文件夹,叫做“chatroom”,然后使用npm进行初始化: $ npm init 然后根据提示以及相关信息一步一步输入,当然也可以一路回车下去,之后会在项目里生成一个package.json文件,里面的信息如下: 1 $ ca

JAVA聊天室客户端不显示内容,服务端没问题的原因总算找到了

o(≧口≦)o刚才打了很多内容,结果忘了网络断开了,没法自动存稿.结果一发布把内容都弄没了.气死,不写了. 今天讲的是JAVAEE,讲的很快,主要就重点或者是和SE不一样的才过一下,差不多的就跳过去,让我们自己看PPT......(没掉的内容)...... 上周做了个简单的聊天室功能代码,一直出错.发的内容只能在服务端看到,客户端却看不到.字符串一直等待接收,但就是收不到.对着老师的代码改了好几遍,还是不行.还让老师帮忙看了很久,也还是没发现问题.今天下午又试着做了一遍,不过代码不熟,还是按照自

Java网络编程 - 基于UDP协议 实现简单的聊天室程序

最近比较闲,一直在抽空回顾一些Java方面的技术应用. 今天没什么事做,基于UDP协议,写了一个非常简单的聊天室程序. 现在的工作,很少用到socket,也算是对Java网络编程方面的一个简单回忆. 先看一下效果: 实现的效果可以说是非常非常简单,但还是可以简单的看到一个实现原理. "聊天室001"的用户,小红和小绿相互聊了两句,"聊天室002"的小黑无人理会,在一旁寂寞着. 看一下代码实现: 1.首先是消息服务器的实现,功能很简单: 将客户端的信息(进入了哪一个聊

简单的聊天室制作

简单的聊天室制作 一个简单的聊天室,主要是就两个部分,一部分就是我们进行的时候那个聊天窗口,另外一个就是背后的服务器,我们要写一个聊天窗口,也要写一个相对应的服务器. 做一个项目过程中,写一个代码很简单,但是把逻辑分析清楚,将制作的过程中所有的逻辑关系分析清楚是项目的最重要的环节. 下面的一步一步,将这个聊天室的制作过程一步一步制作出来. 第一步: 第二步: 第三步: 第四步: 第五步: 第六步: 第七步: 第八步: 第九步: 第十步: 第十一步: 这就是简单的聊天室的制作的过程.这样我们来看一

聊天室客户端源码

import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.OutputStream;import java.io.OutputStreamWriter;import java.io.PrintWriter;import java.net.Socket;import java.net.Unkno

Android开发---MediaPlayer简单音乐播放器

Android开发-MediaPlayer简单音乐播放器 功能介绍 实现一个简单的播放器,类似网易云音乐形式,功能包括: 播放.暂停,停止,退出功能: 后台播放功能: 进度条显示播放进度.拖动进度条改变进度功能: 播放时图片旋转,显示当前播放时间功能: 界面样式 功能实现 1. MediaPlayer的实现 MediaPlayer常用方法介绍 MediaPlayer的实现包括初始化MediaPlayer,MediaPlayer的功能实现,包括播放.暂停.停止.离开等,具体细节如下: MediaP

基于java网络聊天室--客户端

ChatClient.java 包含名为ChatClient的public类,其主要功能为定义客户端的界面,添加时间监听与事件处理.该类定义了Connect()与DisConnect()方法实现与客户端的连接与断开连接.当登陆到指定的服务器时,调用ClientReceive类实现消息收发,同时该类还定义了SendMessaga()方法来其他用户发送带有表情的消息或悄悄话. 1 /* 2 * To change this license header, choose License Headers

Linux网络编程--聊天室客户端程序

聊天室客户端程序 #define _GNU_SOURCE 1 #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <string.h> #in