网络编程的TCP/UDP协议

时间:2021-09-28 10:19:55

首先,着这里说一下网络编程的三要素:

  1. IP,既一个计算机标志;

  2. 端口,每一个程序都对应一个端口,用来通信,这是在计算机中分辨应用程序的标志,端口范围为1-65535,其中0-1024是系统保留端口。

  3. 协议,即通信所要遵守的规则,这里说两种(tcp/udp)

三要素详解:
特殊的IP地址:

  • 127.0.0.1 本地回环地址 用来做一些本地测试
  • ping IP地址 ; 用来检测本机是否可以和指定的IP地址的计算机可以进行正常通讯
  • ipconfig 用来查看IP地址
  • xxx.xxx.xxx.255 广播地址

端口号:

  • 物理端口 物理设备对应的端口 , 网卡口
  • 逻辑端口 用来标示我们的计算机上的进程 , 端口号的有效范围应该是 0-65535 ,其中0-1024被系统占用或者保留

协议:

  • UDP
    把数据打成一个数据包 , 不需要建立连接
    数据包的大小有限制不能超过64k
    因为无连接,所以属于不可靠协议(可能丢失数据)
    因为无连接 ,所以效率高

  • TCP
    需要建立连接,形成连接通道
    数据可以使用连接通道直接进行传输,无大小限制
    因为有链接,所以属于可靠协议
    因为有链接,所以效率低

在这里,还要引入一个类InetAddress:IP地址的描述类
由于这个类没有构造方法,所以我们要使用它的静态的返回值为InetAddress的方法,这里说一些常用的方法,并且在后面附加上一些代码:

public static InetAddress getByName(String host)( host: 可以是主机名,也可以是IP地址的字符串表现形式)
public String getHostAddress()返回 IP 地址字符串(以文本表现形式)。
public String getHostName()获取此 IP 地址的主机名。
public String toString()将此 IP 地址转换为 String
public static InetAddress getLocalHost()返回本地主机。

这里附上InetAddress类的一些代码:

package com.thz_01;

import java.net.InetAddress;

public class InetAddressDemo {
public static void main(String[] args) throws Exception {
//获取本机对象
InetAddress address = InetAddress.getLocalHost();
InetAddress addresss = InetAddress.getByName("thz");
InetAddress addressss = InetAddress.getByName("172.16.52.196");
System.out.println(address.equals(addresss));//true
System.out.println(addressss.equals(addresss));//true
System.out.println(addressss.equals(address));//true

//获取对象计算机名称
String name = address.getHostName();
System.out.println(name);

//获取计算机Ip
String address2 = address.getHostAddress();
System.out.println(address2);
}

}

网络编程也叫socket编程,套接字编程,叫法不一样但都是一个东西。

  • Socket套接字:网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标识符套接字。
    Socket原理机制:
    通信的两端都有Socket。
    网络通信其实就是Socket间的通信。
    数据在两个Socket间通过IO传输。

这里先说UDP协议的网络编程:
与其相关的类:Socket、DatagramPacket
- UDP协议特点:
1.要将数据打包处理,大小不能超过64K
2.无需建立链接,也称为面向无连接协议
3.由于无需建立链接,所以速度快
4.由于无需建立连接,所以不安全

  • UDP发送端步骤
    1.创建UDP发送端socket对象
    2.创建数据包,
    3.用socket对象将打包好的数据包发送
    4.释放资源
  • UDP服务端步骤
    1.创建UDP服务端socket对象
    2.创建一个空数据包
    3.利用socket对象接收数据包
    4.用空数据包解析和记录接收到的数据包
    5.释放资源

这里附上UDP协议的使用多线程,实现多次键盘录入通信的效果的代码:

package com.thz_03;
//UDP发送端
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;

