一
TCP通信过程
二
主要API
三
用线程池技术实现的服务器和客户端案例
3.1
Server
package demoServer;
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;
/**
* 服务端应用程序
* @author Administrator
*
*/
public class Server {
//运行在服务端的Socket
private ServerSocket server;
//线程池,用于管理客户端连接的交互线程
private ExecutorService threadPool;
//保存所有客户端输出流的集合
private List allOut;
/**
* 构造方法,用于初始化服务端
* @throws IOException
*/
public Server() throws IOException{
try {
/*
* 创建ServerSocket时需要指定服务端口
*/
System.out.println("初始化服务端");
server = new ServerSocket(8088);
//初始化线程池
threadPool =
Executors.newFixedThreadPool(50);
//初始化存放所有客户端输出流的集合
allOut = new ArrayList();
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 handler
= new ClientHandler(socket);
// Thread t = new Thread(handler);
// t.start();
/*
* 使用线程池分配空闲线程来处理
* 当前连接的客户端
*/
threadPool.execute(handler);
}
}catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] args){
Server server;
try {
server = new Server();
server.start();
} catch (IOException e) {
e.printStackTrace();
System.out.println("服务端初始化失败");
}
}
/**
* 服务端中的一个线程,用于与某个客户端
* 交互。
* 使用线程的目的是使得服务端可以处理多
* 客户端了。
* @author Administrator
*
*/
class ClientHandler implements Runnable{
//当前线程处理的客户端的Socket
private Socket socket;
/**
* 根据给定的客户端的Socket,创建
* 线程体
* @param socket
*/
public ClientHandler(Socket socket){
this.socket = socket;
/*
* 通过socket获取远端的地址信息
* 对于服务端而言,远端就是客户端了
*/
InetAddress address
= socket.getInetAddress();
//获取远端计算机的IP地址
String ha = address.getHostAddress();
// address.getCanonicalHostName()
//获取客户端的端口号
int port = socket.getPort();
System.out.println(
ha+":"+port+" 客户端连接了");
}
/**
* 该线程会将当前Socket中的输入流获取
* 用来循环读取客户端发送过来的消息。
*/
public void run() {
/*
* 定义在try语句外的目的是,为了在
* finally中也可以引用到
*/
PrintWriter pw = null;
try{
/*
* 为了让服务端与客户端发送信息,
* 我们需要通过socket获取输出流。
*/
OutputStream out
= socket.getOutputStream();
//转换为字符流,用于指定编码集
OutputStreamWriter osw
= new OutputStreamWriter(
out,"UTF-8");
//创建缓冲字符输出流
pw = new PrintWriter(osw,true);
/*
* 将该客户端的输出流存入共享集合
* 以便使得该客户端也能接收服务端
* 转发的消息
*/
allOut.add(pw);
/*
* 通过刚刚连上的客户端的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);
pw.println(message);
}
}catch(Exception e){
//在Windows中的客户端,
//报错通常是因为客户端断开了连接
}finally{
/*
* 首先将该客户端的输出流从共享
* 集合中删除。
*/
allOut.remove(pw);
/*
* 无论是linux用户还是windows
* 用户,当与服务端断开连接后
* 我们都应该在服务端也与客户端
* 断开连接
*/
try {
socket.close();
} catch (IOException e) {
}
System.out.println(
"一个客户端下线了...");
}
}
}
}
package demoClient;
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 {
//Socket,用于连接服务端的ServerSocket
private Socket socket;
/**
* 客户端构造方法,用于初始化客户端
* @throws Exception
*/
public Client() throws Exception{
try {
/*
* 创建Socket对象时,就会尝试根据
* 给定的地址与端口连接服务端。
* 所以,若该对象创建成功,说明与
* 服务端连接正常。
*
*
*
*/
System.out.println("正在连接服务端...");
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 osw
= new OutputStreamWriter(
out,"UTF-8");
/*
* 将字符流包装为缓冲字符流,就可以
* 按行为单位写出字符串了
*/
PrintWriter pw
= new PrintWriter(osw,true);
/*
* 创建一个Scanner,用于接收用户
* 输入的字符串
*/
Scanner scanner
= new Scanner(System.in);
while(true){
String str = scanner.nextLine();
pw.println(str);
}
}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("客户端初始化失败");
}
}
/**
* 该线程的作用是循环接收服务端发送过来
* 的信息,并输出到控制台
* @author Administrator
*/
class GetServerInfoHandler
implements Runnable{
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){
}
}
}
}
UDP
4.1
客户端通信过程
/*
* 向服务端发送数据的步骤:
* 1:创建好Socket(一次就行)
* 2:准备数据
* 3:创建数据包
* 4:将数据存入包中 (3,4是一步完成的)
* 5:将数据包通过socket发送给服务端
*/
代码
package demoServer.udp;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* 客户端
* @author Administrator
*
*/
public class Client {
/**
* 客户端的启动方法
*/
public void start(){
try{
/*
* 向服务端发送数据的步骤:
* 1:创建好Socket(一次就行)
* 2:准备数据
* 3:创建数据包
* 4:将数据存入包中 (3,4是一步完成的)
* 5:将数据包通过socket发送给服务端
*/
DatagramSocket socket
= new DatagramSocket();
String str = "你好!服务端!";
byte[] data
= str.getBytes("UTF-8");
//打包:准备包裹,填写地址,装入数据
InetAddress address
= InetAddress.getByName(
"localhost");
int port = 8088;
//创建发送包
DatagramPacket sendPacket
= new DatagramPacket(
data,
data.length,
address,
port
);
//将包发送出去
socket.send(sendPacket);
/*
* 接收服务端发送回来的信息
*/
data = new byte[100];
DatagramPacket recvPacket
= new DatagramPacket(
data,
data.length
);
//接收数据到包中
//注意,该方法是个阻塞方法。
socket.receive(recvPacket);
//拆包拿数据
byte[] d = recvPacket.getData();
//有效数据长度
int dlen = recvPacket.getLength();
/**
* String(
* byte[] b,
* int offset,
* int len,
* String charsetName)
* 将给定的字节数组中,从offset处
* 开始连续len个字节,再根据给定的字符集
* 转换为字符串
*/
String info
= new String(d,0,dlen,"UTF-8");
System.out.println("服务端说:"+info);
}catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] args){
Client client = new Client();
client.start();
}
}
服务器通信过程
/*
* 接收包的步骤:
* 1:创建Socket(一次)
* 2:创建一个合适大小的包
* 3:通过socket接收数据到包中
* 4:拆包取数据
*/
代码
package demoClient.udp;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* 服务端
* @author Administrator
*
*/
public class Server {
/**
* 服务端的启动方法
*/
public void start(){
try{
/*
* 接收包的步骤:
* 1:创建Socket(一次)
* 2:创建一个合适大小的包
* 3:通过socket接收数据到包中
* 4:拆包取数据
*/
DatagramSocket socket
= new DatagramSocket(8088);
byte[] data = new byte[100];
DatagramPacket recvPacket
= new DatagramPacket(
data,
data.length
);
//接收数据到包中
//注意,该方法是个阻塞方法。
socket.receive(recvPacket);
//拆包拿数据
byte[] d = recvPacket.getData();
//有效数据长度
int dlen = recvPacket.getLength();
/**
* String(
* byte[] b,
* int offset,
* int len,
* String charsetName)
* 将给定的字节数组中,从offset处
* 开始连续len个字节,再根据给定的字符集
* 转换为字符串
*/
String info
= new String(d,0,dlen,"UTF-8");
System.out.println("客户端说:"+info);
//回复客户端
String str = "你好!客户端!";
data = str.getBytes("UTF-8");
//打包:准备包裹,填写地址,装入数据
InetAddress address
= recvPacket.getAddress();
int port = recvPacket.getPort();
//创建发送包
DatagramPacket sendPacket
= new DatagramPacket(
data,
data.length,
address,
port
);
//将包发送出去
socket.send(sendPacket);
}catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] args){
Server server = new Server();
server.start();
}
}