您的位置:首页 > 编程语言 > Java开发

java回顾之socket通信

2016-02-25 11:23 537 查看
JAVA的很有意思的一个部分就是socket通信的内容,基于此可以实现任意二进制流的传输,也就是理论上可以做与计算机通信想关的任何事。

对于socket的应用之前,这里简单介绍一个服务端的小例子,一点不复杂的聊天室,没有太多针对性的功能,主要侧重于socket的基本用法。

效果图:

服务端:



android端:





一、下面这个程序是服务端:

package chat;

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.InetAddress;

import java.net.ServerSocket;

import java.net.Socket;

import java.util.Collection;

import java.util.HashMap;

import java.util.Map;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

/**

* 服务端应用程序

* @author sunny

*

*/

public class Server {

// 运行在服务端的Socket

private ServerSocketserver;

// 线程池,用于管理客户端线程

private ExecutorServicethreadPool;

// 保存所有的客户端输出流集合

private Map<String,PrintWriter>allOut;

/*

* 构造方法,用于初始化服务端

*/

public Server() throws IOException

{

try

{

/*

* 创建ServerSocket时需要指定服务端口

*/

System.out.println("初始化服务端");

server = new ServerSocket(8088);

// 初始化线程池

threadPool = Executors.newFixedThreadPool(50);

// 初始化存放所有的客户端输出流集合

allOut = new HashMap<String,PrintWriter>();

System.out.println("服务端初始化完毕");

}

catch (IOException e)

{

e.printStackTrace();

throw e;

}

}

/*

* 服务端开始工作的方法

*/

public void start() {

try

{

/*

* ServerSocket的accept方法

* 用于监听8088端口

* ,等待客户端的连接

* 该方法是一个阻塞方法

* ,直到一个客户端

* 连接,否则该方法一直阻塞

* 。若一个客户端

* 连接了,会返回该客户端的Socket

*/

while (true)

{

System.out.println("等待客户端连接...");

Socket socket = server.accept();

/*

* 当一个客户端连接后,

* 启动一个线程ClientHandler

* ,

* 将该客户端的Socket传入

* ,

* 使得该线程处理与该客户端的交互

* 。

* 这样我们能再次进入循环

* ,

* 接收下一个客户端的连接了

* 。

*/

Runnable ch = new ClientHandler(socket);

// Thread t =

// new

// Thread(ch);

// t.start();

threadPool.execute(ch);

}

}

catch (Exception e)

{

e.printStackTrace();

}

}

/**

* 将给定的输出流传入共享集合

* @param pw

*/

public synchronized void addOut(String nickname,PrintWriter pw) {

allOut.put(nickname,pw);

}

/**

* 将给定的输出流从共享集合中删

* @param pw

*/

public synchronized void removeOut(String nickname) {

allOut.remove(nickname);

}

/**

* 将给定的消息转发给所有的客户端s

* @param message

*/

public synchronized void sendMessage(String message) {

if(message.indexOf("@")!=-1){

String str1=message.substring(message.indexOf(":")+1);

String str=str1.substring(str1.indexOf("@")+1,str1.indexOf(":"));

allOut.get(str).println(message);

}else{

Collection<PrintWriter> values=allOut.values();

for(PrintWriter pw:values){

pw.println(message);;

}

}

}

public static void main(String[] args) {

Server server;

try

{

server = new Server();

server.start();

}

catch (IOException e)

{

e.printStackTrace();

System.out.println("服务端初始化失败");

}

}

/*

* 服务端的一个线程,用于与某个客户端交互

* 使用线程的目的是使得服务端可以处理多客户端

* @author sunny

*/

class ClientHandler implements Runnable {

// 当前线程处理的客户端的Socket

private Socketsocket;

// 当前客户端的ip

private String ip;

//当前客户端的昵称

private String nickname;

/*

* 根据给定的客户端Socket,创建线程体

*/

public ClientHandler(Socket socket)

{

this.socket = socket;

/*

* 通过socket获取远端的地址信息

* 对于服务端而言,远端就是客户端

*/

InetAddress address = socket.getInetAddress();

/*

* 获取远端计算机的IP地址

*/

ip = address.getHostAddress();

// address.getCanonicalHostName();

// 获取客户端端口号

int port = socket.getPort();

System.out.println(ip + ":" + port + "客户端连接了");

System.out.println("客户端连接了");

//改为了使用昵称了,所以不在这里通知了

//通知其他用户该用户上线了

sendMessage("["+ip+"]上线了");

}

/*

* 线程会将当前的socket中的输入流获取用来循环读取客户端发送来的信息

*/

public void run() {

/*

* 定义在try语句外的目的是,

* 为了在finally中也可以引用到pw

*/

PrintWriter pw = null;

try

{

/*

* 为了让服务端与客户端发送信息,

* 我们需要通过socket获取输出流

* 。

*/

OutputStream out = socket.getOutputStream();

// 转换为字符流,用于指定编码集

OutputStreamWriter osw = new OutputStreamWriter(out, "UTF-8");

// 创建缓冲字符输出流

pw = new PrintWriter(osw, true);

/*

* 将该客户端的输出流存入共享集合

* ,

* 以便使得该客户端也能接收到服务端

* 的转发的消息。

*/

// 输出当前在线人数

System.out.println("当前在线人数是:" + allOut.size());

/*

* 通过刚刚连上的客户端的Socket获取输入流

* 。

* 来读取客户端发送过来的信息

*/

InputStream in = socket.getInputStream();

/*

* 通过in接受来自客户端的信息,

* 使用字符流来根据指定的编码集将字节转换为字符串

*/

InputStreamReader isr = new InputStreamReader(in, "UTF-8");

/*

* 将字符流转换为缓冲字符输入流,

* 这样就可以以行为单位读取字符串了

*/

BufferedReader br = new BufferedReader(isr);

/*

* 当创建好当前客户端的输入流后,读取的第一个对象应当是昵称

*/

nickname=br.readLine();

// allOut.add(pw);不安全

addOut(nickname,pw);

//通知所有客户端当前用户上线了

sendMessage("["+nickname+"]上线了");

// 读取客户端发送过来的一行字符串

String message = null;

// 读取客户端发送过来的一行字符串

/*

* 读取客户端发送过来的信息这里

* windows与linux存在一定的差异

* : linux:

* 当客户端与服务端断开连接后

* 我们通过输入流会读取到null

* 但这是合乎逻辑的

* ,因为缓冲流的

* readLine

* ()方法若返回null就

* 表示无法通过该留再读取到信息

* 。

* 参考之前服务文本文件的判断

* 。 windows

* :当客户端与服务端断开连接后

* readLine

* ()方法会抛出异常。

*/

while ((message = br.readLine()) != null)

{

// System.out.println("客户端说:"

// +

// message);

// pw.println(message);

/*

* 当读取到客户端发送过来的一条信息后

* ,

* 将该消息转发给所有客户端

*/

// for

// (PrintWriter

// pw1 :

// allOut)

// {

// pw1.println(message);

// }

sendMessage(nickname+"说:"+message);

}

}

catch (IOException e)

{

// 在windows中的客户端

// 报错通常是因为客户端断开了连接

}

finally

{

/*

* 首先将该客户端的输出流从共享集合中删除

*/

// allOut.remove(pw);

removeOut(nickname);

// 输出当前在线人数

System.out.println("当前在线人数为:" + allOut.size());

//通知其他用户,该用户下线了

System.out.println("["+nickname+"]下线了");

sendMessage("["+nickname+"]下线了");

/*

* 无论是linux用户还是windows

* 用户,

* 当与服务器断开连接后我们都应当

* 在服务端也与客户端断开连接

*/

try

{

socket.close();

}

catch (IOException e)

{

// TODO

// Auto-generated

// catch

// block

e.printStackTrace();

}

System.out.println("一个客户端下线了...");

}

}

}

}

以上的代码中都带了注释,应该可以大致明白整个的流程。

服务端代码打包(这里面的代码只要关注server类就行了,以上就是其中的代码,别的可以不关注):

http://pan.baidu.com/s/1nuzxjLJ 密码:cp6t

二、下面是安卓端代码

打了个包,并且其中有apk文件可以直接运行,min sdk为8 ,target sdk为21;

代码已经改为gradle的代码,可以在eclipse以及android studio中正常调试

代码中都有注释介绍,有什么问题可以留言交流。

下面是android文件打包地址:

http://pan.baidu.com/s/1dEr6cBB 密码:z7a7

说的多,不如自己实践学的快,共勉
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: