目录
一、Socket套接字
1,概念
2,分类
二、Java数据报套接字通信模型
三、Java流套接字通信模型
四、UDP数据报套接字编程
1,DatagramSocket API
2,DatagramPacket API
3,示例代码
五、TCP流套接字编程
1,ServerSocket API
2,Socket API
3,示例代码
Socket套接字,是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。基 于Socket套接字的网络程序开发就是网络编程。
流套接字:使用传输层TCP协议
TCP,即Transmission Control Protocol(传输控制协议),传输层协议。
以下为TCP的特点
有连接
可靠传输
面向字节流
有接收缓冲区,也有发送缓冲区
大小不限
数据报套接字:使用传输层UDP协议
UDP,即User Datagram Protocol(用户数据报协议),传输层协议。
以下为UDP的特点
无连接
不可靠传输
面向数据报
有接收缓冲区,无发送缓冲区
大小受限:一次最多传输64k
DatagramSocket是UDP Socket,用于发送和接收UDP数据报。
DatagramSocket方法
方法签名 | 方法说明 |
void receive(DatagramPacket p) | 从此套接字接收数据报(如果没有接收到数据报,该方法会阻 塞等待) |
void send(DatagramPacket p) | 从此套接字发送数据报包(不会阻塞等待,直接发送) |
void close() | 关闭此数据报套接字 |
DatagramSocket构造方法
方法签名 | 方法说明 |
DatagramSocket() | 创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口 (一般用于客户端) |
DatagramSocket(int port) | 创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用 于服务端) |
DatagramPacket是UDP Socket发送和接收的数据
DatagramPacket方法
方法签名 | 方法说明 |
InetAddress getAddress() | 从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取 接收端主机IP地址 |
int getPort() | 从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获 取接收端主机端口号 |
byte[] getData() | 获取数据报中的数据 |
DatagramPacket构造方法
方法签名 | 方法说明 |
DatagramPacket(byte[] buf, int length) | 构造一个DatagramPacket以用来接收数据报,接收的数据保存在 字节数组(第一个参数buf)中,接收指定长度(第二个参数 length) |
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address) | 构造一个DatagramPacket以用来发送数据报,发送的数据为字节 数组(第一个参数buf)中,从0到指定长度(第二个参数 length)。address指定目的主机的IP和端口号 |
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UdpEchoServer {
//对于一个服务器来说,核心分成两部
//进入初始化操作(实例化Socket对象)
//进入主循环,接受请求
//读取响应
//根据请求计算响应
//把响应写回客户端
private DatagramSocket socket = null;
public UdpEchoServer(int port) throws SocketException {
socket = new DatagramSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动");
while (true){
//读取解析
DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
socket.receive(requestPacket);
String request = new String(requestPacket.getData(),0, requestPacket.getLength()).trim();
//根据请求计算响应
String response = process(request);
//把响应写回客户端,响应数据就是response,需要包装成一个对象
DatagramPacket datagramPacket = new DatagramPacket(response.getBytes(),response.getBytes().length
,requestPacket.getSocketAddress());
socket.send(requestPacket);
System.out.printf("[%s:%d] req: %s; resp: %s\n", requestPacket.getAddress().toString(),
requestPacket.getPort(), request, response);
}
}
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.*;
import java.util.Scanner;
public class UdpEchoClient {
//1,读客户端的数据
//2,构造请求发送给客户端
//3,从服务器读取响应
//4,把响应写回客户端
private DatagramSocket socket = null;
private String serverIp;
private int serverPort;
//需要在启动客户端的时候来指定那个服务器
public UdpEchoClient(String serverIp,int serverPort) throws SocketException {
this.serverIp = serverIp;
this.serverPort = serverPort;
socket = new DatagramSocket();
}
public void start() throws IOException {
Scanner scanner = new Scanner(System.in);
while (true){
//读取用户数据
System.out.println("->");
String request = scanner.nextLine();
if (request.equals("exit")){
break;
}
//构造请求发送给服务器
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),
request.getBytes().length, InetAddress.getByName(serverIp),serverPort);
socket.send(requestPacket);
//从服务器读取响应
DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
socket.receive(responsePacket);
String response = new String(responsePacket.getData(),0,responsePacket.getLength()).trim();
//显示数据
System.out.println(response);
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient udpEchoClient = new UdpEchoClient("127.0.0.1",9090);
udpEchoClient.start();
}
}
ServerSocket是创建TCP服务端Socket的API
ServerSocket构造方法
方法签名 | 方法说明 |
ServerSocket(int port) | 创建一个服务端流套接字Socket,并绑定到指定端口 |
ServerSocket方法
方法签名 | 方法说明 |
Socket accept() | 开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket 对象,并基于该Socket建立与客户端的连接,否则阻塞等待 |
void close() | 关闭此套接字 |
Socket是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端 Socket。不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。
Socket构造方法
方法签名 | 方法说明 |
Socket(String host, int port) | Socket(String host, int port) |
Socket方法
方法签名 | 方法说明 |
InetAddress getInetAddress() | 返回套接字所连接的地址 |
InputStream getInputStream() | 返回此套接字的输入流 |
OutputStream getOutputStream() | 返回此套接字的输出流 |
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class TcpEchoServer {
//1,初始化服务器
//2,进入主循环
//先从内核中获取到一个TCP连接
//处理这个TCP连接
//读取请求并解析
//根据请求计算响应
//把响应写回客户端
private ServerSocket serverSocket = null;
public TcpEchoServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动");
while (true){
//先从内核中获取一个TCP连接
Socket clientSocket = serverSocket.accept();
//处理这个连接
processConnection(clientSocket);
}
}
private void processConnection(Socket clientSocket) {
System.out.printf("[%s:%d]客户端上线\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
//通过clientSocket来与客户端交互
try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()))) {
//此处建立一个连接过程,处理多个请求和响应
while (true){
//读取请求响应
String request = bufferedReader.readLine();
//根据请求计算响应
String response = process(request);
//把响应写回客户端
bufferedWriter.write(response + "\n");
bufferedWriter.flush();
System.out.printf("[%s:%d] req: %s; resp: %s\n",clientSocket.getInetAddress().toString(),
clientSocket.getPort(),request,response);
}
} catch (IOException e) {
e.printStackTrace();
System.out.printf("[%s:%d] 客户端下线\n",clientSocket.getInetAddress().toString(),
clientSocket.getPort());
}
}
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);
tcpEchoServer.start();
}
}
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class TcpClientServer {
//启动客户端
//进入主循环
//读取用户内容
//构造一个请求发送给服务器
//读取服务器的响应的数据
//把响应写到界面
private Socket socket = null;
public TcpClientServer(String serverIp,int serverPort) throws IOException {
//实例化Socket,建立TCP连接
socket = new Socket(serverIp,serverPort);
}
public void start(){
System.out.println("客户端启动");
Scanner scanner = new Scanner(System.in);
try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))){
while (true){
//读取用户输入内容
System.out.println("->");
String request = scanner.nextLine();
if (request.equals("exit")){
break;
}
//构造请求并发送
bufferedWriter.write(request + "\n");
bufferedWriter.flush();
//读取响应数据
String response = bufferedReader.readLine();
//把响应写到界面
System.out.println(response);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
TcpClientServer tcpClientServer = new TcpClientServer("127.0.0.1",9090);
tcpClientServer.start();
}
}
getOutputStream得到一个流对象,进一步封装成一个BufferedWriter
代码调用BufferedWriter.write方法的时候,先把数据放在缓冲区,此时wrtite操作并没有往内核中写socket文件中的数据。
调用flush方法,把内存缓冲区中的内容写入Socket文件中
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class TcpThreadEchoServer {
private ServerSocket serverSocket = null;
public TcpThreadEchoServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动");
while (true){
Socket clientSocket = serverSocket.accept();
Thread t = new Thread(){
@Override
public void run() {
processConnection(clientSocket);
}
};
t.start();
}
}
public void processConnection(Socket clientSocket) {
System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress().toString(),
clientSocket.getPort());
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()))) {
while (true) {
// 1. 读取请求并解析
String request = bufferedReader.readLine();
// 2. 根据请求计算响应
String response = process(request);
// 3. 把响应写回到客户端
bufferedWriter.write(response + "\n");
bufferedWriter.flush();
System.out.printf("[%s:%d] req: %s; resp: %s\n", clientSocket.getInetAddress().toString(),
clientSocket.getPort(), request, response);
}
} catch (IOException e) {
// e.printStackTrace();
System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress().toString(),
clientSocket.getPort());
}
}
public String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpThreadEchoServer server = new TcpThreadEchoServer(9090);
server.start();
}
}
主线程专门负责accept,其他线程负责和客户端沟通
public void start() throws IOException {
System.out.println("服务器启动");
//先创建一个线程池实例
ExecutorService executorService = Executors.newCachedThreadPool();
while (true){
//针对这个连接,单独创建一个线程池来负责
Socket clientSocket = serverSocket.accept();
executorService.execute(new Runnable() {
@Override
public void run() {
processConnection(clientSocket);
}
});
}
}
虽然使用多线程解决了BUG,但还有很多问题,每次来一个客户端,都要分配一个线程对于一个服务器来说,随时可能会来大量的客户端,随时也会有大量的客户端断开连接,服务器需要频繁的创建和销毁线程,所以可以用线程池