目录
Socket套接字定义:
是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。
基于Socket套接字的网络程序开发就是网络编程
程序员编写代码,主要编写的是应用层的代码,真正要发数据,需要上层协议调用下层协议,即应用层要调用传输层
socket api:传输层给应用层提供的一组api
分类:
基于UDP的api:
- 无连接:使用UDP通信的双方,不需要刻意保存对端的相关信息 (eg:发短信)
- 不可靠传输:信息发了,不关注结果 (不需要接受连接就能通信)
- 面向数据报:以一个UDP数据报为基本单位
- 全双工:一条路径,双向通信
基于TCP的api
- 有连接:使用TCP通信的双方,需要刻意保存对端的相关信息 (eg:打电话)
- 可靠传输:尽可能传输过去,并要关注是否发送成功(需要接受连接才能通信)
- 面向字节流:以字节为基本单位,读写方式非常灵活
- 全双工:一条路径,双向通信
UDP数据报套接字编程:
要想进行网络通信,需要socket这样的对象(相当于遥控器),借助这个对象才能间接操作网卡
往这个socket对象中写数据,相当于通过网卡发送信息
往这个socket对象中读数据,相当于通过网卡接受信息
DatagramSocket API:
DatagramSocket 是UDP Socket,用于发送和接收UDP数据报。
DatagramSocket 构造方法:
DatagramSocket() :
创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口 (一般用于客户端)
DatagramSocket(int port) :
创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用于服务端)
这里的socket对象可能被客户端或者服务器使用,服务器往往要关联一个具体的端口号(必须不变),客户端则不需要手动指定,系统会自动分配
DatagramPacket API:
DatagramPacket是UDP Socket发送和接收的数据报。
DatagramPacket 构造方法:
DatagramPacket(byte[] buf, int length) :
构造一个DatagramPacket以用来接收数据报,接收的数据保存在 字节数组(第一个参数buf)中,接收指定长度(第二个参数 length)
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address) :
构造一个DatagramPacket以用来发送数据报,发送的数据为字节 数组(第一个参数buf)中,从0到指定长度(第二个参数 length)。address指定目的主机的IP和端口号
基于UDP socket最简单的客户端服务器程序:
回显服务器:客户端发送一个请求,服务器返回一个一模一样的响应
服务器的核心工作:
- 读取请求并解析
- 根据请求返回响应(省略)
- 把响应返回客户端
客户端的核心工作:
- 读取用户输入
- 构造请求,并发送
- 读取服务器发来的响应
- 把响应转换成字符串,并显示出来
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class updEcoServer {
private DatagramSocket socket=null;//先定义一个socket对象
public updEcoServer(int port) throws SocketException {
//抛异常的原因:同一个主机,一个端口,同一时刻,只能被一个一个进程绑定
//如果某个端口被其他端口占用了,此时的绑定会出错
socket=new DatagramSocket(port);//指定要关联的端口
}
public void start() throws IOException {
System.out.println("服务器启动!");
while(true){
//1.读取请求并解析
//构造空饭盒
DatagramPacket requestPacket=new DatagramPacket(new byte[4096],4096);
//装满饭(饭从网卡来)
socket.receive(requestPacket);
String request=new String(requestPacket.getData(),0,requestPacket.getLength());
//为了方便处理,把数据报转为string
String response=process(request);//2.根据请求计算响应
//3.把响应写回客户端
//和上面的datagrampacket不同,这里要指定这个报发给谁
DatagramPacket responsePacket= new DatagramPacket(response.getBytes(),response.getBytes().length,
requestPacket.getSocketAddress());//requestPacket是从客户端收来的,
// getSocketAddress()会得到客户端的IP和端口号
socket.send(responsePacket);//发送
System.out.printf("[%s:%d] req: %s, resp: %s\n", requestPacket.getAddress().toString(),
requestPacket.getPort(), request, response);
}
}
public static String process(String request){
return request;
}
public static void main(String[] args) throws IOException {
updEcoServer updEcoServer=new updEcoServer(9090);
updEcoServer.start();
}
}
运行结果:
61656是系统给客户端分配的空闲端口
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class UdpEchoClient {
private DatagramSocket socket=null;
private String serverIP;
private int serverPort;
public UdpEchoClient(String serverIP,int serverPort) throws SocketException {
socket=new DatagramSocket();//不用指定端口,因为系统会自动分配
this.serverIP=serverIP;
this.serverPort=serverPort;
}
public void start() throws IOException {
Scanner scanner=new Scanner(System.in);
while (true){
System.out.println("请输入内容");
//1.控制台读取一个字符串
String request=scanner.next();
//2.把字符串构造成UDP packet
DatagramPacket requestPacket=new DatagramPacket(request.getBytes(),request.getBytes().length,
InetAddress.getByName(serverIP),serverPort);
socket.send(requestPacket);//发送
//3.客户端尝试读取服务器返回的响应
DatagramPacket responsePacket=new DatagramPacket(new byte[4096],4096);
socket.receive(requestPacket);//接收
//把响应数据转换成String
String response=new String(responsePacket.getData(),0,responsePacket.getLength());
System.out.printf("req: %s, resp: %s\n", request, response);
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient udpEchoClient=new UdpEchoClient("127.0.0.1",9090);
udpEchoClient.start();
}
}
运行结果:
执行步骤:
改进:请求是一个英文字母,返回的响应是翻译的中文
import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;
// 使用继承, 是为了复用之前的代码.
public class UdpDictServer extends updEcoServer{
private Map<String, String> dict = new HashMap<>();
public UdpDictServer(int port) throws SocketException {
super(port);
dict.put("dog", "小狗");
dict.put("cat", "小猫");
dict.put("fuck", "卧槽");
}
@Override
public String process(String request) {
return dict.getOrDefault(request, "该单词没有查到!");
}
public static void main(String[] args) throws IOException {
UdpDictServer udpDictServer = new UdpDictServer(9090);
udpDictServer.start();
}
}
执行结果:
TCP流套接字编程:
ServerSocket API:
ServerSocket是创建TCP服务端socket的API
ServerSocket 构造方法:
ServerSocket(int port) :创建一个服务端流套接字Socket,并绑定到指定端口(必须指定)
ServerSocket 方法:
Socket accept() :开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket 对象,并基于该Socket建立与客户端的连接,否则阻塞等待 (可以先简单理解为接受连接)
Socket API:
Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端 Socket。
Socket 构造方法:
Socket(String host, int port) :创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的 进程建立连接(表示服务器的IP和端口)
Socket 方法:
InetAddress getInetAddress() :返回套接字所连接的地址
InputStream getInputStream() :返回此套接字的输入流
OutputStream getOutputStream(): 返回此套接字的输出流
短连接:只能收发一次数据
长连接:可以多次收发数据
基于TCP socket最简单的客户端服务器程序
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TcpEchoServer {
private ServerSocket serverSocket=null;
//serverSocket只有一个
public TcpEchoServer(int port) throws IOException {
serverSocket=new ServerSocket(port);
}
public void start() throws IOException {
ExecutorService executorService= Executors.newCachedThreadPool();
System.out.println("服务器启动");
while (true){
Socket clientSocket=serverSocket.accept();//clientSocket可以有很多个
executorService.submit(new Runnable() {
@Override
public void run() {
try {
processConnection(clientSocket);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
}
}
private void processConnection(Socket clientSocket) throws IOException {
//1.读取请求
System.out.printf("[%s:%d] 客户端上线!\n",clientSocket.getInetAddress().toString(),
clientSocket.getPort());//请求与请求之间用换行分离
try (
InputStream inputStream=clientSocket.getInputStream();
OutputStream outputStream=clientSocket.getOutputStream()){
//使用scanner和printWriter即给in和out套了个壳使用字符流代替字节流,不用一个一个字节的扣,更方便
Scanner scanner=new Scanner(inputStream);
PrintWriter printWriter=new PrintWriter(outputStream);
while (true){
if(!scanner.hasNext()){//即读到了末尾
System.out.printf("[%s:%d] 客户端下线!\n",clientSocket.getInetAddress().toString(),
clientSocket.getPort());
break;
}
String request=scanner.next();//读取一段字符串
//2.根据请求计算响应
String response=process(request);
//3.把响应写回客户端
printWriter.println(response);
printWriter.flush();
System.out.printf("[%s:%d] req:%s;resp:%s\n",clientSocket.getInetAddress().toString(),
clientSocket.getPort(),request,response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
clientSocket.close();
}
}
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.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class TcpEchoClient {
private Socket socket=null;
private TcpEchoClient(String serverIP,int port) throws IOException {
socket=new Socket(serverIP,port);
}
public void start(){
Scanner scanner=new Scanner(System.in);
try {
InputStream inputStream=socket.getInputStream();
OutputStream outputStream=socket.getOutputStream();
Scanner scannerF=new Scanner(inputStream);
PrintWriter printWriter=new PrintWriter(outputStream);
while (true){
//键盘录入用户输入的内容
System.out.println("- >");
String request=scanner.next();
//2.构成请求,发给服务器
//请求以换行分隔
printWriter.println(request);
printWriter.flush();
//3.读取响应内容
String response =scannerF.next();
//4.显示到控制台
System.out.printf("res:%s;resp:%s\n",request,response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient tcp=new TcpEchoClient("127.0.0.1",9090);
tcp.start();
}
}
运行结果:
执行步骤: