参考资料:百度百科TCP协议

本文涉及Java IO流、异常的知识,可参考我的另外的博客

一文简述Java IO

一文简述JAVA内部类和异常

1.概述

计算机网络相关知识:

OSI七层模型

一个报文可以类似于一封信,就像下图(引自狂神说Java)非常生动。

网络编程的目的:数据交换、通信

网络通信的要素:

如何实现网络通信?

通信双方地址:

  • ip
  • 端口号

网络协议:

HTTP, FTP, TCP, UDP 等等

1.1 IP

IP地址:InetAddress(无构造器)

  • 唯一定位一台网络上计算机
  • 127.0.0.1 :本机,localhost
  • ip地址分类:ipv4(4个字节)/ipv6(128位,8个无符号整数组成),公网(ABCD类地址)/私网(局域网)
  • 域名:记忆ip问题

主机名解析

主机名称到IP地址解析是通过使用本地机器配置信息和网络命名服务(如域名系统(DNS)和网络信息服务(NIS))的组合来实现的。所使用的特定命名服务是默认配置的本地机器。对于任何主机名,返回其对应的IP地址。反向名称解析意味着对于任何IP地址,返回与IP地址关联的主机。

InetAddress类提供了将主机名解析为其IP地址的方法,反之亦然。

InetAddress常用方法:

举例:

public static void main(String[] args) throws UnknownHostException {
//查询本机地址
InetAddress inetAddress = InetAddress.getByName("127.0.0.1");
System.out.println(inetAddress); InetAddress i1 = InetAddress.getByName("localhost");
System.out.println(i1); InetAddress i2 = InetAddress.getLocalHost();
System.out.println(i2); //查询网站ip
InetAddress i3 = InetAddress.getByName("www.baidu.com");
System.out.println(i3);
//常用方法
System.out.println(i3.getAddress()); //返回的是byte[],所以输出了乱码
System.out.println(i3.getCanonicalHostName());//规范的名字
System.out.println(i3.getHostAddress());//ip
System.out.println(i3.getHostName());//主机名
}

InetAddress没有构造器,所以需要调用静态方法进行构造。上述代码结果为:

1.2 端口

端口表示计算机上的一个程序的进程

  • 不同的进程有不同的端口号,用来区分软件。
  • 一般被规定为0~65535
  • TCP端口和UDP端口,均有65536个,两个互不冲突。单个协议下端口是不能冲突的,例:TCP占用8080后,不能再次占用此TCP端口了
  • 端口分类:公有端口01023,HTTP:80,HTTPS:443,FTP:21,Telent:23。程序注册的端口102449151,用来分配给用户或者程序,Tomcat:8080,MySQL:3306,Oracle:1521。动态、私有:49152~65535,尽量不要用这里的端口。
netstat -ano  #这条命令用于查看所有端口
netstat -ano|findstr "8080" #查看指定的端口
tasklist|findstr "8696" #查看指定端口的进程

以上均为Linux命令

InetSocketAddress

该类实现IP套接字地址(IP地址+端口号)它也可以是一对(主机名+端口号),在这种情况下将尝试解析主机名。如果解决方案失败,那么该地址被认为是未解决的,但在某些情况下仍可以使用,例如通过代理连接。

它提供了用于绑定,连接或返回值的套接字所使用的不可变对象。

通配符是一个特殊的本地IP地址。 通常意味着“任何”,只能用于bind操作。

InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1",8080);
InetSocketAddress inetSocketAddress1 = new InetSocketAddress("localhost",8080);
System.out.println(inetSocketAddress);
System.out.println(inetSocketAddress1);
System.out.println(inetSocketAddress.getAddress());
System.out.println(inetSocketAddress.getHostName());
System.out.println(inetSocketAddress.getPort());

以上为相关代码。

1.3 通信协议

网络通信协议可能涉及到:速率,传输码率,代码结构,传输控制等等

主要涉及的是以下两个:

TCP:用户传输协议(3次握手,确定返回信息,以后网络相关知识具体说,不在本篇赘述)

UDP:用户数据报协议(不确定返回信息)

TCP和UDP对比

TCP就像打电话,需要连接,稳定

UDP就像发短信,不需要连接,发完即结束,不稳定

  • 基于连接与无连接;

  • 对系统资源的要求(TCP较多,UDP少);

  • UDP程序结构较简单;

  • 流模式与数据报模式 ;(从下面demo中即可看出)

  • TCP保证数据正确性,UDP可能丢包;

  • TCP保证数据顺序,UDP不保证。

2. TCP协议

TCP三次握手的过程如下:

  1. 客户端发送SYN(SEQ=x)报文给服务器端,进入SYN_SEND状态。
  2. 服务器端收到SYN报文,回应一个SYN (SEQ=y)ACK(ACK=x+1)报文,进入SYN_RECV状态。
  3. 客户端收到服务器端的SYN报文,回应一个ACK(ACK=y+1)报文,进入Established状态。

三次握手完成,TCP客户端和服务器端成功地建立连接,可以开始传输数据了。

TCP连接终止过程:

建立一个连接需要三次握手,而终止一个连接要经过四次握手,这是由TCP的半关闭(half-close)造成的。具体过程如上图所示。

(1) 某个应用进程首先调用close,称该端执行“主动关闭”(active close)。该端的TCP于是发送一个FIN分节,表示数据发送完毕。

(2) 接收到这个FIN的对端执行 “被动关闭”(passive close),这个FIN由TCP确认。

注意:FIN的接收也作为一个文件结束符(end-of-file)传递给接收端应用进程,放在已排队等候该应用进程接收的任何其他数据之后,因为,FIN的接收意味着接收端应用进程在相应连接上再无额外数据可接收。

(3) 一段时间后,接收到这个文件结束符的应用进程将调用close关闭它的套接字。这导致它的TCP也发送一个FIN。

(4) 接收这个最终FIN的原发送端TCP(即执行主动关闭的那一端)确认这个FIN。 [3]

既然每个方向都需要一个FIN和一个ACK,因此通常需要4个分节。

TCP协议相关资料参考自百度百科,计算机网络相关知识不再详细描述。

2.1 TCP连接的实现

服务端:

public class TestTCPserver {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(9999);
Socket accept = serverSocket.accept();//等待client连接
InputStream is = accept.getInputStream();
//管道流,将一个输入流通过管道转化为一个合适的输出流,不用管道流直接String可能会输出乱码
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while((len=is.read(buffer))!=-1){
baos.write(buffer,0,len);
}
System.out.println(baos.toString());
baos.close();
is.close();
accept.close();
serverSocket.close();
//正式写代码的过程中,一定要用try,catch,finally,为了代码的安全,出了事故容易判断
}
}

用到了socket类,其中涉及了IO流部分的知识。

客户端:

public class TestTCPclient {
public static void main(String[] args) throws IOException {
InetAddress serverIp = InetAddress.getByName("127.0.0.1");
int port = 9999;
//创建一个socket连接,连接的是本机
Socket socket = new Socket(serverIp,port);
OutputStream os = socket.getOutputStream();
os.write("hello".getBytes());
os.close();
socket.close();
}
}

注意:socket是需要关闭的

  • socket该类实现客户端套接字(也称为“套接字”)。套接字是两台机器之间通讯的端点。套接字的实际工作由SocketImpl类的实例执行。 应用程序通过更改创建套接字实现的套接字工厂,可以配置自己创建适合本地防火墙的套接字。

  • ServerSocket该类实现了服务器套接字。 服务器套接字等待通过网络进入的请求。 它根据该请求执行一些操作,然后可能将结果返回给请求者。

    服务器套接字的实际工作由SocketImpl类的实例执行。 应用程序可以更改创建套接字实现的套接字工厂,以配置自己创建适合本地防火墙的套接字。

客户端:连接服务器socket,发送消息

服务器:建立服务的端口 ServerSocket,等待用户连接,接受用户消息

2.1 TCP实现文件上传

与消息传递类似,只是IO操作稍微变了一下

服务端:

public class TCPserverdemo1 {
public static void main(String[] args) throws IOException {
//创建服务
ServerSocket serverSocket = new ServerSocket(9000);
//监听客户端的连接
Socket accept = serverSocket.accept(); //阻塞式监听,会一定等待客户端连接
InputStream is = accept.getInputStream();
FileOutputStream fos = new FileOutputStream(new File("copide1.jpg"));
byte[] buffer = new byte[1024];
int len;
while((len = is.read(buffer))!=-1){
fos.write(buffer,0,len);
}
fos.close();
is.close();
accept.close();
serverSocket.close();
}
}

客户端:

public class TCPclientdemo1 {
public static void main(String[] args) throws IOException {
//创建一个socket连接
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9000);
//创建一个输出流
OutputStream os = socket.getOutputStream(); //.getOutputStream获得了一个SocketOutputStream实例
//读取文件
FileInputStream fis = new FileInputStream(new File("image.jpg"));
byte[] buffer = new byte[1024];
int len;
while((len=fis.read(buffer))!=-1){
os.write(buffer,0,len); //涉及到BIO
}
fis.close();
os.close();
socket.close();
}
}

上面用的是字节流,字节缓冲流也可以使用。字符流不可以,因为可能会读其他文件的类型,字节流比较稳妥。

服务器接收完信息后其实是可以返回消息到客户端的,socket通信:

服务端可增加:

