java回顾之socket通信

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


说的多,不如自己实践学的快,共勉

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值