赞
踩
想知道Socket是什么就先得了解一下什么是网络编程
网络编程,通过代码来控制两个主机的进程之间能够进行数据交互。
操作系统就把网络编程的一些相关操作,封装起来了,提供了一组API供程序员使用。操作系统提供的功能,访问网络核心的硬件设备,网卡。网卡也是归操作系统来管理的
操作系统提供的socket api 是C语言风格的接口,在Java中是不能直接使用的。JDK其实也针对C语言这里的 socket API 进行了封装,在标准库中有一组类,这组类就能够让我们完成网络编程,这组类本质上仍然是调用的操作系统提供的socketAPI
操作系统,提供的 socket API主要有两类(实际上不止两类),它属于传输层
TCP和UDP这里只是简单说一下它们的特点,便于理解Socket编程,详细的会在后面的博客中写到
TCP
UDP
有连接:类似于微信视频,需要接通才能说话
无连接:类似于发微信消息,直接发就好了
可靠传输:发送方能知道对方是否收到消息
不可靠传输:发送方不知道是不是收到了消息
注意:可靠性 != 安全性
面向字节流:
假设发送数据为1000个字节,可以一次性发10个字节重复发100次,也可以一次发100个字节,重复发送10次,可以非常灵活的完成这里的发送,接收也是同理
TCP的文件读写都是面向字节流的
面向数据报:
以一个一个的数据报为基本单位(每个数据报多大,不同的协议里面是有不同的约定的)
发送的时候,一次至少发送一个数据报,如果尝试发送一个半,实际可能只能发出去一个
接收的时候,一次至少接收一个数据,如果尝试接收半个,剩下半个就没了
全双工:双向通信,A和B可以同时向对方发送数据
半双工:单向通信,要么A给B发,要么B给A发,不能同时发
就类似于两根水管和一根水管的区别
一个服务器的核心流程
1. 读取请求并解析
2. 根据请求计算响应
3. 把响应写回客户端
一个客户端的核心流程
1. 根据用户输入,构造请求
2. 发送请求给服务器
3. 读取服务器的响应
4. 解析响应并显示
DatagramSocket API 是UDP Socket,用于发送和接收UDP数据报
方法名 | 说明 |
---|---|
DatagramSocket() | 创建一个UDP数据报套接字的Socket,绑定到本机任意一个端口号(一般用于客户端) |
DatagramSocket(int port) | 创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用于服务端) |
方法名 | 说明 |
---|---|
void receive(DatagramPacket p) | 从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待) |
void send(DatagramPacket p) | 从此套接字发送数据报包(不会阻塞等待,直接发送) |
void close() | 关闭此数据报套接字 |
构造UDP发送的数据报时,需要传入 SocketAddress ,该对象可以使用 InetSocketAddress 来创建。
InetSocketAddress ( SocketAddress 的子类 )构造方法
方法名 | 说明 |
---|---|
InetSocketAddress(InetAddress addr, int port) | 创建一个Socket地址,包含IP地址和端口号 |
回显服务器就是客户端发送什么请求服务器就返回什么请求,UDP是不需要建立连接的
服务器代码
import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException; public class UdpEchoServer { private DatagramSocket socket; public UdpEchoServer(int port) throws SocketException { this.socket = new DatagramSocket(port); } private void start() throws IOException { System.out.println("服务器启动成功"); while (true) { // 1.读取请求并解析 DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096); this.socket.receive(requestPacket); String request = new String(requestPacket.getData()); // 2.根据请求计算响应 String response = process(request); // 3.把响应返回给客户端 DatagramPacket responsePacket = new DatagramPacket(request.getBytes(),0,request.getBytes().length, requestPacket.getSocketAddress()); this.socket.send(responsePacket); // 4.打印日志 String log = String.format("[%s:%d] request: %s response: %s",requestPacket.getAddress().toString(),requestPacket.getPort(), request,response); System.out.println(log); } } /** * 这是一个回显服务器 * @param request */ private String process(String request) { //发送什么请求就返回什么响应 return request; } public static void main(String[] args) throws IOException { UdpEchoServer udpEchoServer = new UdpEchoServer(9090); udpEchoServer.start(); } }
客户端代码
import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetSocketAddress; import java.net.SocketException; import java.util.Scanner; public class UdpEchoClient { private DatagramSocket socket; private int serverPort; private String serverIp; private InetSocketAddress inetSocketAddress; public UdpEchoClient(int port,String ip) throws SocketException { //客户端的IP和端口号由操作系统自动分配 this.socket = new DatagramSocket(); this.serverPort = port; this.serverIp = ip; this.inetSocketAddress = new InetSocketAddress(this.serverIp, this.serverPort); } public void start() throws IOException { System.out.println("客户端启动成功"); Scanner sc = new Scanner(System.in); while (true) { // 1.从键盘输入请求并构造 System.out.print("-> "); String request = sc.nextLine(); if ("exit".equals(request)) { String log = String.format("客户端退出[%s:%d]",this.serverIp,this.socket.getPort()); System.out.println(log); break; } DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,this.inetSocketAddress); // 2.把请求发送给服务器 this.socket.send(requestPacket); // 3.从服务器获取响应 DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096); this.socket.receive(responsePacket); String response = new String(responsePacket.getData()); // 4.打印日志 String log = String.format("[%s:%d] request: %s response: %s",this.serverIp,responsePacket.getPort(),request,response); System.out.println(log); } } public static void main(String[] args) throws IOException { UdpEchoClient udpEchoClient = new UdpEchoClient(9090,"127.0.0.1"); udpEchoClient.start(); } }
TCP的套接字API和UDP是完全 不同的
ServerSocket 是创建TCP服务端Soket的API
方法名 | 说明 |
---|---|
ServerSocket(int port) | 创建一个服务端流套接字Socket,并绑定到指定端口 |
方法名 | 方法说明 |
---|---|
Socket accept() | 开始监听指定端口(创建时绑定的端口),有客户端连接后,放回一个服务端Socket对象,并基于该Socket建立于客户端的连接,否则阻塞等待 |
void close() | 关闭该套接字,防止内存泄露 |
Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,放回的服务端Socket
不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来和对方收发数据的
方法名 | 说明 |
---|---|
Socket(String host, int port) | 创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接 |
方法名 | 说明 |
---|---|
InetAddress getInetAddress() | 返回套接字所连接的地址 |
InputStream getInputStream() | 放回此套接字的输入流 |
OutputStream getOutputStream() | 放回此套接字的输入流 |
TCP发送数据时,需要先建立连接,什么时候关闭连接就决定是短连接还是长连接
短连接: 每次收到数据并返回响应后,都关闭
长连接: 不关闭连接,一直保持连接状态,双方不停的收发数据,就是长连接,也就是说,长连接可以多次收发数据
对比长短 连接,两者区别如下
建立连接、关闭连接的耗时
短连接每次请求、响应都需要建立连接,关闭连接。而长连接至需要第一次连接,之后的请求、响应都可以直接传输。相对来说建立连接,关闭连接也是耗时的,长连接效率更高
主动发送请求不同
短连接一般是客户端主动向服务器发送请求,而长连接可以是客户端主动发送请求,也可以是服务端主动发
两者的使用场景不同
短连接适用于客户端请求频率不高的场景,入浏览网页等。长连接适用于客户端与服务端通信频繁的场景,如聊天室、实时游戏等
服务器代码
import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.util.Scanner; public class TcpEchoServer { private ServerSocket listenSocket; public TcpEchoServer(int port) throws IOException { this.listenSocket = new ServerSocket(port); } private void start() throws IOException { System.out.println("服务器启动成功"); while (true) { // TCP套接字先要建立连接 Socket socket = this.listenSocket.accept(); //用Thread来处理多个客户端的情况 Thread thread = new Thread(new Runnable() { @Override public void run() { try { connectionProcess(socket); } catch (IOException e) { e.printStackTrace(); } } }); thread.start(); } } private void connectionProcess(Socket socket) throws IOException { try (InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream()) { Scanner sc = new Scanner(inputStream); PrintWriter printWriter = new PrintWriter(outputStream); String log = String.format("[%s:%d] 客户端上线",socket.getInetAddress(),socket.getPort()); System.out.println(log); while (true) { // 1.读取请求并解析 if (!sc.hasNext()) { log = String.format("[%s:%d] 客户端下线",socket.getInetAddress(),socket.getPort()); System.out.println(log); break; } String request = sc.nextLine(); // 2.根据请求计算响应 String response = process(request); // 3.把响应发给客户端 printWriter.println(response); //加上flush刷新缓冲区 printWriter.flush(); // 4.打印日志 log = String.format("[%s:%d] request: %s response: %s",socket.getInetAddress(),socket.getPort(), request,response); System.out.println(log); } sc.close(); printWriter.close(); } catch (IOException e) { e.printStackTrace(); } finally { //使用后关闭,防止内存泄露 socket.close(); } } /** * 回显服务器直接返回请求 * @param request * @return */ private String process(String request) { return request; } public static void main(String[] args) throws IOException { TcpEchoServer server = new TcpEchoServer(9090); server.start(); } }
客户端代码
import java.io.*; import java.net.Socket; import java.util.Scanner; public class TcpEchoClient { private Socket clientSocket; private String serverIp; private int serverPort; public TcpEchoClient(int serverPort,String serverIp) throws IOException { this.clientSocket = new Socket(serverIp,serverPort); this.serverPort = serverPort; this.serverIp = serverIp; } public void start() { System.out.println("客户端启动成功"); Scanner sc = new Scanner(System.in); try (InputStream inputStream = clientSocket.getInputStream(); OutputStream outputStream = clientSocket.getOutputStream()) { Scanner responseSc = new Scanner(inputStream); PrintWriter printWriter = new PrintWriter(outputStream); while (true) { // 1.从键盘输入请求 System.out.print("-> "); String request = sc.nextLine(); if ("exit".equals(request)) { break; } // 2.发送请求给服务器 printWriter.println(request); //加上flush刷新缓冲区 printWriter.flush(); // 3.从服务器获取响应 String response = responseSc.nextLine(); // 4.打印日志 String log = String.format("[%s:%d] request: %s response: %s",this.serverIp,this.serverPort ,request,response); System.out.println(log); } responseSc.close(); printWriter.close(); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) throws IOException { TcpEchoClient client = new TcpEchoClient(9090,"127.0.0.1"); client.start(); } }
下一篇 ———— 《UDP首部格式》
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。