Socket:所谓套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议根进行交互的接口
我们就使用java开发一个简单的聊天工具,看代码:
客户端代码:
package com.zhang.test.socket;
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;
/**
* 聊天室客户端
* @author Administrator
*
*/
public class Client {
/*
* 套接字
* 封装着TCP协议的通讯
* 使用它可以进行基于TCP协议的网络通讯
*/
private Socket socket;
/**
* 构造方法,用来初始化客户端
*/
public Client(){
try {
/*
* 实例化Socket的时候需要传入
* 两个参数:
* 1:远端计算机的IP地址
* 2:远端计算机的端口
* 通过IP地址可以找到服务端的
* 计算机,通过端口可以找到运行
* 在该机器上的服务端应用程序。
*
* 实例化Socket的过程就是连接
* 远端计算机的过程,若远端计算机
* 没有响应,会抛出异常导致实例化
* 失败。
*/
System.out.println("正在连接服务端...");
socket = new Socket("localhost",8088);
System.out.println("连接服务端完毕!");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 客户端开始工作的方法
*/
public void start(){
try {
/*
* 首先将读取服务端发送过的消息的
* 线程启动起来。
*/
ServerHandler handler= new ServerHandler();
Thread t = new Thread(handler);
t.start();
Scanner scanner = new Scanner(System.in);
String nickname = null;
while(true){
System.out.println("请输入昵称:");
nickname = scanner.nextLine();
if(nickname.length()==0){
System.out.println("请至少输入一个字符.");
continue;
}
break;
}
/*
* Socket提供了方法:
* OutputStream getOutputStream()
* 获取一个字节输出流,通过该流
* 写出的字节会被发送到远端计算机
*/
OutputStream out = socket.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
PrintWriter pw = new PrintWriter(osw,true);
//单独发送昵称
pw.println(nickname);
System.out.println("你好!"+nickname+",开始聊天吧!");
String message = null;
while(true){
message = scanner.nextLine();
pw.println(message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
Client client = new Client();
client.start();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 该线程用于读取服务端发送过来的消息,
* 并输出到客户端的控制台
* @author Administrator
*
*/
private class ServerHandler implements Runnable{
public void run() {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(),"UTF-8"));
String message = null;
while((message = br.readLine())!=null){
System.out.println(message);
}
} catch (Exception e) {
}
}
}
}
服务端代码:
package com.zhang.test.socket;
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;
/**
* 聊天室服务端
* @author Administrator
*
*/
public class Server {
/*
* java.net.ServerSocket
* 运行在服务端的ServerSocket负责申请
* 服务端口,客户端就是通过这个端口与
* 这边服务端应用程序建立连接的。
* ServerSocket的另一个职责就是监听该
* 端口,一旦一个客户端连接,这边就会
* 自动创建一个Socket,通过这个Socket就
* 可以与刚刚连接的客户端通讯了。
*/
private ServerSocket server;
/*
* 该集合用户存放所有客户端的输出流
* 以便于服务端广播消息
*/
private List<PrintWriter> allOut;
public Server(){
try {
/*
* 初始化ServerSocket时要指定
* 服务端口,客户端通过该端口
* 进行连接,该端口号不能与操作
* 系统其它应用程序申请的重复,
* 否则会抛出异常
*/
server = new ServerSocket(8088);
allOut = new ArrayList<PrintWriter>();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 向共享集合中添加一个新的输出流
* @param out
*/
private synchronized void addOut(PrintWriter out){
allOut.add(out);
}
/**
* 将给定的输出流从共享集合中删除
* @param out
*/
private synchronized void removeOut(PrintWriter out){
allOut.remove(out);
}
/**
* 遍历共享集合中的所有输出流,将给定的
* 消息发送给所有客户端
* @param message
*/
private synchronized void sendMessage(String message){
for(PrintWriter out : allOut){
out.println(message);
}
}
public void start(){
try {
/*
* 该方法是一个阻塞方法,用于一直
* 监听服务端口(8088),直到一个客户
* 端连接,然后就创建一个与该客户端
* 通讯的Socket
*/
while(true){
System.out.println("等待客户端连接...");
Socket socket = server.accept();
System.out.println("一个客户端连接了!");
//启动一个线程来处理该客户端的交互
ClientHandler handler= new ClientHandler(socket);
Thread t = new Thread(handler);
t.start();
}
} catch (Exception e) {
}
}
public static void main(String[] args) {
try {
Server server = new Server();
server.start();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 该线程负责与一个客户端进行交互
* @author Administrator
*
*/
private class ClientHandler implements Runnable{
private Socket socket;
//客户端的地址信息
private String host;
//客户端的昵称
private String nickname;
public ClientHandler(Socket socket){
this.socket = socket;
/*
* 获取远程计算机的地址信息
*/
InetAddress address = socket.getInetAddress();
//获取客户端ip地址的字符串格式
host = address.getHostAddress();
}
public void run(){
PrintWriter pw = null;
try {
/*
* 通过Socket获取输出流,以便可以将
* 消息发送给客户端
*/
OutputStream out = socket.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
pw = new PrintWriter(osw,true);
//将该输出流存入共享集合
addOut(pw);
/*
* Socket提供的方法:
* InputStream getInputStream()
* 该方法获取的输入流是用来读取远端计算机
* 发送过来的数据的。
*/
InputStream in = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(in,"UTF-8");
BufferedReader br = new BufferedReader(isr);
//客户端发送的第一个字符串是昵称
nickname = br.readLine();
//通知所有客户端该用户上线了
sendMessage(nickname+"上线了!当前在线人数:"+allOut.size()+"人");
String message = null;
while((message = br.readLine())!=null){
/*
* 当客户端断开连接后,由于客户端的
* 操作系统不同,服务端这里br.readLine
* 方法的执行结果也不同:
* 当windows客户端断开连接后:br.readLine
* 方法会直接抛出异常
* 当linux客户端断开连接后:br.readLine
* 方法会返回null。
*/
// message = br.readLine();
// System.out.println(host+"说:"+message);
//将读到的话回复给当前客户端(暂时)
// pw.println(host+"说:"+message);
//将读到的话转发给所有客户端
sendMessage(nickname+"说:"+message);
}
} catch (Exception e) {
} finally{
//处理客户端断开连接后的操作
//将该客户端的输出流从共享集合中删除
removeOut(pw);
sendMessage(nickname+"下线了.当前在线人数:"+allOut.size()+"人");
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
使用的时候,要先启动服务端,再启动客户端,
控制台打印出这些,就连接成功了,可以给两个机器上分别装上,测试一下,就可以互相聊天了