1、什么是计算机网络
计算机网络学习的核心内容就是网络协议的学习。网络协议是为计算机网络中进行数据交换而建立的规则、标准或者说是约定的集合。因为不同用户的数据终端可能采取的字符集是不同的,两者需要进行通信,必须要在一定的标准上进行。一个很形象地比喻就是我们的语言,我们大天朝地广人多,地方性语言也非常丰富,而且方言之间差距巨大。A地区的方言可能B地区的人根本无法接受,所以我们要为全国人名进行沟通建立一个语言标准,这就是我们的普通话的作用。同样,放眼全球,我们与外国友人沟通的标准语言是英语,所以我们才要苦逼的学习英语。
计算机网络协议同我们的语言一样,多种多样。而ARPA公司与1977年到1979年推出了一种名为ARPANET的网络协议受到了广泛的热捧,其中最主要的原因就是它推出了人尽皆知的TCP/IP标准网络协议。目前TCP/IP协议已经成为Internet中的"通用语言",下图为不同计算机群之间利用TCP/IP进行通信的示意图。
网络编程的目的:数据交换、通信。
2、网络通信的两个要素
1.通信双方地址:ip、端口
2.通信协议 tcp、udp
ip+端口号就可以连接到具体的一台计算机上的具体的一个应用。
3、IP地址
public class internet {
public static void main(String[] args) {
try {
InetAddress byName = InetAddress.getByName("127.0.0.1");
System.out.println("byName = " + byName);
InetAddress byName1 = Inet4Address.getByName("www.baidu.com");
System.out.println("byName1 = " + byName1);
InetAddress loopbackAddress = InetAddress.getLoopbackAddress();
System.out.println("loopbackAddress = " + loopbackAddress);
InetAddress localHost = InetAddress.getLocalHost();
System.out.println("localHost = " + localHost);
System.out.println(byName1.getCanonicalHostName());//获取此IP地址的标准域名。
System.out.println(byName1.getAddress());//返回原始IP地址
System.out.println(byName1.getHostAddress());//以文本形式返回IP地址字符串。
System.out.println(byName1.getHostName());//获取此IP地址的主机名
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
}
byName = /127.0.0.1
byName1 = www.baidu.com/14.215.177.38
loopbackAddress = localhost/127.0.0.1
localHost = huangkongshenghugh/192.168.123.2
14.215.177.38
[B@a14482
14.215.177.38
www.baidu.com
4、端口
端口表示一台计算机的进程,一个进程就分配的一个端口号。
- 端口规定:0~65535
- TCP和UDP的端口总数:65535*2,单个协议端口不能重复
- 端口分类:
- 公有端口 0~1023
Https :443
Http: 80
Ftp: 21
Telent :23 - 程序注册端口 1024~49151
tomcat:8080
mysql:3306 - 动态私有 49152~65535
netstat -ano #查看所有进程
netstat -ano|findstr “6500” - 公有端口 0~1023
public class MYInetAddressSocket {
public static void main(String[] args) {
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1",8080);
System.out.println("inetSocketAddress = " + inetSocketAddress);
InetAddress address = inetSocketAddress.getAddress();
System.out.println("address = " + address);
String hostName = inetSocketAddress.getHostName();
System.out.println("hostName = " + hostName);
String hostString = inetSocketAddress.getHostString();
System.out.println("hostString = " + hostString);
int port = inetSocketAddress.getPort();
System.out.println("port = " + port);
}
}
inetSocketAddress = /127.0.0.1:8080
address = /127.0.0.1
hostName = 127.0.0.1
hostString = 127.0.0.1
port = 8080
5、协议
TCP/IP协议:
TCP/IP协议是Internet最基本的协议、Internet国际互联网络的基础,由网络层的IP协议和传输层的TCP协议组成。通俗而言:TCP负责发现传输的问题,一有问题就发出信号,要求重新传输,直到所有数据安全正确地传输到目的地。而IP是给因特网的每一台联网设备规定一个地址。
TCP协议的三次握手和四次挥手:
注:seq:"sequance"序列号;ack:"acknowledge"确认号;SYN:"synchronize"请求同步标志;;ACK:“acknowledge"确认标志”;FIN:"Finally"结束标志。
TCP连接建立过程: 首先Client端发送连接请求报文,Server段接受连接后回复ACK报文,并为这次连接分配资源。Client端接收到ACK报文后也向Server段发生ACK报文,并分配资源,这样TCP连接就建立了。
TCP连接断开过程: 假设Client端发起中断连接请求,也就是发送FIN报文。Server端接到FIN报文后,意思是说"我Client端没有数据要发给你了",但是如果你还有数据没有发送完成,则不必急着关闭Socket,可以继续发送数据。所以你先发送ACK,“告诉Client端,你的请求我收到了,但是我还没准备好,请继续你等我的消息”。这个时候Client端就进入FIN_WAIT状态,继续等待Server端的FIN报文。当Server端确定数据已发送完成,则向Client端发送FIN报文,“告诉Client端,好了,我这边数据发完了,准备好关闭连接了”。Client端收到FIN报文后,“就知道可以关闭连接了,但是他还是不相信网络,怕Server端不知道要关闭,所以发送ACK后进入TIME_WAIT状态,如果Server端没有收到ACK则可以重传。”,Server端收到ACK后,“就知道可以断开连接了”。Client端等待了2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,我Client端也可以关闭连接了。Ok,TCP连接就这样关闭了!
为什么要三次握手?
在只有两次"握手"的情形下,假设Client想跟Server建立连接,但是却因为中途连接请求的数据报丢失了,故Client端不得不重新发送一遍;这个时候Server端仅收到一个连接请求,因此可以正常的建立连接。但是,有时候Client端重新发送请求不是因为数据报丢失了,而是有可能数据传输过程因为网络并发量很大在某结点被阻塞了,这种情形下Server端将先后收到2次请求,并持续等待两个Client请求向他发送数据…问题就在这里,Cient端实际上只有一次请求,而Server端却有2个响应,极端的情况可能由于Client端多次重新发送请求数据而导致Server端最后建立了N多个响应在等待,因而造成极大的资源浪费!所以,"三次握手"很有必要!
为什么要四次挥手?
试想一下,假如现在你是客户端你想断开跟Server的所有连接该怎么做?第一步,你自己先停止向Server端发送数据,并等待Server的回复。但事情还没有完,虽然你自身不往Server发送数据了,但是因为你们之前已经建立好平等的连接了,所以此时他也有主动权向你发送数据;故Server端还得终止主动向你发送数据,并等待你的确认。其实,说白了就是保证双方的一个合约的完整执行!
使用TCP的协议:FTP(文件传输协议)、Telnet(远程登录协议)、SMTP(简单邮件传输协议)、POP3(和SMTP相对,用于接收邮件)、HTTP协议等。
UDP协议
UDP用户数据报协议,是面向无连接的通讯协议,UDP数据包括目的端口号和源端口号信息,由于通讯不需要连接,所以可以实现广播发送。
UDP通讯时不需要接收方确认,属于不可靠的传输,可能会出现丢包现象,实际应用中要求程序员编程验证。
UDP与TCP位于同一层,但它不管数据包的顺序、错误或重发。因此,UDP不被应用于那些使用虚电路的面向连接的服务,UDP主要用于那些面向查询—应答的服务,例如NFS。相对于FTP或Telnet,这些服务需要交换的信息量较小。
每个UDP报文分UDP报头和UDP数据区两部分。报头由四个16位长(2字节)字段组成,分别说明该报文的源端口、目的端口、报文长度以及校验值。UDP报头由4个域组成,其中每个域各占用2个字节,具体如下:
(1)源端口号;
(2)目标端口号;
(3)数据报长度;
(4)校验值。
使用UDP协议包括:TFTP(简单文件传输协议)、SNMP(简单网络管理协议)、DNS(域名解析协议)、NFS、BOOTP。
TCP 与 UDP 的区别:TCP是面向连接的,可靠的字节流服务;UDP是面向无连接的,不可靠的数据报服务。
6、TCP实现聊天
//服务端
public class ServerDome1 {
public static void main(String[] args) {
try {
//指定服务端端口号
ServerSocket serverSocket = new ServerSocket(8989);
//等待客户端连接
Socket accept = serverSocket.accept();
InputStream inputStream = accept.getInputStream();
//字节输出流
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] bytes=new byte[1024];
int len;
while ((len=inputStream.read(bytes))!=-1) {
outputStream.write(bytes,0,len);
}
System.out.println(outputStream.toString());
//关闭服务
outputStream.close();
inputStream.close();
accept.close();
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//客户端
public class ClientDome1 {
public static void main(String[] args) {
try {
InetAddress IPAddress = InetAddress.getByName("127.0.0.1");
int port=8989;
//指定ip和端口
Socket socket = new Socket(IPAddress, port);
//使用输出流发送消息
OutputStream outputStream = socket.getOutputStream();
outputStream.write("nothing's gonna change my love for you".getBytes());
//关闭服务
outputStream.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
7、文件上传(TCP)
public class ServerDome {
public static void main(String[] args) throws IOException {
//创建服务
ServerSocket serverSocket = new ServerSocket(7878);
//监听客户端,监听到客户端才会继续走
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
//保存上传的文件
FileOutputStream fileOutputStream = new FileOutputStream(new File("tt.jpg"));
byte[] bytes=new byte[1024];
int len;
while ((len=inputStream.read(bytes))!=-1) {
fileOutputStream.write(bytes,0,len);
}
//接收上传的文件完毕
socket.shutdownInput();
//向客户端发送上传成功的消息
OutputStream outputStream = socket.getOutputStream();
outputStream.write("我已经收到了".getBytes());
//关闭服务
outputStream.close();
fileOutputStream.close();
inputStream.close();
socket.close();
serverSocket.close();
}
}
public class ClientDome {
public static void main(String[] args) throws Exception {
//创建socket连接
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 7878);
OutputStream upstream = socket.getOutputStream();
//输入流来读取本地文件进行上传
FileInputStream fileInputStream = new FileInputStream(new File("dd.jpg"));
byte[] bytes=new byte[1024];
int len;
while ((len=fileInputStream.read(bytes))!=-1) {
upstream.write(bytes,0,len);
}
//上传结束
socket.shutdownOutput();
//接收服务器发来的上传成功的消息
InputStream inputStream = socket.getInputStream();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] bytes1=new byte[1024];
int len1;
while ((len1=inputStream.read(bytes1))!=-1) {
byteArrayOutputStream.write(bytes1,0,len1);
}
System.out.println(byteArrayOutputStream.toString());
//关闭服务
byteArrayOutputStream.close();
inputStream.close();
fileInputStream.close();
upstream.close();
socket.close();
}
}
8、UDP发送消息
public class ServerDome {
public static void main(String[] args) throws IOException {
int port=9090;
//创建socket,相当于建码头
DatagramSocket socket = new DatagramSocket(port);
byte[] bytes=new byte[102];
//创建DatagramPacket,相当于创建集装箱,用于接收发来的数据
DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.length);
//DatagramPacket packet = new DatagramPacket(bytes,bytes.length);
//接收数据
socket.receive(packet);
System.out.println(packet.getAddress().getHostName());
//jdk1.8以后才能用
System.out.println(new String(packet.getData(),0, packet.getLength()));
//关闭
socket.close();
}
}
public class ClientDome {
public static void main(String[] args) throws IOException {
//建立socket连接
//相当于码头
DatagramSocket socket = new DatagramSocket();
//数据,相当于货物
String msg="Hello Server";
InetAddress ipaddress = InetAddress.getByName("127.0.0.1");
int prot=9090;
//创建DatagramPacket,相当于货轮,将数据打包(要发送的数据,数据的长度,Ip地址,端口)
DatagramPacket packet = new DatagramPacket(msg.getBytes(),0,msg.length(),ipaddress,prot);
//发送
socket.send(packet);
//关闭
socket.close();
}
}
9、UDP聊天实现
单项发送
public class UDPServerDome {
public static void main(String[] args) throws IOException {
int port=9090;
DatagramSocket socket = new DatagramSocket(port);
boolean b=true;
while (b){
byte[] bytes=new byte[1024];
DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.length);
socket.receive(packet);
String s = new String(packet.getData(), 0, packet.getLength());
System.out.println(s);
if ("close".equals(s)){
b=false;
}
}
socket.close();
}
}
public class UDPClientDome {
public static void main(String[] args) throws IOException {
DatagramSocket socket = new DatagramSocket();
BufferedReader bufferedReader =null;
boolean b=true;
while (b){
int port=9090;
bufferedReader = new BufferedReader(new InputStreamReader(System.in));
String line = bufferedReader.readLine();
DatagramPacket packet = new DatagramPacket(line.getBytes(), 0, line.length(),InetAddress.getByName("127.0.0.1"), port);
socket.send(packet);
if ("close".equals(line)){
b=false;
}
}
bufferedReader.close();
socket.close();
}
}
双向发送(使用多线程)
学生
public class H_Student {
public static void main(String[] args) {
new Thread(new UDPClientDome1(6767,8787)).start();
new Thread(new UDPServerDome1(7171,"老师")).start();
}
}
老师
public class H_Teacher {
public static void main(String[] args) {
new Thread(new UDPClientDome1(7070,7171)).start();
new Thread(new UDPServerDome1(8787,"学生")).start();
}
}
接收
public class UDPServerDome1 implements Runnable {
int port;//自身端口
String Speaker;
DatagramSocket socket =null;
boolean b=true;
public UDPServerDome1(int port,String Speaker){
this.port=port;
this.Speaker=Speaker;
}
@Override
public void run() {
try {
socket = new DatagramSocket(port);
while (b){
byte[] bytes=new byte[1024];
DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.length);
socket.receive(packet);
String s = new String(packet.getData(), 0, packet.getLength());
System.out.println(Speaker+":"+s);
if ("close".equals(s)){
b=false;
}
}
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
socket.close();
}
}
}
发送
public class UDPClientDome1 implements Runnable {
int port;//自身端口
int toport;//要发送去的端口
DatagramSocket socket = null;
BufferedReader bufferedReader=null;
boolean b=true;
public UDPClientDome1(int port,int toport){
this.port=port;
this.toport=toport;
}
@Override
public void run() {
try {
socket= new DatagramSocket(port);
while (b){
bufferedReader=new BufferedReader(new InputStreamReader(System.in));
String s = bufferedReader.readLine();
DatagramPacket packet = new DatagramPacket(s.getBytes(), 0, s.getBytes().length(), InetAddress.getByName("127.0.0.1"), toport);
//之前使用这个包中文乱码,底下有详解。DatagramPacket packet = new DatagramPacket(s.getBytes(), 0, s.length(), InetAddress.getByName("127.0.0.1"), toport);
socket.send(packet);
if ("close".equals(s)){
b=false;
}
}
} catch (SocketException e) {
e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
socket.close();
}
}
}
在UDP客户端构造发送信息,将信息打包到DatagramPacket中,在打包过程中,如果发送的是纯英文字符,自然是一个字符占据一个字节,如果包含中文,则一个中文字符占据两个字节,所以发送数据的长度如果用s.length()计算,英文字符不会出错,计算中文字符就会少计算了要发送的总字节数,造成UDP服务端接收中文乱码.
统一使用: s.getBytes().length