计算机网络基础
利用通信线路和通信设备,将地理位置不同的、功能独立的多台计算机互连起来,以功能完善的网络软件来实现资源共享和信息传递,就构成了计算机网络系统
IP地址和端口
通过IP地址,区分不同的计算机
每一台电脑在一个网络上拥有一个独属于自己的IP地址,用于区别其他电脑,可以通过对方的IP地址进行访问;所以当我们的手机开着流量的时候,是无法访问连着WIFI的电脑的
电脑上运行的大量程序可能需要通过网络来访问其他计算机,这时通过端口号来进行区分
因此一般看到的是地址都是IP:port
形式访问目标主机的一个应用程序,端口号的值为[0, 65535]
IP地址分为IPv4和IPv6,IPv4类似于192.168.0.11
,上面提到的例子都是使用的IPv4,它一共有四组数字,每组数字占8个bit位,IPv4地址0.0.0.0
表示为2进制就是:00000000.00000000.00000000.00000000,共32个bit,最大为255.255.255.255
IPv6能够保存128个bit位,因此它也可以表示更多的IP地址,一个IPv6地址看起来像这样:1030::C9B4:FF12:48AA:1A2B
,目前也正在向IPv6的阶段过渡
TCP和 UDP
- TCP:当一台计算机想要与另一台计算机通讯时,两台计算机之间的通信需要畅通且可靠(会进行三次握手,断开也会进行四次挥手),这样才能保证正确收发数据,因此TCP更适合一些可靠的数据传输场景
- UDP:它是一种无连接协议,数据不会建立可靠传输,传输过程中有可能会导致部分数据丢失,但是它比TCP传输更加简单高效,适合视频直播之类的
Socket技术
通过Socket技术(它是计算机之间进行通信的一种约定或一种方式),可以实现两台计算机之间的通信,Socket也被翻译为套接字
,是操作系统底层提供的一项通信技术,它支持TCP和UDP。而Java就对socket底层支持进行了一套完整的封装,因此可以通过Java来实现Socket通信。
要实现Socket通信,需创建一个数据发送者和一个数据接收者,也就是客户端和服务端,我们需要提前启动服务端,来等待客户端的连接,而客户端只需要随时启动去连接服务端即可
//服务端
public static void main(String[] args) {
try(ServerSocket server = new ServerSocket(8080)){ //将服务端创建在端口8080上
System.out.println("正在等待客户端连接...");
Socket socket = server.accept(); //当没有客户端连接时,线程会阻塞,直到有客户端连接为止
System.out.println("客户端已连接,IP地址为:"+socket.getInetAddress().getHostAddress());
}catch (IOException e){
e.printStackTrace();
}
}
用户端
public static void main(String[] args) {
try (Socket socket = new Socket("localhost", 8080)){
System.out.println("已连接到服务端!");
}catch (IOException e){
System.out.println("服务端连接失败!");
e.printStackTrace();
}
}
实际上他是一个TCP连接的过程,一旦连接建立,只要客户端不主动关闭,整个程序就不会停止运行;服务端也不仅仅支持一个客户端,在同一网络的前提下,可以连接多个服务端
public static void main(String[] args) {
try(ServerSocket server = new ServerSocket(8080)) { //port,也就是端口号为8080
System.out.println("等待连接...");
while(true) {
Socket s = server.accept(); //无限循环等待客户端的连接
System.out.println("IP地址为:" + socket.getInetAddress().getHostAddress());
}
}catch(IOException e) {
e.printStackTrace();
}
}
使用Socket进行数据传输
通过Socket对象,我们就可以获取到对应的I/O流进行网络数据传输:
服务端
//Server端
public class server {
public static void main(String[] args) {
try(ServerSocket server = new ServerSocket(8080)){ //try-with-resource语句,自动关闭,不需要close
System.out.println("正在等待客户端连接:");
Socket socket = server.accept();
socket.setSoTimeout(5000); // timeout
System.out.println("客户端已连接,IP地址为:" + socket.getInetAddress().getHostAddress());
System.out.println("读取客户端数据:");
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
System.out.println("内容为:" + reader.readLine());
OutputStreamWriter writer = new OutputStreamWriter(socket.getOutputStream());
writer.write("收到\n");
writer.flush();
socket.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
用户端
public class Main {
public static void main(String[] args) {
try(Socket s = new Socket("localhost", 8080);
Scanner sc = new Scanner(System.in)){
System.out.println("已连接到服务区");
OutputStreamWriter writer = new OutputStreamWriter(s.getOutputStream()); //读取发送内容
writer.write(sc.nextLine() + "\n"); //加换行符是读取需要
writer.flush();
s.shutdownOutput();
System.out.println("数据已发送");
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
System.out.println("收到服务端响应:" + reader.readLine());
}catch(IOException e) {
System.out.println("连接失败");
e.printStackTrace();
}finally {
System.out.println("断开连接");
}
}
}
先启动Server端,再启动用户端;127.0.0.1是自己电脑
手动关闭单向的流:
socket.shutdownOutput(); //输出方向关闭
socket.shutdownInput(); //关闭输入方向
可以通过调用setSoTimeout()
方法来设定IO超时时间:
socket.setSoTimeout(3000);
当超过设定时间都依然没有收到客户端或是服务端的数据时,会抛出异常:
java.net.SocketTimeoutException: Read timed out
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
at java.net.SocketInputStream.read(SocketInputStream.java:171)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
at java.io.InputStreamReader.read(InputStreamReader.java:184)
at java.io.BufferedReader.fill(BufferedReader.java:161)
at java.io.BufferedReader.readLine(BufferedReader.java:324)
at java.io.BufferedReader.readLine(BufferedReader.java:389)
at com.test.Main.main(Main.java:41)
也可以不自动链接
try (Socket socket = new Socket();
Scanner sc = new Scanner(System.in)) {
socket.connect(new InetSocketAddress("localhost", 8080), 1000);
}
可能会出现连接双方出现意外却通知不到对方的情况,这个时候其中一方还持有联系,就会占用资源,可以使用setKeepAlive()
的方法来防止:
socket.setKeepAlive(true);
当客户端连接之后,如果此函数设置值为true,当对方没有发送任何数据过来的时候,超过一个时间(要看系统内核参数配置),这边就会发送一个ack探测包,探测TCP/IP连接是否有效
此缓冲区大小为:8192,我们可以手动调整其大小来优化传输效率:
socket.setReceiveBufferSize(25565); //TCP接收缓冲区
socket.setSendBufferSize(25565); //TCP发送缓冲区
使用Socket传输文件
实现文件传输
用户端
public class Main {
public static void main(String[] args) {
try(Socket s = new Socket("localhost", 8080)){
FileInputStream fis = new FileInputStream("test.txt"); //输入文件
OutputStream stream = s.getOutputStream(); //输出流
byte[] bytes = new byte[1024];
int len;
while((len = fis.read(bytes)) != -1) {
stream.write(bytes, 0, len);
}
fis.close();
stream.flush();
stream.close();
System.out.println("文件已传输");
}catch(IOException e) {
System.out.println("连接失败");
e.printStackTrace();
}
}
}
服务端
public class server {
public static void main(String[] args) {
try(ServerSocket server = new ServerSocket(8080)){
Socket socket = server.accept();
InputStream stream = socket.getInputStream();
FileOutputStream file = new FileOutputStream("toserver.txt");
byte[] buffer = new byte[1024];
int len;
while((len = stream.read(buffer)) != -1){
file.write(buffer, 0, len);
}
file.flush();
file.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
使用浏览器访问Socket服务器
public class server {
public static void main(String[] args) {
try(ServerSocket server = new ServerSocket(8080)){
System.out.println("正在等待客户端连接:");
Socket socket = server.accept();
System.out.println("IP地址为:" + socket.getInetAddress().getHostAddress());
InputStream in = socket.getInputStream();
while(true) {
int read = in.read();
if(read == -1) {
break;
}
System.out.print((char) read);
}
}catch(IOException e){
e.printStackTrace();
}
}
}
启动Server端,用浏览器输入http://localhost:8080或是http://127.0.0.1:8080/访问;会发现浏览器无法访问,但是IDEA给了一堆反馈
我们的服务端能够读取HTTP请求。但是Http协议并不会保持长连接,在得到我们响应的数据后会立即关闭TCP连接。
既然使用的是Http连接,如果我们的服务器要支持响应HTTP请求,那么就需要按照HTTP协议的规则,返回一个规范的响应文本,首先是响应头,它至少要包含一个响应码:
HTTP/1.1 200 Accpeted
然后就是响应内容(注意一定要换行再写),我们尝试来编写一下支持HTTP协议的响应内容:
public static void main(String[] args) {
try(ServerSocket server = new ServerSocket(8080)){
System.out.println("正在等待客户端连接...");
Socket socket = server.accept();
System.out.println("客户端已连接,IP地址为:"+socket.getInetAddress().getHostAddress());
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
System.out.println("接收到客户端数据:");
while (reader.ready()) System.out.println(reader.readLine());
OutputStreamWriter writer = new OutputStreamWriter(socket.getOutputStream());
writer.write("HTTP/1.1 200 Accepted\r\n"); //200是响应码,Http协议规定200为接受请求,400为错误的请求,404为找不到此资源(不止这些,还有很多)
writer.write("\r\n"); //在请求头写完之后还要进行一次换行,然后写入响应实体(会在浏览器上展示的内容)
writer.write("You're on your own Kids!");
writer.flush();
}catch (Exception e){
e.printStackTrace();
}
}
我们可以打开浏览器的开发者模式,我们来观察一下浏览器的实际请求过程。