accept.shutdownInput();
//通知客户端已经接收完毕
OutputStream os = accept.getOutputStream();
os.write("ending".getBytes()); //发送给客户端

客户端可增加:

socket.shutdownOutput();//通知服务器传输完毕
InputStream is = socket.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer2 = new byte[1024];
int len2;
while((len2 = is.read(buffer))!=-1){
baos.write(buffer,0,len2);
}
System.out.println(baos);

客户端读入了服务端返回的ending字符串。

实现两端通信之后再决定是否进行其他操作,例如close()等等。

3. UDP

无需连接,但是需要知道对方地址

3.1 UDP消息发送

主要依赖的是DatagramSocketDatagramPacket

  • DatagramSocket此类表示用于发送和接收数据报数据包的套接字。 数据报套接字是分组传送服务的发送或接收点。 在数据报套接字上发送或接收的每个数据包都被单独寻址和路由。 从一个机器发送到另一个机器的多个分组可以不同地路由,并且可以以任何顺序到达。 在可能的情况下,新构建的DatagramSocket启用了SO_BROADCAST套接字选项,以允许广播数据报的传输。 为了接收广播数据包,DatagramSocket应该绑定到通配符地址。 在一些实现中,当DatagramSocket绑定到更具体的地址时,也可以接收广播分组。
  • DatagramPacket该类表示数据报包。 数据报包用于实现无连接分组传送服务。 仅基于该数据包中包含的信息,每个消息从一台机器路由到另一台机器。 从一台机器发送到另一台机器的多个分组可能会有不同的路由,并且可能以任何顺序到达。 包传送不能保证。

以下实现UDP消息传送的一个简单例子:

public class TestUDP1 {
//不需要连接服务器
public static void main(String[] args) throws IOException {
//建立Socket
DatagramSocket datagramSocket = new DatagramSocket(); //为空将默认绑定一个可用端口 //建个数据报
String message = "hello,server";
InetAddress inetAddress = InetAddress.getByName("localhost");
int port = 9000;
int len = message.getBytes().length;
//数据,数据的长度起始位置,要发送给谁
DatagramPacket datagramPacket = new DatagramPacket(message.getBytes(), 0,len, inetAddress, port); datagramSocket.send(datagramPacket); //进行发送
datagramSocket.close();
}
}

UDP发送完就不需要其他操作了,为了验证我们发送的消息,建立了一个接收端:

public class UDPaccept {
public static void main(String[] args) throws IOException {
//开放端口
DatagramSocket socket = new DatagramSocket(9000);
//接收数据报
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length); //接收并不需要对方地址和端口
socket.receive(packet); //阻塞式接收
System.out.println(packet.getAddress());
System.out.println(new String(packet.getData(),0,packet.getData().length));
socket.close();
}
}

其中收发消息用到了两个方法DatagramSocket.send()DatagramSocket.receive()

3.2 UDP聊天的实现(单向)

通过UDP协议进行一个发送端和接收端的demo。当出现"bye"时,结束对话。

发送端:

public class UDPsender {
public static void main(String[] args) throws IOException {
DatagramSocket socket = new DatagramSocket(8080);//发送端口
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
//BufferedInputStream bis = new BufferedInputStream(System.in);
int len;
while(true){
String data = reader.readLine(); //接收键盘输入的信息
DatagramPacket packet = new DatagramPacket(data.getBytes(),0,data.getBytes().length,new InetSocketAddress("localhost",6000));
socket.send(packet);
if(data.equals("bye"))
break;
}
reader.close();
socket.close();
}
}

接收端:

public class UDPreceiver {
public static void main(String[] args) throws IOException {
DatagramSocket socket = new DatagramSocket(6000);//打开接收端口 while(true){
byte[] container = new byte[1024]; //用来装数据报内容
DatagramPacket packet = new DatagramPacket(container,0,container.length); //接受时候只需要一个空byte[]
socket.receive(packet);
byte[]data = packet.getData();
String datas = new String(data,0,data.length); //仍带有byte[]的其他信息,若转为真正字符串需trim()
System.out.println(datas);
if(datas.trim().equals("bye"))
break;
}
socket.close();
}
}

这个demo只实现了单向发消息。后续将实现双向发送。

3.3 双向聊天(多线程)

双向聊天和以上内容相似,只需要每个端开启两个线程(接收线程和发送线程),以下为代码演示:

public class TalkSend implements Runnable{    //发送线程
DatagramSocket socket = null;
BufferedReader reader = null; private String ToIP;
private int ToPort; public TalkSend(String toString, int toPort) {
ToIP = toString;
ToPort = toPort;
} @Override
public void run() {
try {
socket = new DatagramSocket();//发送端口
reader = new BufferedReader(new InputStreamReader(System.in));
} catch (SocketException e) {
e.printStackTrace();
}
//BufferedInputStream bis = new BufferedInputStream(System.in);
while(true){
String data = null;
try {
data = reader.readLine();
DatagramPacket packet = new DatagramPacket(data.getBytes(),0,data.getBytes().length,new InetSocketAddress(this.ToIP,this.ToPort));
socket.send(packet);
if(data.equals("bye"))
break;
} catch (IOException e) {
e.printStackTrace();
}
}
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
socket.close();
}
}
public class TalkReceive implements Runnable{   //接收线程
DatagramSocket socket = null; private int FromPort;
private String Person; public TalkReceive(int fromPort,String person) {
FromPort = fromPort;
Person = person;
} @Override
public void run() {
try {
socket = new DatagramSocket(this.FromPort);//打开接收端口
} catch (SocketException e) {
e.printStackTrace();
} while(true){
byte[] container = new byte[1024]; //用来装数据报内容
DatagramPacket packet = new DatagramPacket(container,0,container.length);
try {
socket.receive(packet);
} catch (IOException e) {
e.printStackTrace();
}
byte[]data = packet.getData();
String datas = new String(data,0,data.length);
System.out.println(Person+":"+datas);
if(datas.trim().equals("bye"))
break;
}
socket.close();
}
}

然后需要设置两个端进行聊天:

new Thread(new TalkSend("localhost",8080)).start();
new Thread(new TalkReceive(6000,"老师")).start();

这里设置为学生端,然后开启两个线程,设置发送端口和接收端口

new Thread(new TalkSend("localhost",6000)).start();
new Thread(new TalkReceive(8080,"学生")).start();

这里设置为老师端,然后开启线程,设置端口,其实打开了4个端口,因为学生端和老师端发送线程中,DatagramSocket默认绑定的还有两个端口。

4. URL

https://www.baidu.com/

统一资源定位符:定位互联网上的某个资源

DNS域名解析: www.baidu.com ——》xxx.xxx.xxx.xxx

协议://ip地址:端口/项目名/资源

URL类的基本使用

URL url = new URL("http://localhost:8080/helloworld/index.jsp?username=yuan&password=123");
System.out.println(url.getProtocol());//得到协议名
System.out.println(url.getHost());//主机
System.out.println(url.getPort());//端口
System.out.println(url.getPath());//文件
System.out.println(url.getFile());//文件全路径
System.out.println(url.getQuery()); //得到url查询的部分(参数)

Java万物皆对象

下载一个URL资源

public class UrlDown {
public static void main(String[] args) throws IOException {
URL url = new URL("http://localhost:8080/gaoyuan/SecurityFile.txt");
//连接到这个资源
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); //需要转换类型,因为返回的是URPConnection
InputStream is = urlConnection.getInputStream();
FileOutputStream fos = new FileOutputStream("SecurityFile.txt");
byte[] buffer = new byte[1024];
int len;
while((len = is.read(buffer))!=-1){
fos.write(buffer,0,len);
}
fos.close();
is.close();
urlConnection.disconnect();//断开连接
}
}

这是打开Tomcat服务器后,把一个文件添加到相应目录之后下载的。

网络编程基础部分结束,例如:计算机网络,BIO等知识将会在后续博客中发布。

最新文章

  1. flume整合kafka
  2. 《BI项目笔记》创建多维数据集Cube(2)
  3. ThinkPHP 使用极光推送给ios推送消息
  4. Keil C51程序设计中几种精确延时方法
  5. codeforces 335A Banana(贪心)
  6. Android应用程序组件介绍
  7. 快速排序的C语言实现
  8. python str转dict
  9. 运营商专线服务的基本原理(BGP传递私网路由)
  10. 一个关于finally和return的面试题
  11. easyui datagrid 去掉外边框及行与行之间的横线标题字体
  12. http 请求头大小写的问题
  13. 制作keil5的pack
  14. 批处理文件(Batch Files )
  15. SQLServer2008数据库卸载图解
  16. 定价(Price)
  17. ps aux命令解析
  18. code1319 玩具装箱
  19. Python学习笔记7:函数对象及函数对象作參数
  20. 常用模块一(random模块、time模块、sys模块)

热门文章

  1. 【函数分享】每日PHP函数分享(2021-2-19)
  2. C++单链表反转、两有序链表合并仍有序
  3. 1.代码规范之 if 语句编写
  4. <span>居中
  5. Vuex入门、同步异步存取值进阶版
  6. Vue框架:vue-cookies组件
  7. JavaScript:什么是回调?
  8. JAVA学生宿舍管理系统
  9. k8s自定义controller设计与实现
  10. windows下MySQL如何完全卸载并安装行的版本