记得之前我去培训Android时,有一个Socket编程面试题目是:
使用Socket编程实现文件传输
当时的自己很单纯,想着什么都是自己做,不去查怎么做。记得文件内容传输的实现很简单,但是文件名称该怎么传却难到了我,在难也要自己做,后面多建立了一条Socket专门用来传输文件名。
Socket编程好久没用到过了,了解NIO的时候,看到了Socket,所以特地回忆下:
Socket
Socket编程根据通信协议的不同可以分为UDP(数据通信协议),TCP(流通信协议)/IP两种;
UDP协议用到的API: DatagramSocket
TCP/IP协议用到的API :ServerSocket
DatagramSocket
UDP是一种无连接的协议,这就意味着我们每次发送数据报时,需要同时发送本机的socket描述符和接收端的socket描述符,而且其在大小上有64KB的限制,是一种不可靠的协议,发送的数据报不一定会按照其发送顺序被接收端的socket接受;
这里主要是想总结TCP/IP,这里就直接给贴出网上找的代码
服务器端代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public static void main(String[] args) throws Exception{ int serverPort = 9999; DatagramSocket ds = null; DatagramPacket sendDp; DatagramPacket receiveDp; ds = new DatagramSocket(serverPort); System.out.println("服务器创建成功!端口号为: "+ds.getLocalPort()); byte[] buf = new byte[1024]; receiveDp = new DatagramPacket(buf,buf.length); ds.receive(receiveDp); System.out.println("收到: "+ receiveDp.getSocketAddress()); System.out.println("Data is "+ new String(receiveDp.getData(),0,receiveDp.getLength())); InetAddress clientIp = receiveDp.getAddress(); int clientPort = receiveDp.getPort(); String respose = "OK,收到来自星星的你的祝福"; byte[] bData = respose.getBytes(); sendDp = new DatagramPacket(bData,bData.length,clientIp,clientPort); ds.send(sendDp); ds.close(); }
|
客户器端代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public static void main(String[] args) throws Exception{ DatagramSocket ds = null;// DatagramPacket sendDp; DatagramPacket receiveDp; String serverHost = "127.0.0.1"; int serverPort = 9999; ds = new DatagramSocket(); byte[] buf = "hello,UDP协议!来自星星的问候……".getBytes(); sendDp = new DatagramPacket(buf,buf.length,InetAddress.getByName(serverHost),serverPort); ds.send(sendDp); byte[] bufr = new byte[1024]; receiveDp = new DatagramPacket(bufr,bufr.length); ds.receive(receiveDp); byte[] response = receiveDp.getData(); int len = receiveDp.getLength(); String s = new String(response,0,len); System.out.println("服务器端反馈为: "+s); ds.close(); }
|
ServerSocket
直接使用线程BIO
如果使用Socket编程,编写服务端,如果客户端有多个,那么编程模型就是1:N的关系;
服务端代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| public class Server {
public void start() throws IOException {
ServerSocket serverSocket = new ServerSocket(9091); System.out.println("∂ 服务真在等待客户端接入请求"); for (; ; ) { System.out.println("∂ for accept 之前"); Socket socket = serverSocket.accept(); new Thread(new ScoketChanil(socket)).start(); } }
public static void main(String[] args) throws IOException { new Server().start(); } }
public class ScoketChanil implements Runnable {
public Socket socket; public ScoketChanil(Socket socket) { this.socket = socket; }
@Override public void run() { InputStream inputStream = null; OutputStream outputStream = null; try { inputStream = socket.getInputStream(); outputStream = socket.getOutputStream(); byte[] bytes = new byte[1024];
int len; while ((len = inputStream.read(bytes)) != -1) { String requestStr = new String(bytes, 0, len); System.out.println(requestStr); outputStream.write("yeah im copy".getBytes("UTF-8")); } } catch (IOException e) { e.printStackTrace(); } finally { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }
|
客户端代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| public class MultipleClientA { public static void main(String[] args) throws IOException { new Client("AAAA").start(); } }
public class Client {
private String clientName;
public Client(String clientName) { this.clientName = clientName; }
public void start() throws IOException { Socket socket = new Socket("127.0.0.1", 9091); OutputStream outputStream = socket.getOutputStream(); InputStream inputStream = socket.getInputStream();
new Thread(new Runnable() { @Override public void run() { byte[] bytes = new byte[1024]; int len; try { while ((len = inputStream.read(bytes)) != -1) { String s = new String(bytes, 0, len); System.out.println("接受到服务器端的相应 " + s); } } catch (IOException e) { e.printStackTrace(); } finally {
} } }).start();
Scanner scanner = new Scanner(System.in); while (scanner.hasNextLine()) { String inputStr = scanner.next(); if ("bye".equals(inputStr)) { break; } outputStream.write((clientName + inputStr).getBytes("UTF-8")); } outputStream.close(); scanner.close(); } }
|
使用线程池的伪BIO
如果使用Socket编程,编写服务端,如果客户端有多个,那么编程模型就是1:N的关系,服务器端很可能会出现线程太多导致CPU消耗殆尽,导致服务卡死或者宕机;所以出现了使用线程池来限制管理线程的伪BIO;
服务端代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class SupportThreadPoolServer {
public void start() throws IOException {
ServerSocket serverSocket = new ServerSocket(9091); ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(2); ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(2, 2, 100000, TimeUnit.MILLISECONDS, queue);
for (; ; ) { Socket socket = serverSocket.accept(); poolExecutor.execute(new ScoketChanil(socket)); }
}
public static void main(String[] args) throws IOException { new SupportThreadPoolServer().start(); } }
|
客户端代码
客户端的代码没变化
ServerSocket阻塞方法
ServerSocket的accept()是一个阻塞方法,如果没有Socket接入会一直阻塞,直到有Socket建立连接。
文件传输问题解决
到了今天我还是没能想清楚怎么传输名字,但是我学会了查资料,把查到的内容学会了,也是自己的。
传输文件名客户端可以用DataOutputStream.writeUTF(“XXX”)将名字写出,服务器端使用DataInputStream.readUTF()得到名字;
自己的Java基础是很功利的看了一段时间视频学习的,清晰的记得学习路线是从最简单的语法到对象,再到集合,线程,IO,Socket编程,最终是泛型和反射;
今天自己也不那么较真了,不会的,虽然网上找到的不是自己的,但是学会了就是自己的。
GitHub登录不了?信任该网站之后再登录。