public class ClientThread implements Runnable{
//构造传参
DatagramSocket ds;
public ClientThread(DatagramSocket ds){
this.ds = ds;
}
@Override
public void run() {
try {
//这里我是给我自己的电脑发送的数据,你也可以给别人的电脑发送,只要你填写了对方的IP并且你们使用了同一个端口就行了
InetAddress address = InetAddress.getLocalHost();
Scanner sc = new Scanner(System.in);
String line;
while((line = sc.nextLine())!=null){
byte[] buf = line.getBytes();
int length = buf.length;
//创建数据包,buf:你要传送的数据;length:你要传送的数据大的小;address:你要给那个IP传送数据;9999:你们使用的端口
DatagramPacket dp = new DatagramPacket(buf, length, address, 9999);
//发送数据包
ds.send(dp);
}
//释放资源
ds.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

}
package com.thz_03;
//UDP服务端
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class ServerThread implements Runnable{
//构造传参
DatagramSocket ds;
public ServerThread(DatagramSocket ds){
this.ds = ds;
}
@Override
public void run() {
try {
//注意,由于启用了线程,继承了Runnable接口,且Runnable接口没有抛出异常,因此这里只能抓即try{}catch{},不能抛出throws
while(true){
//创建一个空数据包
byte[] buf = new byte[1024];
int length = buf.length;
DatagramPacket dp = new DatagramPacket(buf,length);
//接收数据包
ds.receive(dp);

//解析数据包
byte[] data = dp.getData();
int length2 = dp.getLength();
System.out.println(new String(data,0,length2));
//由于你在接受的时候,不知道要接受多少次,因此这里使用while(true)死循环,而且没有跳出语句,所以此代码后的代码是执行不到的,故不用释放资源
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

}
package com.thz_03;

import java.net.DatagramSocket;
import java.net.SocketException;

public class Chat {
public static void main(String[] args) throws Exception {
//使用链式编程创建发送端socket对象并启用这个线程
new Thread(new ServerThread(new DatagramSocket(9999))).start();
//使用链式编程创建服务端socket对象并启用这个线程
new Thread(new ClientThread(new DatagramSocket())).start();
}

}

由于不建立连接,因此无论先启动哪个线程都可以

  • TCP通信特点
    1.需要建立连接
    2.传送数据无大小限制
    3.由于需要建立连接,故也称面向连接通信
    4.由于需要建立连接,故效率较低
    5.由于需要建立连接,故安全
  • TCP通信客户端步骤
    1.创建TCP客户端socket对象,指定服务器IP及端口
    2.使用socket对象获取输出流,并写数据
    3.释放资源
  • TCP服务端步骤
    1.创建TCP服务端socket对象,指定端口
    2.使用socket对象监测这个端口是否有连接
    3.使用socket对象获取输入流
    4.释放资源

需要注意的是,在使用TCP通信时,我们必须先开启服务端,在开启客户端,因为他是一种可靠的协议,否则会产生运行时异常:java.net.ConnectException: Connection refused: connect

总结:UDP通信客户端和服务端创建的socket对象,全是DatagramSocket
TCP通信客户端创建的socket对象是Socket,服务端创建的是ServerSocket

这里附上线程改进的客户端从文件读取数据并发送到服务端然后服务端将数据写入到文件中的代码:

package com.thz_05;
//TCP服务端
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;

public class ServerThread implements Runnable{
//构造传参
Socket sk;
public ServerThread(Socket sk){
this.sk = sk;
}
@Override
public void run() {
//获取流数据,写入文件
try {
//获取输入流
BufferedReader br = new BufferedReader(new InputStreamReader(sk.getInputStream()));
//利用UUID类生成的随机序列作为文件名
BufferedWriter bw = new BufferedWriter(new FileWriter(UUIDUtils.getFileName()));
String line;
while((line = br.readLine())!=null){
bw.write(line);
bw.newLine();
bw.flush();
}
//释放资源
bw.close();
br.close();
sk.close();

} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}


}

}
package com.thz_05;
//TCP客户端
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.Socket;

//读文件发到流
public class Client {
public static void main(String[] args) throws Exception, IOException {
//读文件,写到流
//创建客户端socket对象,这里我是给自己发的数据,你可以使用别人的InetAddress对象给他发数据
Socket sk = new Socket(InetAddress.getLocalHost().getHostAddress(),7777);
BufferedReader br = new BufferedReader(new FileReader("ClientThread.java"));
//获取输出流,并使用转换流转换成字符流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(sk.getOutputStream()));
String line;
while((line = br.readLine())!=null){
bw.write(line);
bw.newLine();
bw.flush();
}
//释放资源
bw.close();
br.close();
sk.close();




}


}
package com.thz_05;
//UUID类是表示通用唯一标识符 (UUID) 的类。 UUID 表示一个 128 位的值。
import java.util.UUID;

public class UUIDUtils {
public static String getFileName(){
////借用UUID的public static UUID randomUUID()方法生成一个UUID,来作为文件名
return UUID.randomUUID().toString().replaceAll("-", "")+".txt";
}

}
package com.thz_05;

import java.net.ServerSocket;
import java.net.Socket;

public class Chat {
public static void main(String[] args) throws Exception {
//创建服务端socket对象
ServerSocket ss = new ServerSocket(7777);
//监测是否有连接
Socket sk = ss.accept();
new Thread(new ServerThread(sk)).start();


}

}