网络编程系统化学习(1.1.)--socket基础

时间:2023-03-09 18:45:32
网络编程系统化学习(1.1.)--socket基础

大纲

  网络编程系统化学习(1.1.)--socket基础

  学完该阶段内容,你将会很好的完成如下的面试题

  socket面试试题相关:

  1.写一个简单的socket程序,实现客户端发送数据,服务端收到数据后,将该内容转发给客户端

  2.简要概述一下socket的请求流程

  3.简要概述一下什么是3次握手,为什么需要3次握手

  4.简要概述一下什么是4次挥手,为什么需要4次挥手

  5.简单说一下你对io流的认识

  6.简要说一下TCP/IP的通信模型

  7.简要说一下socket的通信模型

  8.简要说一下OSI通信模型有那7层,tcp协议在那一层?路由选择那一层

  9.简要说一下socket能否传递java对象,怎么传

  10.简要说一下如何实现多个客户端同时连接同一个服务端

  11.socket如何实现多线程传送文件

  12.简要说一下tcp与udp的区别

  13.简要说一下你对io \ bio \ nio \ aio 的认识

1.socket简介

  网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。建立网络通信连接至少要一对端口号(socket)。

  socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口;

  HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。

  对于java程序来说,它的实现方式是通过调用网络库(java.net库)的一些API函数来实现分布在不同主机的相关进程之间的数据交换

2.socket hello world

  需求:写一个简单的服务器程序,客户端向服务端发送数据,服务端收到数据后,返回一句:"你好,我已经收到数据".

2.1.两个重要的API介绍

  ServerSocket \ Socket

  API地址:http://blog.fondme.cn:8000/apidoc/jdk-1.8-google/

2.2.服务端设计  

  服务端设计步骤:
  1.创建socket服务端
  serverSocket = new ServerSocket(1001);
  2.监听客户端(是一个阻塞方法)
  server = serverSocket.accept();
  3.获取输入流
  server.getInputStream()
  4.获取输出流
  server.getOutputStream()
  5.关闭资源
  serverSocket.close();

  案例代码:

package com.wfd360.com.socket;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket; /**
* @Copyright (C)公众号:java实战分享
* @Author: 姿势帝-博客园
* @Date: 2019-09-01 15:14
* @Description:
*/
public class ServerSocketDemo {
/**
* ServerSocket 的构造方法如下所示。
* ServerSocket():无参构造方法。
* ServerSocket(int port):创建绑定到特定端口的服务器套接字。
* ServerSocket(int port,int backlog):使用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口。
* ServerSocket(int port,int backlog,InetAddress bindAddr):使用指定的端口、监听 backlog 和要绑定到本地的 IP 地址创建服务器。
* <p>
* ===================================================================================================================
* ServerSocket 的常用方法如下所示。
* Server accept():监听并接收到此套接字的连接。
* void bind(SocketAddress endpoint):将 ServerSocket 绑定到指定地址(IP 地址和端口号)。
* void close():关闭此套接字。
* InetAddress getInetAddress():返回此服务器套接字的本地地址。
* int getLocalPort():返回此套接字监听的端口。
* SocketAddress getLocalSoclcetAddress():返回此套接字绑定的端口的地址,如果尚未绑定则返回 null。
* int getReceiveBufferSize():获取此 ServerSocket 的 SO_RCVBUF 选项的值,该值是从 ServerSocket 接收的套接字的建议缓冲区大小。
*
* @param args 服务端设计步骤:
* 1.创建socket服务端
* serverSocket = new ServerSocket(1001);
* 2.监听客户端
* server = serverSocket.accept();
* 3.获取输入流
* server.getInputStream()
* 4.获取输出流
* server.getOutputStream()
* 5.关闭资源
* serverSocket.close();
*/
public static void main(String[] args) {
ServerSocket serverSocket = null;
Socket server = null;
BufferedReader reader = null;
BufferedWriter writer = null;
try {
//1.建立socket服务器
serverSocket = new ServerSocket(1001);
System.out.println("服务端创建成功----");
while (true) {
//2.监听客户端连接
server = serverSocket.accept();
//3.读取客户端数据
reader = new BufferedReader(new InputStreamReader(server.getInputStream()));
String s = reader.readLine();
System.out.println("服务端收到数据:" + s);
//4.响应客户端
writer = new BufferedWriter(new OutputStreamWriter(server.getOutputStream()));
writer.write("已经收到数据[" + s + "]\n");
//清除
writer.flush();
} } catch (IOException e) {
e.printStackTrace();
} finally {
try {
//关闭资源
if (writer != null) {
writer.close();
}
if (reader != null) {
reader.close();
}
if (server != null) {
server.close();
}
if (serverSocket != null) {
serverSocket.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

2.3.客户端设计

  客户端设计逻辑:
  1.创建socket通信,设置连接IP和端口
  socket = new Socket("localhost", 1001);
  2.建立IO输出流,向服务端发送数据
  socket.getOutputStream()
  3.建立IO输入流,读取服务端应答的数据
  socket.getInputStream()
  4.关闭资源
  socket.close();

  案例代码:

package com.wfd360.com.socket;

import java.io.*;
import java.net.Socket; /**
* @公众号: java实战分享
* @Author: 姿势帝-博客园
* @Date: 2019-09-01 15:50
* @Description:
*/
public class ClientSocketDemo {
/**
* 客户端设计逻辑:
* 1.创建socket通信,设置连接IP和端口
* socket = new Socket("localhost", 1001);
* 2.建立IO输出流,向服务端发送数据
* socket.getOutputStream()
* 3.建立IO输入流,读取服务端应答的数据
* socket.getInputStream()
* 4.关闭资源
* socket.close();
*
* @param args
*/
public static void main(String[] args) {
Socket socket = null;
BufferedWriter writer = null;
BufferedReader reader = null;
try {
//1.创建socket通信
socket = new Socket("localhost", 1001);
//2.建立IO输出流,向服务端发送数据
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
//注意这里必须加\n,因为是按照行读取
writer.write("hello world \n");
writer.flush();
//3.建立IO输入流,读取服务端应答的数据
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String s = reader.readLine();
System.out.println("客户端收到数据:" + s);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (reader != null) {
reader.close();
}
if (writer != null) {
writer.close();
}
if (socket != null) {
socket.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

 

2.4.socket生命周期与请求流程

  网络编程系统化学习(1.1.)--socket基础

  上图详细的介绍了socket的通信流程:

  1.建立服务端并绑定端口

  2.客户端通过ip和端口连接服务端,重要面试(3次握手)

  3.服务端监听客户端的链接,注意这里是一个阻塞线程,效率很低,后面我们讲NIO将会解决这个

  4.客户端建立IO输出流,向服务端发送数据

  5.服务端建立IO输入流,获取客户端发来的数据

  6.服务端建立IO输出流,向客户端发送数据,注意如果要持续不断地发送与接收数据,将重复4\5\6三个步骤

  7.客户端当数据发送与接收完毕后断开连接,重要面试点(4次挥手)

  8.配合客户端断开连接

3.网络编程中的重要IO操作

  网络编程系统化学习(1.1.)--socket基础

3.1.IO流的分类

  1.根据处理数据类型不同分为:字符流和字节流
  2.根据数据流向不同分为:输入流和输出流
  3.根据功能不同分为:节点流和处理流
  字节流和字符流的区别:
  1.字符流使用了缓冲区,而字节流没有使用缓冲区。
  2.读取单位不同:
  字节流以字节(8bit)为单位,字符流以字符为单位,
  根据码表映射字符,一次可能读多个字节.

  3.处理对象不同:字节流能处理所有类型的数据(如图片\视频等)
  而字符流只能处理字符类型的数据.
  字节流:一次读入或读出8位二进制.
  字符流:一次读入或读出16位二进制.

3.2.扩展分析

  对字符流而言,在关闭字符流时会强制性地将缓冲区中的内容进行输出,但是如果程序没有关闭,则缓冲区中的内容是无法输出的.(请自己写代码验证)

  提问:什么叫缓冲区?在很多地方都碰到缓冲区这个名词,那么到底什么是缓冲区?又有什么作用呢?

  回答:缓冲区可以简单地理解为一段内存区域。可以简单地把缓冲区理解为一段特殊的内存。

  某些情况下,如果一个程序频繁地操作一个资源(如文件或数据库),则性能会很低,此时为了提升性能,

  就可以将一部分数据暂时读入到内存的一块区域之中,以后直接从此区域中读取数据即可,因为读取内存速度会比较快,这样可以提升程序的性能。

  在字符流的操作中,所有的字符都是在内存中形成的,在输出前会将所有的内容暂时保存在内存之中,所以使用了缓冲区暂存数据。

  如果想在不关闭时也可以将字符流的内容全部输出,则可以使用Writer类中的flush()方法完成。

  提问:使用字节流好还是字符流好?

  学习完字节流和字符流的基本操作后,已经大概地明白了操作流程的各个区别,那么在开发中是使用字节流好还是字符流好呢?
  回答:使用字节流更好。

  在回答之前,先为读者讲解这样的一个概念,所有的文件在硬盘或在传输时都是以字节的方式进行的,包括图片等都是按字节的方式存储的,而字符是只有在内存中才会形成,

  所以在开发中,字节流使用较为广泛。

  提问:字节流与字符流主要的区别是他们的的处理方式

  分析:

  流分类:
  1.Java的字节流
  InputStream是所有字节输入流的祖先,而OutputStream是所有字节输出流的祖先。
  2.Java的字符流
  Reader是所有读取字符串输入流的祖先,而writer是所有输出字符串的祖先。

  InputStream,OutputStream,Reader,writer都是抽象类。所以不能直接new

  字节流是最基本的,所有的InputStream和OutputStream的子类都是,主要用在处理二进制数据,它是按字节来处理的,

  但实际中很多的数据是文本,又提出了字符流的概念,它是按虚拟机的Encode来处理,也就是要进行字符集的转化。

  这两个之间通过 InputStreamReader,OutputStreamWriter来关联,实际上是通过byte[]和String来关联。

  在实际开发中出现的汉字问题实际上都是在字符流和字节流之间转化不统一而造成的。

  在从字节流转化为字符流时,实际上就是byte[]转化为String时

  public String(byte bytes[], String charsetName)

  有一个关键的参数字符集编码,通常我们都省略了,那系统就用操作系统的lang

  而在字符流转化为字节流时,实际上是String转化为byte[]时,

  byte[] String.getBytes(String charsetName)

  也是一样的道理

  至于java.io中还出现了许多其他的流,按主要是为了提高性能和使用方便,

  如BufferedInputStream,PipedInputStream等

3.3.代码实现将文件a.txt复制到b.txt

package com.wfd360.com.io;

import org.junit.Test;

import java.io.*;
import java.util.ArrayList;
import java.util.List; /**
* @公众号: java实战分享
* @Author: 姿势帝-博客园
* @Date: 2019-09-12 11:15
* @Description:
*/
public class FileDemo { /**
* 字节操作
* 需求:
* 将a.txt 复制到 b.txt
*
* @throws Exception
*/
@Test
public void test2() throws Exception {
String filePath = "E:\\test\\a.txt";
String filePathNew = "E:\\test\\b.txt";
//1.读取本地文件a
File file = new File(filePath);
DataInputStream dataInputStream = new DataInputStream(new FileInputStream(file));
//读取文件名
// String fileName = dataInputStream.readUTF();
//读取文件大小
// long fileSize = dataInputStream.readLong();
// System.out.println("fileName="+fileName+",fileSize="+fileSize);
//准备写入文件对象
File fileNew = new File(filePathNew);
// fileNew.createNewFile();
DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream(fileNew)); //按照字节读取文件
byte[] aByte = new byte[128];
System.out.println("length=" + aByte.length);
int read = dataInputStream.read(aByte, 0, aByte.length);
while (read != -1) {
//当前文件内容
String s = new String(aByte, "gbk");
System.out.println("read=" + read + "s=" + s);
//写入文件
dataOutputStream.write(aByte, 0, read);
dataOutputStream.flush();
read = dataInputStream.read(aByte, 0, aByte.length);
}
} /**
* 字符操作
* 需求:
* 将a.txt 复制到 b.txt
* 使用:FileInputStream,FileOutputStream
*
* @throws Exception
*/
@Test
public void test1() throws Exception {
//1:读取a.txt中的内容
//1.1.建立文件
File file = new File("D:\\test\\a.txt");
//1.2.读取文件内容 字节流
FileInputStream fileInputStream = new FileInputStream(file);
//1.3.转变为处理流 gb2312
BufferedReader reader = new BufferedReader(new InputStreamReader(fileInputStream, "gbk"));
//1.4.按照行读取数据
List<String> content = new ArrayList<String>();
String line = reader.readLine();
while (line != null) {
System.out.println("line=" + line);
content.add(line);
//读取下一行
line = reader.readLine();
} //2:写入到b.txt中
//2.1.创建文件
File fileB = new File("D:\\test\\b.txt");
boolean newFile = fileB.createNewFile();
System.out.println("newFile=" + newFile);
//2.2.创建写入文件字节流
FileOutputStream fileOutputStream = new FileOutputStream(fileB);
//2.3.转变为处理流
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(fileOutputStream, "gbk"));
for (String s : content) {
writer.write(s);
}
writer.flush();
writer.close();
}
}

4.socket的通信模型

4.1.TCP/IP与OSI参考模型

  网络编程系统化学习(1.1.)--socket基础

  OSI的层次划分:OSI将计算机网络体系结构(architecture)划分为以下七层:

  1、物理层  Physical Layer (用什么介质传:光纤等问题,类比:纸)

  物理接口规范,传输比特流,网卡是工作在物理层的。

  分析:

  怎样充分利用资源,用什么物质传又省钱又好用?这就是物理层要解决的问题了,这里展开来就是无线铜线光纤损耗等等的问题。

  2、数据链路层  Data Link Layer (怎么传送,类比:文字一行一行的写,信封)

  成帧,保证帧的无误传输,MAC地址,形成EHTHERNET帧

  分析:

  然后知道介质了,怎样把信息传给设备?这就是数据链路层要解决的问题了。

  这里需要解决信息的封装问题,信息传送机制问题。

  所以这一层的所有协议设计都是围绕着怎么传信息展开。

  3、网络层  Network Layer (找到指定ip,类比:精确找到信交给谁)

  路由选择,流量控制,IP地址,形成IP包

  分析:

  理论上到这里就结束了,但随着入网设备越来越多,就需要被管理了,所以网络层就来解决网络管理的问题。

  IP层是整个网络分层里承上启下的核心,他主要解决两个问题,一个是编号,一个是找人。网络太大,人太多,认识不过来,所以要用ip地址给人编号。

  编完号找人,几十亿人一个个问也不可行,也是要讲方法的,所以这里就有了路由技术。

  网络层的关键是路由技术,路由技术解决怎样快速准确找到对端的问题。

  4、传输层  Transport Layer (传输速度和信息的完整性,类比:开车送信,还是马车送信)

  端口地址,如HTTP对应80端口。TCP和UDP工作于该层,还有就是差错校验和流量控制。 

  分析:

  找到对端后,怎样保证对端完整收到信息?这就是传输层要解决的问题了,这里就有传送速度的调节,传送信息的验证,传完信息的确认等等问题,对应的TCP和UDP也是两种不同的实现思路。

  5、会话层  Session Layer

  组织两个会话进程之间的通信,并管理数据的交换使用NETBIOS和WINSOCK协议。QQ等软件进行        通讯因该是工作在会话层的。

  6、表示层  Presentation Layer

  使得不同操作系统之间通信成为可能。

  7、应用层 Application Layer

  对应各应用软件 

  分析(5,6,7):

  来到这里,关于网络的问题基本上就结束了,因为终端已经拿到了别人发过来的信息了,至于怎么处理信息,就是会话层,表示层,应用层的问题了。

  不同的应用程序有着不同的通信协议(Email——SMTP,Web——Http,文件传输——Ftp等),这些通信协议都工作在应用层。

4.2.socket通信模型

  socket是基于TCP\UDP协议的应用层通信.

  理解几个概念:

  SYN:同步序列编号(Synchronize Sequence Numbers)。是TCP/IP建立连接时使用的握手信号。

  ACK:  确认字符(Acknowledge character),在数据通信中,接收站发给发送站的一种传输类控制字符。表示发来的数据已确认接收无误。

  FIN: 表示关闭连接

  PSH: 表示有 DATA数据传输

  RST: 表示连接重置

    问题1:什么是tcp长连接的三次握手

  网络编程系统化学习(1.1.)--socket基础

  第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;

  第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(seq=k),即SYN+ACK包,此时服务器进入SYN_RECV状态。

  第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手.

  完成三次握手,客户端与服务器开始传送数据。

  问题2:为什么需要三次握手?

  假设在信号不好的情况下,用张无忌给赵敏打电话作为例子,下面是连接对话

  张无忌: 亲爱的,你可以听得到我说话吗?(第一次握手)

  赵敏: 我可以听到你说话,你能听到我说话么?(第二次握手)

  张无忌: 可以的,我开始给你说重大事情了.(第三次握手)

  三次握手完成后,可以确定这样一件事情,张无忌能听到赵敏说的话,赵敏也能听到张无忌说的话,这样就可以开始正常沟通了

  如果只有二次握手,赵敏不能确定,张无忌是否能听到自己说的话.

  如果4次握手,就会造成浪费

  问题3:什么是4次挥手

  网络编程系统化学习(1.1.)--socket基础

  连接终止协议(四次挥手)

  由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。

  收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。

  第一次挥手:  TCP客户端发送一个FIN,用来关闭客户到服务器的数据传送。(告诉服务端,客户端已停止向服务端传输数据,但还可以接收数据)

  第二次挥手:  服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。(服务端已收到关闭请求,但自己还没有准备好关闭)

  第三次挥手:  服务器关闭客户端的连接,发送一个FIN给客户端。

  第四次挥手:  客户端发回ACK报文确认,并将确认序号设置为收到序号加1。(告知服务端你也可以关闭连接)

  问题4: 为什么需要4次挥手

  确保数据能够完整传输

  分析:

  张无忌给赵敏打电话时,突然明教发生了重大事情,张无忌想挂电话了
  ......
  张无忌:亲爱的,我要说的都说完了,你还有什么要说的么?(第一次挥手)

  赵敏:嗯好,我把周芷若的丑事说完就不说了......(第二次挥手)

  赵敏:好,就这些,你一定要记住额,我也说完了(第三次挥手)

  张无忌:好的,记住了,挂了.(第四次握手)

5.socket传输java对象

  重点:

  1.被传输对象实现 Serializable 接口  

  2.使用ObjectInputStream   \    ObjectOutputStream 作为输入输出对象

  序列化对象

 package com.wfd360.com.socketJava.model;

 import java.io.Serializable;

 /**
* @公众号: java实战分享
* @Author: 姿势帝-博客园
* @Date: 2019-09-12 17:48
* @Description: <p>
* <p>
* 什么是对象序列化与对象反序列化
* 把java对象转变为字节序列的过程叫: 做对象序列化
* 把字节序列恢复为java对象称为 : 对象反序列化
* <p>
* 对象序列化的作用:
* 用于存储
* 用于传输
* <p>
* java对象如何实现序列化
* 实现Serializable接口
*/
public class User implements Serializable {
private String name;
private Integer age; public User() {
} public User(String name, Integer age) {
this.name = name;
this.age = age;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public Integer getAge() {
return age;
} public void setAge(Integer age) {
this.age = age;
} @Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

  服务端

 package com.wfd360.com.socketJava;

 import com.wfd360.com.socketJava.model.User;

 import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket; /**
* @公众号: java实战分享
* @Author: 姿势帝-博客园
* @Date: 2019-09-01 15:14
* @Description:
*/
public class ServerSocketDemo {
/**
* @param args 服务端设计步骤:
* 1.创建socket服务端
* 2.监听客户端
* 3.获取输入流
* 4.输出
*/
public static void main(String[] args) {
ServerSocket serverSocket = null;
Socket server = null;
ObjectInputStream objectInputStream = null;
ObjectOutputStream objectOutputStream = null;
try {
//1.建立socket服务器
serverSocket = new ServerSocket(1002);
System.out.println("服务端创建成功----");
while (true) {
//2.监听客户端连接
server = serverSocket.accept();
//3.读取客户端数据
objectInputStream = new ObjectInputStream(server.getInputStream());
User user = (User) objectInputStream.readObject();
System.out.println("服务端收到数据:" + user);
//4.响应客户端
objectOutputStream = new ObjectOutputStream(server.getOutputStream());
user.setName("我来做服务端");
objectOutputStream.writeObject(user);
//清除缓存
objectOutputStream.flush();
} } catch (Exception e) {
e.printStackTrace();
} finally {
try {
//关闭资源
if (objectOutputStream != null) {
objectOutputStream.close();
}
if (objectInputStream != null) {
objectInputStream.close();
}
if (server != null) {
server.close();
}
if (serverSocket != null) {
serverSocket.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
} }

  客户端 

 package com.wfd360.com.socketJava;

 import com.wfd360.com.socketJava.model.User;

 import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket; /**
* @公众号: java实战分享
* @Author: 姿势帝-博客园
* @Date: 2019-09-01 15:50
* @Description:
*/
public class ClientSocketDemo {
/**
* 客户端设计逻辑:
* 1.创建socket通信,设置IP和端口
* 2.建立IO输出流,向服务端发送数据
* 3.建立IO输入流,读取服务端应答的数据
*
* @param args
*/
public static void main(String[] args) {
Socket socket = null;
ObjectOutputStream writer = null;
ObjectInputStream reader = null;
try {
//1.创建socket通信
socket = new Socket("localhost", 1002);
//2.建立IO输出流,向服务端发送数据
writer = new ObjectOutputStream(socket.getOutputStream());
writer.writeObject(new User("无忌", 19));
writer.flush();
//3.建立IO输入流,读取服务端应答的数据
reader = new ObjectInputStream(socket.getInputStream());
User o = (User) reader.readObject();
System.out.println("==>" + o);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (reader != null) {
reader.close();
}
if (writer != null) {
writer.close();
}
if (socket != null) {
socket.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

6.socket的多线程通信

  网络编程系统化学习(1.1.)--socket基础

  代码

  处理线程类

 package com.wfd360.com.socketThread.thread;

 import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket; public class HandleServerThread extends Thread {
private Socket socket; public HandleServerThread() { } public HandleServerThread(Socket socket) {
this.socket = socket;
} @Override
public void run() {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String s = reader.readLine();
System.out.println("收到连接的客户端:ip=" + socket.getInetAddress() + " port:" + socket.getPort() + " s=" + s);
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
writer.write("服务端收到的数据为[" + s + "]\n");
writer.flush();
writer.close();
reader.close();
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}

  服务端

 package com.wfd360.com.socketThread;

 import com.wfd360.com.socketThread.thread.HandleServerThread;

 import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket; /**
* @公众号: java实战分享
* @Author: 姿势帝-博客园
* @Date: 2019-09-01 15:14
* @Description:
*/
public class ServerSocketDemo {
static int port = 1103; /**
* 服务端开启多线程(为后面讲解NIO做铺垫)
* 因为:Socket socket = server.accept();是线程阻塞的;
* 如何让多个客户端同时连接到服务端勒?
* 思路:当服务端接收到一个客户端就开启一个线程去处理,然后继续监听其他的客户端来连接
*
* @param args
*/
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(port);
int i = 1;
System.out.println("服务端已启动----" + i);
while (true) {
System.out.println("等待客户端连接---" + i);
Socket socket = server.accept();
System.out.println("已连接---" + i);
HandleServerThread thread = new HandleServerThread(socket);
thread.start();
i++;
}
}
}

  客户端

 package com.wfd360.com.socketThread;

 import java.io.*;
import java.net.Socket; /**
* @公众号: java实战分享
* @Author: 姿势帝-博客园
* @Date: 2019-09-01 15:50
* @Description:
*/
public class ClientSocketDemo {
static String host = "192.168.0.103";
static Integer port = 1103; /**
* 客户端设计逻辑:
* 1.创建socket通信,设置IP和端口
* 2.建立IO输出流,向服务端发送数据
* 3.建立IO输入流,读取服务端应答的数据
*
* @param args
*/
public static void main(String[] args) {
Socket socket = null;
BufferedWriter writer = null;
BufferedReader reader = null;
try { while (true) {
//1.创建socket通信
socket = new Socket(host, port);
//2.建立IO输出流,向服务端发送数据
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
//读取控制台输入的数据
System.out.print("请输入要发送的数据(结束请输入exit):");
String sysIn = new BufferedReader(new InputStreamReader(System.in)).readLine();
System.out.println("sysIn=" + sysIn);
if ("exit".equals(sysIn)) {
break;
}
//注意这里必须加\n,因为是按照行读取
writer.write("向服务端发送数据:" + sysIn + "\n");
writer.flush();
//3.建立IO输入流,读取服务端应答的数据
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String s = reader.readLine();
System.out.println("客户端收到数据:" + s);
} } catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (reader != null) {
reader.close();
}
if (writer != null) {
writer.close();
}
if (socket != null) {
socket.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

7.socket实现文件传输(多线程)

  线程处理代码

 package com.wfd360.com.socketFile.thread;

 import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.net.Socket; public class HandleServerThread extends Thread {
private Socket socket; public HandleServerThread() { } public HandleServerThread(Socket socket) {
this.socket = socket;
} /**
* 将文件保存到本地
*/
@Override
public void run() {
try {
//需要写入的文件
File file = new File("E:\\test\\c.txt");
file.createNewFile(); //处理流 输入
DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
String fileName = dataInputStream.readUTF();
long fileSize = dataInputStream.readLong();
System.out.println("fileName=" + fileName + ",fileSize=" + fileSize);
//处理流 输出
DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream(file)); //采用字节数组读取文件内容
byte[] bytes = new byte[128];
int readSize = dataInputStream.read(bytes, 0, bytes.length);
System.out.println("readSize=" + readSize);
while (readSize != -1) {
//写入本地文件
dataOutputStream.write(bytes, 0, readSize);
dataOutputStream.flush();
readSize = dataInputStream.read(bytes, 0, bytes.length);
System.out.println("readSize=" + readSize);
}
System.out.println("========文件上传完成==========");
} catch (Exception e) {
e.printStackTrace();
}
}
}

  服务端代码

 package com.wfd360.com.socketFile;

 import com.wfd360.com.socketFile.thread.HandleServerThread;

 import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket; /**
* @公众号: java实战分享
* @Author: 姿势帝-博客园
* @Date: 2019-09-01 15:14
* @Description:
*/
public class ServerSocketDemo {
static int port = 1103; /**
* 服务端开启多线程
*
* @param args
*/
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(port);
int i = 1;
System.out.println("服务端已启动----" + i);
while (true) {
System.out.println("等待客户端连接---" + i);
Socket socket = server.accept();
System.out.println("已连接---" + i);
HandleServerThread thread = new HandleServerThread(socket);
thread.start();
i++;
}
}
}

  客户端代码

 package com.wfd360.com.socketFile;

 import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.net.Socket; /**
* @公众号: java实战分享
* @Author: 姿势帝-博客园
* @Date: 2019-09-01 15:50
* @Description:
*/
public class ClientSocketDemo {
static String host = "192.168.0.103";
static Integer port = 1103; /**
* 客户端设计逻辑:
* 1.创建socket通信,设置IP和端口
* 2.建立IO输出流,向服务端发送数据
* 3.建立IO输入流,读取服务端应答的数据
*
* @param args
*/
public static void main(String[] args) throws Exception {
Socket socket = new Socket(host, port);
//准备上传文件
File file = new File("E:\\test\\a.txt");
DataInputStream dataInputStream = new DataInputStream(new FileInputStream(file));
//准备输出 处理流
DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
dataOutputStream.writeUTF(file.getName());
dataOutputStream.flush();
dataOutputStream.writeLong(file.length());
dataOutputStream.flush();
//准备以字节的形式输出
byte[] bytes = new byte[128];
int readSize = dataInputStream.read(bytes, 0, bytes.length);
while (readSize != -1) {
System.out.println("readSize=" + readSize);
dataOutputStream.write(bytes, 0, readSize);
dataOutputStream.flush();
readSize = dataInputStream.read(bytes, 0, bytes.length);
}
System.out.println("-----完成-------");
dataInputStream.close();
//当关闭的时候,服务端才会停止监听
dataOutputStream.close();
}
}

8.UDP的socket

8.1.udp与tcp的区别

  UDP协议和TCP协议都是传输层协议。

  TCP(Transmission Control Protocol,传输控制协议)提供的是面向连接,可靠的字节流服务。

  即客户和服务器交换数据前,必须现在双方之间建立一个TCP连接,之后才能传输数据。

  并且提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。

  UDP(User Data Protocol,用户数据报协议)是一个简单的面向数据报文的运输层协议。

  它不提供可靠性,只是把应用程序传给IP层的数据报发送出去,但是不能保证它们能到达目的地。

  由于UDP在传输数据报前不用再客户和服务器之间建立一个连接,且没有超时重发等机制,所以传输速度很快。

8.2.基于udp的socket案例

  服务端

 package com.wfd360.com.socketUDP;

 import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress; public class UDPServer {
/**
* 一、DatagramPacket类:
* <p>
* 如果把DatagramSocket比作创建的港口码头,那么DatagramPacket就是发送和接收数据的集装箱。
* 构造函数:一个用来接收数据,一个用来发送数据
* public DatagramPacket(byte[] buf,int length) //接收数据
* 构造 DatagramPacket 用来接收长度为 ilength 的包。
* public DatagramPacket(byte[] buf,int length,InetAddress address,int port)
* 构造数据报文包用来把长度为 ilength 的包传送到指定宿主的指定的端口号。
* getAddress()
* 返回接收或发送此数据报文的机器的 IP 地址。
* getData()
* 返回接收的数据或发送出的数据。
* getLength()
* 返回发送出的或接收到的数据的长度。
* getPort()
* 返回接收或发送该数据报文的远程主机端口号。
* <p>
* 二、DatagramSocket类
* <p>
* 此类表示用来发送和接收数据报包的套接字。 数据报套接字是包投递服务的发送或接收点。
* DatagramSocket(int port) 创建数据报套接字并将其绑定到本地主机上的指定端口。
* DatagramSocket(int port, InetAddress laddr) 创建数据报套接字,将其绑定到指定的本地地址。
* <p>
* receive(DatagramPacket p)
* 从此套接字接收数据报包。
* void send(DatagramPacket p)
* 从此套接字发送数据报包。
* bind(SocketAddress addr)
* 将此 DatagramSocket 绑定到特定的地址和端口。
* void close()
* 关闭此数据报套接字。
* void connect(InetAddress address, int port)
* 将套接字连接到此套接字的远程地址。
* void connect(SocketAddress addr)
* 将此套接字连接到远程套接字地址(IP 地址 + 端口号)。
* void disconnect()
* 断开套接字的连接。
* getInetAddress()
* 返回此套接字连接的地址。
* InetAddress getLocalAddress()
* 获取套接字绑定的本地地址。
* <p>
* 三、InetAddress类
* InetAddress用于表示计算机IP地址的一个类,而在日常应用中的IP地址用"192.168.0.1",
* "WWW.it315.org"等字符串格式表示的。
* getByName方法
* getHostAddress方法
*
* @param args
*/
public static void main(String[] args) throws Exception {
int port = 1001;
String host = "192.168.0.104";
//1.创建服务端 DatagramSocket(int port, InetAddress laddr) 创建数据报套接字,将其绑定到指定的本地地址。
DatagramSocket datagramSocket = new DatagramSocket(port);
System.out.println("服务已开启-----");
//2.接收数据
byte[] bytes = new byte[128];
DatagramPacket datagramPacket = new DatagramPacket(bytes, 0, bytes.length);
System.out.println("-----等待接收数据");
datagramSocket.receive(datagramPacket);
InetAddress address = datagramPacket.getAddress();
int port1 = datagramPacket.getPort();
int length = datagramPacket.getLength();
byte[] data = datagramPacket.getData();
String s = new String(data);
String format = String.format("address=%s,port=%s,length=%s,data=%s", address, port1, length, s);
System.out.println(format);
//3.发送数据包
String sendData = "发送数据-" + s;
byte[] bytes1 = sendData.getBytes();
InetAddress byName = InetAddress.getByName(host);
DatagramPacket datagramPacketSend = new DatagramPacket(bytes1, 0, bytes1.length, byName, 1002);
datagramSocket.send(datagramPacketSend);
}
}

  客户端

 package com.wfd360.com.socketUDP;

 import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress; public class UDPClient {
public static void main(String[] args) throws Exception {
int port = 1002;
String host = "192.168.0.104";
//1.创建服务端 DatagramSocket(int port, InetAddress laddr) 创建数据报套接字,将其绑定到指定的本地地址。
DatagramSocket datagramSocket = new DatagramSocket(port);
//3.发送数据包
String sendData = "你好吗?";
byte[] bytes1 = sendData.getBytes();
InetAddress byName = InetAddress.getByName(host);
DatagramPacket datagramPacketSend = new DatagramPacket(bytes1, 0, bytes1.length, byName, 1001);
datagramSocket.send(datagramPacketSend);
//2.接收数据
byte[] bytes = new byte[128];
DatagramPacket datagramPacket = new DatagramPacket(bytes, 0, bytes.length);
System.out.println("-----等待接收数据");
datagramSocket.receive(datagramPacket);
InetAddress address = datagramPacket.getAddress();
int port1 = datagramPacket.getPort();
int length = datagramPacket.getLength();
byte[] data = datagramPacket.getData();
String s = new String(data);
String format = String.format("address=%s,port=%s,length=%s,data=%s", address, port1, length, s);
System.out.println(format);
}
}

完美!