Socket编程:
什么是Socket编程,socket编程是建立在传输层之上的,应用层之下的端对端网络通信:如图
所以按照传输层协议 Socket编程是分两大类(因为传输层主要是两类TCP/UDP):
1:TCP
2:UDP
另一种分类:阻塞和非阻塞编程
Socket编程的分类图:
TCP的同步阻塞通信:
主要依靠的有两个类:Socket和ServerSocket
Socket作用:
1.建立和远程服务器端的连接 如:Socket socket=new Socket("dict.org",2628); //连接的是dict服务器端
2.发送数据 如:PrintWriter pw=new PrintWriter(socket.getOutputStream())
3.接受数据 如: Scanner sc=new Scanner(socket.getInputStream() )
ServerSocket作用:
1等待连接 如:ServerSocket connect=new ServerSocket(5172))//在开一个本地端口 作为服务器端口 等待客户端来连接
2建立连接 如:Socket socket = connect.accept();//当有客户端连接我们建立的服务器时 在服务器端调用ServerSocket的accept()方法来获取Socket对象
下面是TCP阻塞Echo服务器的例子和其搭配的客户端(主要就是使用上面我们介绍的Socket和ServerSocket对象):
echo服务器:
package pre.guowei.Sock;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
/**
* TCP阻塞Echo服务器()
* @author 22116
*
*/
public class EchoServer {
public static void main(String[] args) {
try(ServerSocket connect=new ServerSocket(5172))
{
while (true) {
Socket socket = connect.accept();
new Thread(new Runnable() {
@Override
public void run() {
try (Scanner sc = new Scanner(socket.getInputStream());
PrintWriter pw = new PrintWriter(socket.getOutputStream(), true)) {
System.out.println("收到来自 " + socket.getRemoteSocketAddress() + "连接");
while (true) {
String s = sc.nextLine();
if (s.equals("see you"))
break;
pw.println(s);
pw.flush();
}
if (socket != null)
{
System.err.println("客户端"+socket.getRemoteSocketAddress()+"断开连接");
socket.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
对于上面的服务器其实严格上来说是非阻塞的 因为我使用了多线程 (但就每个线程来说是阻塞的)
这里最好是用线程池,因为我们的服务器资源是有限的不可能应客户端请求去无限的创建线程,那样的话服务器几下就被搞死了,改良之后线程池实现:
package pre.guowei.Sock;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.print.attribute.standard.Severity;
/**
* TCP阻塞Echo服务器
* @author 22116
*
*/
public class EchoServer {
public static void main(String[] args) {
ExecutorService pool=Executors.newFixedThreadPool(20);
try(ServerSocket connect=new ServerSocket(5109))
{
while (true) {
Socket socket = connect.accept();
socket.setSoTimeout(10000);
ServerWork t=new ServerWork(socket);
pool.submit(t);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class ServerWork implements Runnable{
private Socket socket;
public ServerWork(Socket socket) {
super();
this.socket = socket;
}
public void run() {
try (Scanner sc = new Scanner(socket.getInputStream());
PrintWriter pw = new PrintWriter(socket.getOutputStream(), true)) {
System.out.println("收到来自 " + socket.getRemoteSocketAddress() + "连接");
while (true) {
String s = sc.nextLine();
if (s.equals("see you"))
break;
pw.println(s);
pw.flush();
}
if (socket != null)
{
System.err.println("客户端"+socket.getRemoteSocketAddress()+"断开连接");
socket.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
echo客户端:(客户端的话就没有必要非阻塞)
package pre.guowei.Sock;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
/**
* Echo客户端
* @author 22116
*
*/
public class ClientForEcho {
public static void main(String[] args) {
try(Socket socket=new Socket("localhost",5172);
PrintWriter pw=new PrintWriter(socket.getOutputStream(),true);
Scanner scForIn=new Scanner(socket.getInputStream());
Scanner sc=new Scanner(System.in)){
String line=sc.next();
while(!line.equals("end"))
{
pw.println(line);
if(scForIn.hasNextLine())
System.out.println(scForIn.nextLine());
line=sc.next();
}
pw.println("see you");
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
TCP的同步非阻塞通信:
主要使用的类是:
1.SelectableChannel类:SocketChannel和ServerSocketChannel
2.Selector类
3.SelectionKey类
其主要的关系是:SelectableChannel向Selector调用register()方法注册监视事件 并得到对应的SelectionKey
TCP同步非阻塞通信实例:
文件发送服务器:
package pre.guowei.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Iterator;
import java.util.Set;
/**
* 利用tcp非阻塞编程的服务器
* 为所有连接此服务器的客户端发送aaa.txt
* @author 22116
*
*/
//把数据文件内容读到一个ByterBuffer中,每来一个客户端把这个ByterBuffer复制(duplicate)一份
// 作为客户端向Selector对象注册写就绪事件时产生的SelectionKey的附件
// 通过这个duplicate出来的ByteBuffer为每个客户端记录发送进度
public class FlightDataServer5 {
private ServerSocketChannel serverSocketChannel;
private static final int PORT = 8888;
private Selector selector;
// 用于装载数据文件内容的字节缓冲区
private ByteBuffer flightDataFileBuffer;
public FlightDataServer5() throws Exception {
serverSocketChannel = ServerSocketChannel.open();
selector = Selector.open();
serverSocketChannel.configureBlocking(false);//设成非阻塞
ServerSocket serverSocket = serverSocketChannel.socket();//创建 和serverSockeChannel关联的ServerSocket
serverSocket.setReuseAddress(true);
serverSocket.bind(new InetSocketAddress(PORT));
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);//seversocketchannel对象向selector注册接受连接就绪事件
System.out.println("服务器启动成功");
// 将数据文件内容读入到dataFileBuffer里
// File file = new File("fds_data(lab4,5).txt");
// int flightDataFileLength = (int) file.length();
// flightDataFileBuffer = ByteBuffer.allocateDirect(flightDataFileLength);
// FileInputStream fis = new FileInputStream(file);
// FileChannel fileChannel = fis.getChannel();
// fileChannel.read(flightDataFileBuffer);
// flightDataFileBuffer.flip();
// fis.close();
//或者这样写
Path file = FileSystems.getDefault().getPath("aaa.txt");
byte[] data = Files.readAllBytes(file);
flightDataFileBuffer = ByteBuffer.wrap(data);
}
// 万能的Reactor,在所有的服务器程序里写法一样
public void service() throws IOException {
while (selector.select() > 0) {
/*
* 在执行Selector对象的select()方法时,如果与SelectionKey相关联的事件发生了,
*这个SelectionKey就会被加入到Selector对象的selected-keys集合中
*/
Set<SelectionKey> selectedKeys = selector.selectedKeys();
//叠代器
Iterator<SelectionKey> iterator = selectedKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = null;
try {
key = (SelectionKey) iterator.next();
iterator.remove();
if (key.isAcceptable()) {
accept(key);
}
if (key.isWritable()) {
send(key);
}
// if (key.isReadable()) {
// //本程序不关心读就绪事件
// }
} catch (IOException e) {
e.printStackTrace();
try {
if (key != null) {
key.cancel();
key.channel().close();
}
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
}
private void accept(SelectionKey key) throws IOException {
SocketChannel socketChannel = serverSocketChannel.accept();
System.out
.println("接收到来自 :" + socketChannel.getRemoteAddress() + "的连接");
socketChannel.configureBlocking(false);//socketchannel设置成非阻塞
ByteBuffer duplicatedBuffer = flightDataFileBuffer.duplicate(); //复制一个装好要传送文件的bytebuffer
//socketchannel向selector对象注册写就绪事件并且将bytebuffer attach到key
socketChannel.register(selector, SelectionKey.OP_WRITE, duplicatedBuffer);
//·这样写也行
//socketChannel.register(selector, SelectionKey.OP_WRITE);
//key.attach(duplicatedBuffer);
}
// 从duplicatedBuffer里取出数据并发送给客户端,每次能发多少发多少
private void send(SelectionKey key) throws IOException {
ByteBuffer duplicatedBuffer = (ByteBuffer) key.attachment();
SocketChannel socketChannel = (SocketChannel) key.channel();
if(duplicatedBuffer.hasRemaining()) {
int oldPosition = duplicatedBuffer.position();
socketChannel.write(duplicatedBuffer); //能发多少发多少
//把本次实际发送的数据打印出来
int newPosition = duplicatedBuffer.position();
int length = newPosition - oldPosition;
byte[] dataSent = new byte[length];
//duplicatedBuffer.get(oldPosition, dataSent, 0, length);
duplicatedBuffer.position(oldPosition);
duplicatedBuffer.get(dataSent);
System.out.println("给客户端" + socketChannel.getRemoteAddress() + "发送了数据:"
+ new String(dataSent));
} else {
System.out.println(
"对客户端" + socketChannel.getRemoteAddress() + "的所有数据已经发送完毕");
socketChannel.close();
key.cancel();
}
}
public static void main(String[] args) throws Exception {
new FlightDataServer5().service();
}
}
/*首先构造函数里面先
*1创建serversocketchannel对象并设置非阻塞
*2创建serversocket对象并为其绑定端口(是通过serversocketchannel里socket()方法创建出来的)
*3创建selector对象
*4serversocket对象向selector注册接受就绪事件返回selectorkey对象
*5如果有什么工具要加入到key中就可以调用key.attach()方法
*/
/*
* 在执行方法里 首先while()selector()方法是否>0 即是否有就绪事件发生 同时就绪事件相关联的key会被变成selectoredkey
* 然后迭代selectedkey 对每一个进行判断什么事件就绪了 进行处理
* 最后调用selectionkey的cancel()方法使selectionkey失效
* 关闭serversocketchannel 通过channel()方法获取和selectionkey关联的该对象 再调用close即可
*
*/
其中最重要的是要掌握阻塞通信的框架:
if(selector.selector()>0)
{
set<SelectionKey> selectedKeys=selector.selectedKeys();
Iterator<SelectionKey> iterator=selectedKeys.iterator();
while(iterator.hasNext())
{
SelectionKey key=(SelectionKey)iterator.next();
iterator.remove();
if(key.isAcceptable())
处理连接就绪
if(Key.isWritable())
处理写就绪事件
if(Key.isReadable())
处理读就绪事件
}
}
简单的测试TCP非阻塞服务器的客户端
package pre.guowei.nio;
import java.io.InputStream;
import java.net.Socket;
import java.util.Scanner;
public class FlightDataClient {
public static void main(String[] args) throws Exception {
try (Socket socket = new Socket("localhost", 8888);
Scanner scanner = new Scanner(socket.getInputStream())) {
while(scanner.hasNextLine()) {
System.out.println(scanner.nextLine());
}
}
}
}
关于异步通信本篇博客就不做介绍 UDP通信的话 参考本人的另两篇博客