Socket
什么是TCP/IP
TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WANs)设计的。
TCP/IP协议族包括运输层、网络层、链路层
Socket是什么
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
计算机网络
架构模式:B/S Browser/Server 浏览器/服务器模式,节约开发成本,减少使用难度
C/S Client/Server 客户端/服务器模式,用户学习成本更高,但功能更强大,不受限于浏览器
OSI七层模型:应用层,表示层,会话层,传输层,网络层,数据链路层,物理层
网络编程三要素
协议
计算机网络通信必须遵守的规则
IP地址
俗称IP。IP地址用来给一个网络中的计算机设备做唯一的编号。假如我们把“个人电脑”比作“一台电话”的话,那么“IP地址”就相当于“电话号码”。
端口号
网络的通信,本质上是两个进程(应用程序)的通信。
如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的进程(应用程序)了。
- 端口号:用两个字节表示的整数,它的取值范围是0~65535。其中,0~1023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败。
利用协议
+IP地址
+端口号
三元组合,就可以标识网络中的进程了,那么进程间的通信就可以利用这个标识与其它进程进行交互。
五层模型:
- 应用层 SMTP HTTP HTTPS
- 传输层 TCP(可靠传输,三次握手,四次握手) UDP 端口号
- 网络层 IP协议 寻址
- 数据链路层
- 物理层
DNS域名解析
1.浏览器会先看自己有没有存域名对应的ip
2.如果找不到,去DNS服务器查询,DNS服务器会返回给浏览器后,浏览器会缓存对应IP地址
3.浏览器发送请求到对应IP的服务器
网址的组成
https://www.baidu.com/?tn=54093922_1_hao_pg
协议类型 域名 端口号(https默认是443 http默认是80/8080) 参数
TCP通信程序
TCP通信能实现两台计算机之间的数据交互,通信的两端,要严格区分为客户端(Client)与服务端(Server)。
两端通信时步骤:
- 服务端程序,需要事先启动,等待客户端的连接。
- 客户端主动连接服务器端,连接成功才能通信。服务端不可以主动连接客户端。
在Java中,提供了两个类用于实现TCP通信程序:
- 客户端:
java.net.Socket
类表示。创建Socket
对象,向服务端发出连接请求,服务端响应请求,两者建立连接开始通信。 - 服务端:
java.net.ServerSocket
类表示。创建ServerSocket
对象,相当于开启一个服务,并等待客户端的连接。
Socket类
实现客户端套接字,套接字指的是两台设备之间通讯的端点。
构造方法
-
public Socket(String host, int port)
:创建套接字对象并将其连接到指定主机上的指定端口号。如果指定的host是null ,则相当于指定地址为回送地址。
Socket client = new Socket("127.0.0.1", 6666);
String host:服务器主机的名称/服务器的IP地址
成员方法
-
public InputStream getInputStream()
: 返回此套接字的输入流。- 如果此Scoket具有相关联的通道,则生成的InputStream 的所有操作也关联该通道。
- 关闭生成的InputStream也将关闭相关的Socket。
-
public OutputStream getOutputStream()
: 返回此套接字的输出流。- 如果此Scoket具有相关联的通道,则生成的OutputStream 的所有操作也关联该通道。
- 关闭生成的OutputStream也将关闭相关的Socket。
-
public void close()
:关闭此套接字。- 一旦一个socket被关闭,它不可再使用。
- 关闭此socket也将关闭相关的InputStream和OutputStream 。
-
public void shutdownOutput()
: 禁用此套接字的输出流。- 任何先前写出的数据将被发送,随后终止输出流。
实现步骤
1.创建一个客户端对象Socket,构造方法绑定服务器的IP地址和端口号
2.使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象
3.使用网络字节输出流OutputStream对象中的方法write,给服务器发送数据
4.使用Socket对象中的方法getInputStream()获取网络字节输入流对象InputStream
5.使用网络字节输入流InputStream对象中的方法read,读取服务器回写的数据
6.释放资源(Socket)
注意:
1.客户端和服务器端进行交互必须使用Socket中提供的网络流.不能使用自己创建的流对象
2.当我们创建客户端对象Socket的时候,就会去请求服务器和服务器经过3次握手建立链接通路
这时如果服务器没有启动,就会抛出异常.
如果服务器已经启动,就可以进行交互了
ServerSocket类
实现了服务器套接字,该对象等待通过网络的请求。
构造方法
-
public ServerSocket(int port)
:使用该构造方法在创建ServerSocket对象时,就可以将其绑定到一个指定的端口号上,参数port就是端口号。
ServerSocket server = new ServerSocket(6666);
成员方法
-
public Socket accept()
:侦听并接受连接,返回一个新的Socket对象,用于和客户端实现通信。该方法会一直阻塞直到建立连接
TCP通信分析图解
- 【服务端】启动,创建ServerSocket对象,等待连接。
- 【客户端】启动,创建Socket对象,请求连接。
- 【服务端】接收连接,调用accept方法,并返回一个Socket对象。
- 【客户端】Socket对象,获取OutputStream,向服务端写出数据。
- 【服务端】Scoket对象,获取InputStream,读取客户端发送的数据。
服务端实现:
public class ServerTCP {
public static void main(String[] args) throws IOException {
System.out.println("服务端启动 , 等待连接 .... ");
// 1.创建 ServerSocket对象,绑定端口,开始等待连接
ServerSocket ss = new ServerSocket(6666);
// 2.接收连接 accept 方法, 返回 socket 对象.
Socket server = ss.accept();
// 3.通过socket 获取输入流
InputStream is = server.getInputStream();
// 4.一次性读取数据
// 4.1 创建字节数组
byte[] b = new byte[1024];
// 4.2 据读取到字节数组中.
int len = is.read(b);
// 4.3 解析数组,打印字符串信息
String msg = new String(b, 0, len);
System.out.println(msg);
//5.关闭资源.
is.close();
server.close();
}
}
客户端向服务器端发送数据:
服务端实现:
public class ServerTCP {
public static void main(String[] args) throws IOException {
System.out.println("服务端启动 , 等待连接 .... ");
// 1.创建 ServerSocket对象,绑定端口,开始等待连接
ServerSocket ss = new ServerSocket(6666);
// 2.接收连接 accept 方法, 返回 socket 对象.
Socket server = ss.accept();
// 3.通过socket 获取输入流
InputStream is = server.getInputStream();
// 4.一次性读取数据
// 4.1 创建字节数组
byte[] b = new byte[1024];
// 4.2 据读取到字节数组中.
int len = is.read(b);
// 4.3 解析数组,打印字符串信息
String msg = new String(b, 0, len);
System.out.println(msg);
//5.关闭资源.
is.close();
server.close();
}
}
客户端实现:
public class ClientTCP {
public static void main(String[] args) throws Exception {
System.out.println("客户端 发送数据");
// 1.创建 Socket ( ip , port ) , 确定连接到哪里.
Socket client = new Socket("localhost", 6666);
// 2.获取流对象 . 输出流
OutputStream os = client.getOutputStream();
// 3.写出数据.
os.write("你好么? tcp ,我来了".getBytes());
// 4. 关闭资源 .
os.close();
client.close();
}
}
服务器向客户端回写数据
服务端实现:
public class ServerTCP {
public static void main(String[] args) throws IOException {
System.out.println("服务端启动 , 等待连接 .... ");
// 1.创建 ServerSocket对象,绑定端口,开始等待连接
ServerSocket ss = new ServerSocket(6666);
// 2.接收连接 accept 方法, 返回 socket 对象.
Socket server = ss.accept();
// 3.通过socket 获取输入流
InputStream is = server.getInputStream();
// 4.一次性读取数据
// 4.1 创建字节数组
byte[] b = new byte[1024];
// 4.2 据读取到字节数组中.
int len = is.read(b);
// 4.3 解析数组,打印字符串信息
String msg = new String(b, 0, len);
System.out.println(msg);
// =================回写数据=======================
// 5. 通过 socket 获取输出流
OutputStream out = server.getOutputStream();
// 6. 回写数据
out.write("我很好,谢谢你".getBytes());
// 7.关闭资源.
out.close();
is.close();
server.close();
}
}
客户端实现:
public class ClientTCP {
public static void main(String[] args) throws Exception {
System.out.println("客户端 发送数据");
// 1.创建 Socket ( ip , port ) , 确定连接到哪里.
Socket client = new Socket("localhost", 6666);
// 2.通过Scoket,获取输出流对象
OutputStream os = client.getOutputStream();
// 3.写出数据.
os.write("你好么? tcp ,我来了".getBytes());
// ==============解析回写=========================
// 4. 通过Scoket,获取 输入流对象
InputStream in = client.getInputStream();
// 5. 读取数据数据
byte[] b = new byte[100];
int len = in.read(b);
System.out.println(new String(b, 0, len));
// 6. 关闭资源 .
in.close();
os.close();
client.close();
}
}
使用telnet发送请求
win + R 输入 cmd 打开 dos 窗口
telnet time-A.timefreq.bldrdoc.gov 13
使用socket发送请求
public static void main(String[] args) {
//链接服务 加在try里省去关闭
try(Socket socket = new Socket("time-A.timefreq.bldrdoc.gov",13)){
//获取输入流
InputStream inputStream= socket.getInputStream();
//用Scanner包装
try (Scanner scanner = new Scanner(inputStream)){
//判断有没有下一行
while(scanner.hasNextLine()){
System.out.println(scanner.nextLine());
}
}
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
使用socket作为服务端接收请求
public static void main(String[] args) {
//创建服务,给指定一个端口号
try(ServerSocket serverSocket = new ServerSocket(8189)){
//接收请求 获取输入 获取输出
try (Socket socket = serverSocket.accept()){
InputStream inputStream =socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
PrintWriter printWriter = new PrintWriter(outputStream,true);
printWriter.println("Client say:");
try(Scanner scanner = new Scanner(inputStream)){
while(scanner.hasNextLine()){
//获取请求数据 输出
String inputStr = scanner.nextLine();
System.out.println("Client say:"+inputStr);
//给一个响应
//服务端从控制台输入信息
System.out.print("Server say:");
try(Scanner serverScanner = new Scanner(System.in)){
if(serverScanner.hasNextLine()){
String serverSayStr =serverScanner.nextLine();
//将服务器输入的信息打印给客户端
printWriter.println("Server say:");
printWriter.println(serverSayStr);
}
}
printWriter.println("Client say:");
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
URL
统一资源定位符:定位互联网上的某个资源
协议://ip地址:端口/项目名/资源
public static void main(String[] args) throws MalformedURLException {
URL url =new URL("http://localhost:8080/helloworld/index.jsp?username=tsy&password=123");
url.getProtocol();//协议
url.getHost(); //主机ip
url.getPort(); //端口
url.getPath(); //文件
url.getFile(); //全路径
url.getQuery(); //参数
}
用URL类下载图片
public static void main(String[] args) throws IOException {
//下载地址
URL url = new URL("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fitem%2F201908%2F28%2F20190828104530_msrvw.thumb.400_0.jpg&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1630136449&t=11d18f4911382cc71de58ec0ec9c8123");
//链接到这个资源 http
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
InputStream inputStream = urlConnection.getInputStream();
FileOutputStream fos = new FileOutputStream("Picture.jpg");
byte[] buffer = new byte[1024];
int len;
while ((len=inputStream.read(buffer))!=-1){
fos.write(buffer,0,len);//写出数据
}
fos.close();
inputStream.close();
urlConnection.disconnect();//断开链接
}
}
注解
注解就是放在java源码的类,方法,字段,参数前的一种特殊的“注释”
分类
-
第一类编译器使用,不会进入.class文件,编译后被编译器扔掉
@Override 让编译器检查该方法是否正确的实现覆盖 @SuppressWarnings告诉编译器忽略此处代码产生的警告
-
第二类由工具处理.class 文件使用的注解,一般被底层使用,不必我们处理
比如有些工具在加载classs时,对class做动态修改
-
第三类程序运行期能够读取的注解,加载后一直在JVM中,也是最常用的注解
例如@PostConstruct在调用构造方法后被自动调用,这是java代码读取该注解实现的哦功能,JVM并不会识别该注解
参数
注解可以由参数,最常用的是value,参数可以有默认值;如果只有一个参数value可以省略名称
@Check(99) -->等同于 @Check(value=99)
定义注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
元注解:修饰注解的注解,常用的有@Target @Retention
@Target 表示该注解可以放在什么位置
类或接口:
ElementType.TYPE
;字段:
ElementType.FIELD
;方法:
ElementType.METHOD
;构造方法:
ElementType.CONSTRUCTOR
;-
方法参数:
ElementType.PARAMETER
。@Target({ ElementType.METHOD, ElementType.FIELD }) //可以加多个 public @interface Report { ... }
@Retention 表示该注解的声明周期
仅编译期:
RetentionPolicy.SOURCE
;仅class文件:
RetentionPolicy.CLASS
;-
运行期:
RetentionPolicy.RUNTIME
。通常我们自定义的都是 RUNTIME ,所以要加上@Retention(RetentionPolicy.RUNTIME)
@Retention(RetentionPolicy.RUNTIME) public @interface Report { int type() default 0; String level() default "info"; String value() default ""; }
@Repeatable 表示注解是否可以重复
@Repeatable(Reports.class)
@Target(ElementType.TYPE)
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
@Report(type=1, level="debug")
@Report(type=2, level="warning")
public class Hello {
}
@Inherited 表示注解是否可以集成父类定义的 注解
处理注解
判断注解是否存
Class.isAnnotationPresent(Class)
Field.isAnnotationPresent(Class)
Method.isAnnotationPresent(Class)
Constructor.isAnnotationPresent(Class)
读取注解
Class.getAnnotation(Class)
Field.getAnnotation(Class)
Method.getAnnotation(Class)
Constructor.getAnnotation(Class)
使用
// 获取Person定义的@Report注解:
Report report = Person.class.getAnnotation(Report.class);
int type = report.type();
String level = report.level();
读取方法参数的注解稍显复杂,因为参数是要给数组,每个参数又可以修饰多个注解,所以最终是一个二维数组
public void hello(@NotNull @Range(max=5) String name, @NotNull String prefix) {
}
// 获取Method实例:
Method m = ...
// 获取所有参数的Annotation:
Annotation[][] annos = m.getParameterAnnotations();
// 第一个参数(索引为0)的所有Annotation:
Annotation[] annosOfName = annos[0];
for (Annotation anno : annosOfName) {
if (anno instanceof Range) { // @Range注解
Range r = (Range) anno;
}
if (anno instanceof NotNull) { // @NotNull注解
NotNull n = (NotNull) anno;
}
}