Server 1
package day20150914socket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 服务端应用程序
* (MINA的本质也是使用server)
*/
public class Server {
//服务端的Socket
private ServerSocket server;
//构造方法,用于初始化服务端
public Server() throws IOException{
try {
System.out.println("初始化服务端");
/*
* 创建ServerSocket时需要指定的服务端口
*/
server = new ServerSocket(8088);
System.out.println("服务端初始化完毕");
} catch (IOException e) {
throw e;
}
}
public void start(){
try {
System.out.println("等待客户端连接。。。");
/*
* ServerSocket的accept()方法:
* 用于监听8088端口,等待客户连接, 否则该方法阻塞。
* 若一个客户端连接了,会返回给客户端的Socket
*/
Socket socket = server.accept();
//获取远端(客户端)地址
InetAddress address = socket.getInetAddress();
//获取远端IP地址
String ip = address.getHostAddress();
//获取远端端口号
int port = socket.getPort();
System.out.println(ip+":"+port+"客户端连接上了");
/*
* 通过刚刚连接上来的客户端的Socket获取输入流
* 来读取客户端发过来的信息
*/
InputStream in = socket.getInputStream();
//将字节输入流包装为字符输入流,这样就可指定编码集
InputStreamReader isr = new InputStreamReader(in,"utf-8");
//将字符流转为缓冲字符输入流,这样就可以以行为单位来读取字符串了
BufferedReader br = new BufferedReader(isr);
String message = null;
while((message=br.readLine())!=null){
System.out.println("客户端说:"+message);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
Server server = new Server();
server.start();
} catch (IOException e) {
e.printStackTrace();
System.out.println("服务器初始化失败");
}
}
}
Client 1
package day20150914socket;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class Client {
//用于连接服务器端的Socket
private Socket socket;
public Client() throws Exception{
try {
System.out.println("正在连接服务端。。。");
/*
* 创建Socket对象,
* 就会尝试根据给定的地址与端口连接服务器
* 所以,若该对象创建成功,说明与服务器端连接正常
*/
//localhost:本机。连接其他计算机可写IP
socket = new Socket("localhost",8088);
System.out.println("成功连接服务端。");
} catch (Exception e) {
throw e;
}
}
public void start(){
try{
/*
* 可以通过Socket的getOutputStream()方法获取一条输出流
* 用于将信息发送至服务器
*/
OutputStream out = socket.getOutputStream();
/*
*使用字符流指定编码集将字符串转为字节后,
*再通过out发送给服务器
*/
OutputStreamWriter osr = new OutputStreamWriter(out,"utf-8");
/*
* 将字符流包装为缓冲字符流
* 就可以以行为单位写出字符串了。
*/
PrintWriter pw = new PrintWriter(osr);
Scanner sc = new Scanner(System.in);
while(true){
String str = sc.nextLine();
pw.println(str);
pw.flush();
}
}catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
Client client = new Client();
client.start();
} catch (Exception e) {
e.printStackTrace();
System.out.println("客户端初始化失败");
}
}
}
Server(2)
package day20150914socket2;
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.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 服务端应用程序
* (MINA的本质也是使用server)
*/
public class Server2 {
//服务端的Socket
private ServerSocket server;
//线程池,用于管理客户端连接的交互线程
private ExecutorService threadPool;
//保存所有客户端输出流的集合
private List allOut;
//构造方法,用于初始化服务端
public Server2() throws IOException{
try {
System.out.println("初始化服务端");
/*
* 创建ServerSocket时需要指定的服务端口
*/
server = new ServerSocket(8088);
//初始化线程池
threadPool = Executors.newFixedThreadPool(50);
/*
* 初始化存放所有客户端输出流的集合
* 使用ArrayList而不是linkedList的原因:
* 增删元素不频繁,而是使用遍历频繁
*/
allOut = new ArrayList();
System.out.println("服务端初始化完毕");
} catch (IOException e) {
throw e;
}
}
public void start(){
try {
/*
* ServerSocket的accept()方法:
* 用于监听8088端口,等待客户连接, 否则该方法阻塞。
* 若一个客户端连接了,会返回给客户端的Socket
*/
while(true){
System.out.println("等待客户端连接。。。");
Socket socket = server.accept();
/*
* 当一个客户端连接后,启动一个线程ClientHandler
* 将客户端的socket传入,使得该线程处理与该客户端的交互
* 这样,可再次进入循环,接收下一个客户端的连接
*/
Runnable handler = new ClientHandler(socket);
//Thread t = new Thread(handler);
//t.start();
/*
* 使用线程池分配空闲线程来处理当前连接的客户端
*/
threadPool.execute(handler);
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 将给定的输出流存入共享集合
*
* 加synchronized关键字,锁定Server对象,
* 下面3个方法锁定后,互斥(3个方法锁定同一个对象)
* 即一个线程访问其中一个方法后,
* 其他线程不可访问这3个方法中的任何一个
*/
public synchronized void addOut(PrintWriter pw){
allOut.add(pw);
}
/**
* 将给定的输出流从共享集合中删除
*/
public synchronized void removeOut(PrintWriter pw){
allOut.remove(pw);
}
/**
* 将给定的消息转发给所有客户端
*/
public synchronized void sendMessage(String message){
for(PrintWriter pw : allOut){
pw.println(message);
}
}
public static void main(String[] args) {
try {
Server2 server = new Server2();
server.start();
} catch (IOException e) {
e.printStackTrace();
System.out.println("服务器初始化失败");
}
}
/**
* 服务器端的一个线程,用于与某个客户端交互,
* 使用线程的目的是使得服务器可以处理多个客户端了
*/
class ClientHandler implements Runnable{
//当前线程处理的客户端的socket
private Socket socket;
//当前客户端的IP
private String ip;
//当前客户端的昵称
private String nickname;
/**
* 根据给定的客户端的Socket,创建线程体
*/
public ClientHandler(Socket socket){
this.socket = socket;
//获取远端(客户端)地址
InetAddress address = socket.getInetAddress();
//获取远端IP地址
ip = address.getHostAddress();
//获取远端端口号
int port = socket.getPort();
//改为使用昵称,所以不在这里通知了
//System.out.println(ip+":"+port+"客户端连接上了");
}
/**
* 该线程会将当前socket中的输入流获取
* 用来循环读取客户端发送过来的消息
*/
@Override
public void run() {
PrintWriter pw = null;
try{
/*
* 为了让服务端向客户端发送信息
* 通过socket获取输出流
*/
OutputStream out = socket.getOutputStream();
//转为字符流,用于指定编码集
OutputStreamWriter osr = new OutputStreamWriter(out,"utf-8");
//创建缓冲字符输出流,true自动行刷新
pw = new PrintWriter(osr,true);
/*
* 将该客户端的输出流存入共享集合
* 以便使得该客户端也能接收服务器转发的消息
*/
//allOut.add(pw);
addOut(pw);
System.out.println("当前在线人数:"+allOut.size());
/*
* 通过刚刚连接上来的客户端的Socket获取输入流
* 来读取客户端发过来的信息
*/
InputStream in = socket.getInputStream();
//将字节输入流包装为字符输入流,这样就可指定编码集
InputStreamReader isr = new InputStreamReader(in,"utf-8");
//将字符流转为缓冲字符输入流,这样就可以以行为单位来读取字符串了
BufferedReader br = new BufferedReader(isr);
//当创建好当前客户端的输入流后,读取的第一个字符串应当是昵称
nickname = br.readLine();
//通知所有客户端,当前用户上线了
sendMessage("["+nickname+"]上线了");
sendMessage("当前在线人数为:"+allOut.size());//告知所有客户端在线人数
String message = null;
/*
* 读取客户端发来的一行字符串
* windows与linux的差异:
* linux:当客户端断开连接后,通过输入流会读取到null,
* 这是合乎逻辑的,
* 因为缓冲流的readLine方法若返回null就无法通过该流读取到信息
*
* windows:当客户端与服务器端断开接连后
* readLine()方法会抛出异常
*/
while((message=br.readLine())!=null){
//System.out.println("客户端说:"+message);
//pw.println(message);//把客户端发来的消息回给客户端
//当前客户端说话内容告诉给所有客户端
sendMessage(nickname+"说:"+message);
}
}catch(Exception e){
/*
* 在windows中的客户端
* 报错通常是因为客户端断开了连接
*
* 不用关流,可直接关Socket
*/
}finally{
/*
* 首先将该客户端的输出流从共享集合中删除
*/
//allOut.remove(pw);
removeOut(pw);
//控制台显示该用户下线了
System.out.println("["+nickname+"]下线了");
//通知其他用户该用户下线了
sendMessage("["+nickname+"]下线了");
//输出当前在线人数(输出流的个数)
System.out.println("当前在线人数为:"+allOut.size());
sendMessage("当前在线人数为:"+allOut.size());//告知所有客户端在线人数
/*
* 无论是linux用户还是windows用户,
* 当客户与服务端断开连接后,
* 我们都应当在服务器端与客户端断开连接
*/
try {
socket.close();
//关闭之后,catch处理意义也不大,故catch块里的内容可为空
} catch (IOException e) {
}
//System.out.println("一个客户端下线了");
}
}
}
}
Client(2)
package day20150914socket2;
import java.io.BufferedReader;
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.util.Scanner;
public class Client2 {
//用于连接服务器端的Socket
private Socket socket;
public Client2() throws Exception{
try {
System.out.println("正在连接服务端。。。");
/*
* 创建Socket对象,
* 就会尝试根据给定的地址与端口连接服务器
* 所以,若该对象创建成功,说明与服务器端连接正常
*/
//localhost:本机。连接其他计算机可写IP
socket = new Socket("localhost",8088);
System.out.println("成功连接服务端。");
} catch (Exception e) {
throw e;
}
}
/**
* 客户端启动方法
*/
public void start(){
try{
//创建并启动线程,来接收服务器端发送过来的消息
Runnable runn = new GetServerInfoHandler();
Thread t = new Thread(runn);
t.start();
/*
* 可以通过Socket的getOutputStream()方法获取一条输出流
* 用于将信息发送至服务器
*/
OutputStream out = socket.getOutputStream();
/*
*使用字符流指定编码集将字符串转为字节后,
*再通过out发送给服务器
*/
OutputStreamWriter osr = new OutputStreamWriter(out,"utf-8");
/*
* 将字符流包装为缓冲字符流
* 就可以以行为单位写出字符串了。
*/
PrintWriter pw = new PrintWriter(osr,true);//true,自动行刷新
//创建一个Scanner,用于接收用户输入的字符串
Scanner sc = new Scanner(System.in);
//输出欢迎语
System.out.println("欢迎来到传奇的聊天室");
while(true){
System.out.println("请输入昵称");
String nickname = sc.nextLine();
if(nickname.trim().length()>0){
pw.println(nickname);
break;
}
System.out.println("昵称不能为空");
}
while(true){
String str = sc.nextLine();
pw.println(str);
//pw.flush();//PrintWriter自动行刷新就不要此句了
}
}catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
Client2 client = new Client2();
client.start();
} catch (Exception e) {
e.printStackTrace();
System.out.println("客户端初始化失败");
}
}
/**
* 该线程的作用是循环接收服务器端发送过来的信息,
* 并输出到控制台
*/
class GetServerInfoHandler implements Runnable{
@Override
public void run() {
try{
//通过socket获取输入流
InputStream in = socket.getInputStream();
//将字节输入流转为字符输入流,指定编码集
InputStreamReader isr = new InputStreamReader(in,"utf-8");
//将字符流转为缓冲字符输入流,这样就可以以行为单位来读取字符串了
BufferedReader br = new BufferedReader(isr);
String message = null;
//循环读取服务端发送过来的每个字符串
while((message=br.readLine())!=null){
//将服务端发送的字符串输出到控制台
System.out.println(message);
}
}catch(Exception e){
}
}
}
}