网络协议及编程示例
网络协议这边列举两个较为常用传输层的协议 TCP UDP
TCP
特点
点对点的通信方式,一般应用于客户端和服务端
传输前需要建立TCP连接,形成数据传输通道
连接时需要先进行三次握手,确保连接双方存在,连接可靠性高
传输的数据量大
传输完毕需要释放已建立的连接,进行四次挥手,效率较低
例如:打电话
三次握手
为什么采取三次握手,不是一次两次,不是四次五次呢?
因为第一次和第二次握手之后,连接保证两者存在的可靠性相对较低,第三次握手之后可靠性提升很大,可达到99%,第四次第五次之后虽说可靠性会提升,但是提升的很少,可靠性再第三次已经足够,为了耗费少一些的网络资源,故TCP选择三次握手,即可靠也尽量减少了网络资源的消耗
四次挥手
断开连接的请求,客户端和服务端都可以发送,断开连接的方式为四次挥手,通过四次挥手确定连接已经断开
TCP网络编程例
再网络发送文件时,对于中文,一般使用ByteArrayOutputStream,先把读取的字节存放至ByteArrayOutputStream中,再读取
,这样可以解决中文读取乱码问题
1.客户端发送消息给服务端
服务端
public class TCPSever {
public static void main(String[] args) {
ServerSocket serverSocket = null;
Socket socket = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
//指定服务器端口号
serverSocket = new ServerSocket(9999);
//服务器接收客户端的Socket
socket = serverSocket.accept();
//创建读取流读取客户端消息
is = socket.getInputStream();
//信息读取
int len;
byte[] buffer = new byte[20];
baos = new ByteArrayOutputStream();
while ((len = is.read(buffer)) != -1) {
//使用ByteArrayOutputStream,先把读取的字节存放至ByteArrayOutputStream中,再读取
//解决中文读取乱码问题
//先把字节写到ByteArrayOutputStream对象的数组中
baos.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//读取ByteArrayOutputStream对象中的数据,并显示在控制台上
System.out.println(baos.toString());
//资源关闭
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
客户端
public class TCPClient {
public static void main(String[] args) {
Socket socket = null;
OutputStream os = null;
try {
//创建IP
InetAddress inetAddress = InetAddress.getByName("127.0.0.1");
//指定客户端的端口号和IP 创建Socket对象
socket = new Socket(inetAddress,9999);
//根据Socket对象创建写出流
os = socket.getOutputStream();
//写出操作
os.write("学好Java,网络编程不可少".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
//资源关闭
if (os != null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
在TCP中需要先开启服务端再开启客户端,否则会报错连接不上。
2.客户端发送文件给服务端,服务端将其保存至本地
客户端
public class Client2 {
public static void main(String[] args) {
Socket socket = null;
OutputStream os = null;
OutputStreamWriter osw = null;
BufferedReader bfr = null;
try {
//创建socket
InetAddress inetAddress = InetAddress.getLocalHost();
socket = new Socket(inetAddress, 6666);
//创建输出流 以及 文件读取流
os = socket.getOutputStream();
osw = new OutputStreamWriter(os);
bfr = new BufferedReader(new FileReader("Test"));
//文件读取,并进行传输
int len;
char[] buffer = new char[5];
while ((len = bfr.read(buffer)) != -1) {
osw.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
}
//资源关闭
if (bfr != null){
try {
bfr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (osw != null){
try {
osw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("客户端启动成功");
}
}
服务端
public class Server2 {
public static void main(String[] args) {
ServerSocket serverSocket = null;
Socket socket = null;
InputStream is = null;
BufferedWriter bfw = null;
InputStreamReader isr = null;
try {
//指定自身端口号
serverSocket = new ServerSocket(6666);
System.out.println("服务端启动成功,等待客户端发送请求......");
//接收客户端socket
socket = serverSocket.accept();
System.out.println("收到客户端发送请求");
//创建流对象
is = socket.getInputStream();
bfw = new BufferedWriter(new FileWriter("destTest.txt"));
//转换流转换为字符流
isr = new InputStreamReader(is);
int len;
char[] buffer = new char[5];
while ((len = isr.read(buffer)) != -1) {
bfw.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//资源关闭
if (bfw != null) {
try {
bfw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (isr != null) {
try {
isr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
System.out.println("文件传输成功");
}
}
3.客户端发送消息给服务端,服务端收到并返回发送成功给客户端。发送完成关闭连接
客户端
public class Client3 {
public static void main(String[] args) {
Socket socket = null;
OutputStream os = null;
BufferedInputStream bfis = null;
InputStream is = null;
ByteOutputStream baos = null;
OutputStream os2 = null;
try {
System.out.println("客户端启动......");
//创建socket
socket = new Socket(InetAddress.getLocalHost(), 6666);
//创建流
os = socket.getOutputStream();
bfis = new BufferedInputStream(new FileInputStream("test4.jpg"));
//文件写出
int len;
byte[] buffer = new byte[1024];
while ((len = bfis.read(buffer)) != -1) {
os.write(buffer,0,len);
}
socket.shutdownOutput();
//服务端返回消息读入
is = socket.getInputStream();
baos = new ByteOutputStream();
while ((len = is.read(buffer)) != -1){
baos.write(buffer,0,len);
}
System.out.println(baos.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
//资源关闭
if (bfis != null){
try {
bfis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (os != null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (baos != null) {
baos.close();
}
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (os2 != null){
try {
os2.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
服务端
public class Server3 {
public static void main(String[] args) {
ServerSocket serverSocket = null;
Socket socket = null;
BufferedInputStream bfis = null;
BufferedOutputStream bfos = null;
OutputStream os = null;
ByteArrayOutputStream baos = null;
InputStream is = null;
try {
System.out.println("服务端启动......");
//得到socket
serverSocket = new ServerSocket(6666);
socket = serverSocket.accept();
//创建流
bfis = new BufferedInputStream(socket.getInputStream());
bfos = new BufferedOutputStream(new FileOutputStream("test5.jpg"));
//文件读取写入
int len;
byte[] buffer = new byte[1024];
while ((len = bfis.read(buffer)) != -1) {
bfos.write(buffer,0,len);
}
os = socket.getOutputStream();
os.write("服务器已接收到,正在关闭连接.....".getBytes());
socket.shutdownOutput();
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭资源
if (bfis != null) {
try {
bfis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bfos!=null) {
try {
bfos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (baos != null){
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (is != null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
在使用旧版IO的客户端和服务端数据交换时(因为read操作是阻塞式的)用socket的流做写出操作时,写完一定要关闭socket,否则接受时不能判断是否已经写出完,会一直在while循环中等待一方的输入;使用NIO则可以解决这一问题
测试:客户端发送英文给服务端,服务端将其转换为大写发送回客户端
客户端
public class Client4 {
public static void main(String[] args) {
System.out.println("客户端启动...");
Scanner scanner = new Scanner(System.in);
Socket socket = null;
BufferedInputStream bfis = null;
OutputStream os = null;
ByteArrayOutputStream baos = null;
try {
socket = new Socket(InetAddress.getLocalHost(), 6666);
//创建输出流
os = socket.getOutputStream();
System.out.print("请输入:");
//输出
os.write(scanner.next().getBytes());
socket.shutdownOutput();
//接收服务器返回并输出
bfis = new BufferedInputStream(socket.getInputStream());
int len;
byte[] buffer = new byte[1024];
baos = new ByteArrayOutputStream();
while ((len = bfis.read(buffer)) != -1) {
baos.write(buffer,0,len);
}
System.out.println(baos.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
//资源关闭
if (socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bfis != null){
try {
bfis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (baos != null){
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (os != null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
服务端
public class Server4 {
public static void main(String[] args){
System.out.println("服务端启动...");
ServerSocket serverSocket = null;
Socket socket = null;
OutputStream os = null;
BufferedInputStream bfis = null;
ByteArrayOutputStream baos = null;
try {
//接收客户端socket
serverSocket = new ServerSocket(6666);
socket = serverSocket.accept();
//接收
bfis = new BufferedInputStream(socket.getInputStream());
int len;
byte[] buffer = new byte[1024];
baos = new ByteArrayOutputStream();
while ((len = bfis.read(buffer)) != -1){
System.out.println("进入循环");
baos.write(buffer,0,len);
}
System.out.println("服务器处理完毕,正在返回...");
//返回
os = socket.getOutputStream();
os.write(baos.toString().toUpperCase().getBytes());
socket.shutdownOutput();
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭资源
if (serverSocket != null){
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (baos != null){
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bfis != null){
try {
bfis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (os != null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
UDP
特点
将数据、源、目的封装成数据包,不需要建立连接
发送结束不需要释放资源,开销小,传输速度快但不可靠
传输内容较少一次最多64K
可以广播发送
例如:发电报,发短信
UDP网络编程例
接收端
public class Sender {
public static void main(String[] args) {
DatagramSocket socket = null;
try {
//创建socket
socket = new DatagramSocket();
//创建发送的数据报,并指明发送IP和端口号
byte[] data = "UDP发送的数据".getBytes();
InetAddress inetAddress = InetAddress.getLocalHost();
DatagramPacket datagramPacket = new DatagramPacket(data, 0, data.length, inetAddress, 6666);
//通过socket发送数据报
socket.send(datagramPacket);
} catch (IOException e) {
e.printStackTrace();
} finally {
//资源关闭
socket.close();
}
}
}
发送端
public class Sender {
public static void main(String[] args) {
DatagramSocket socket = null;
try {
//创建socket
socket = new DatagramSocket();
//创建发送的数据报,并指明发送IP和端口号
byte[] data = "UDP发送的数据".getBytes();
InetAddress inetAddress = InetAddress.getLocalHost();
DatagramPacket datagramPacket = new DatagramPacket(data, 0, data.length, inetAddress, 6666);
//通过socket发送数据报
socket.send(datagramPacket);
} catch (IOException e) {
e.printStackTrace();
} finally {
//资源关闭
socket.close();
}
}
}
发送端
public class Sender {
public static void main(String[] args) {
DatagramSocket socket = null;
try {
//创建socket
socket = new DatagramSocket();
//创建发送的数据报,并指明发送IP和端口号
byte[] data = "UDP发送的数据".getBytes();
InetAddress inetAddress = InetAddress.getLocalHost();
DatagramPacket datagramPacket = new DatagramPacket(data, 0, data.length, inetAddress, 6666);
//通过socket发送数据报
socket.send(datagramPacket);
} catch (IOException e) {
e.printStackTrace();
} finally {
//资源关闭
socket.close();
}
}
}
由于UDP协议的特性,先开启发送端,再开启接收端是不会报错的,只是收不到消息;而在TCP中,先开启客户端(不论做发送还是接收)再开启服务端都会进行三次握手导致报错。