目录
一、Socket套接字
1,概念
Socket套接字,是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。基 于Socket套接字的网络程序开发就是网络编程。
2,分类
流套接字:使用传输层TCP协议
TCP,即Transmission Control Protocol(传输控制协议),传输层协议。
以下为TCP的特点
有连接
可靠传输
面向字节流
有接收缓冲区,也有发送缓冲区
大小不限
数据报套接字:使用传输层UDP协议
UDP,即User Datagram Protocol(用户数据报协议),传输层协议。
以下为UDP的特点
无连接
不可靠传输
面向数据报
有接收缓冲区,无发送缓冲区
大小受限:一次最多传输64k
二、Java数据报套接字通信模型
三、Java流套接字通信模型
四、UDP数据报套接字编程
1,DatagramSocket API
DatagramSocket是UDP Socket,用于发送和接收UDP数据报。
DatagramSocket方法
方法签名
方法说明
void receive(DatagramPacket p)
从此套接字接收数据报(如果没有接收到数据报,该方法会阻 塞等待)
void send(DatagramPacket p)
从此套接字发送数据报包(不会阻塞等待,直接发送)
void close()
关闭此数据报套接字
DatagramSocket构造方法
方法签名
方法说明
DatagramSocket()
创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口 (一般用于客户端)
DatagramSocket(int port)
创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用 于服务端)
2,DatagramPacket API
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和端口号
3,示例代码
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
", 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();
}
}
五、TCP流套接字编程
1,ServerSocket API
ServerSocket是创建TCP服务端Socket的API
ServerSocket构造方法
方法签名
方法说明
ServerSocket(int port)
创建一个服务端流套接字Socket,并绑定到指定端口
ServerSocket方法
方法签名
方法说明
Socket accept()
开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket 对象,并基于该Socket建立与客户端的连接,否则阻塞等待
void close()
关闭此套接字
2,Socket API
Socket是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端 Socket。不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。
Socket构造方法
方法签名
方法说明
Socket(String host, int port)
Socket(String host, int port)
Socket方法
方法签名
方法说明
InetAddress getInetAddress()
返回套接字所连接的地址
InputStream getInputStream()
返回此套接字的输入流
OutputStream getOutputStream()
返回此套接字的输出流
3,示例代码
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]客户端上线
",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 + "
");
bufferedWriter.flush();
System.out.printf("[%s:%d] req: %s; resp: %s
",clientSocket.getInetAddress().toString(),
clientSocket.getPort(),request,response);
}
} catch (IOException e) {
e.printStackTrace();
System.out.printf("[%s:%d] 客户端下线
",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 + "
");
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] 客户端上线!
", 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 + "
");
bufferedWriter.flush();
System.out.printf("[%s:%d] req: %s; resp: %s
", clientSocket.getInetAddress().toString(),
clientSocket.getPort(), request, response);
}
} catch (IOException e) {
// e.printStackTrace();
System.out.printf("[%s:%d] 客户端下线!
", 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,但还有很多问题,每次来一个客户端,都要分配一个线程对于一个服务器来说,随时可能会来大量的客户端,随时也会有大量的客户端断开连接,服务器需要频繁的创建和销毁线程,所以可以用线程池
总结
写到这里也结束了,在文章最后放上一个小小的福利,以下为小编自己在学习过程中整理出的一个关于 java开发 的学习思路及方向。从事互联网开发,最主要的是要学好技术,而学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯,更加需要准确的学习方向达到有效的学习效果。
由于内容较多就只放上一个大概的大纲,需要更及详细的学习思维导图的 点击我的Gitee获取。
还有 高级java全套视频教程 java进阶架构师 视频+资料+代码+面试题!
全方面的java进阶实践技术资料,并且还有技术大牛一起讨论交流解决问题。