目录
一、浅析TCP协议
TCP协议是建立在lP协议之上的,简单地说,IP协议只负责发数据包,不保证顺序和正确性,而TCP协议负责控制数据包传输,它在传输数据之前需要先建立连接,建立连接后才能传输数据,传输完后还需要断开连接。TCP协议之所以能保证数据的可靠传输,是通过接收确认、超时重传这些机制实现的。并且,TCP协议允许双向通信,即通信双方可以同时发送和接收数据。
TCP协议也是应用最广泛的协议,许多高级协议都是建立在TCP协议之上的,例如HTTP 、SMTP等。
二、什么是Socket?
在开发网络应用程序的时候,会遇到Socket 这个概念。Socket是一个抽象概念,一个应用程序通过一个 Socket来建立一个远程连接,而Socket内部通过TCP/IP协议把数据传输到网络。
┌───────────┐ ┌───────────┐
│Application│ │Application│
├───────────┤ ├───────────┤
│ Socket │ │ Socket │
├───────────┤ ├───────────┤
│ TCP │ │ TCP │
├───────────┤ ┌──────┐ ┌──────┐ ├───────────┤
│ IP │<────>│Router│<─────>│Router│<────>│ IP │
└───────────┘ └──────┘ └──────┘ └───────────┘
Java提供的几个Socket相关的类就封装了操作系统提供的接口:ServerSocket类、Socket类。
为什么需要Socket 进行网络通信?因为仅仅通过IP地址进行通信是不够的,同一台计算机同一时间会运行多个网络应用程序,例如浏览器、QQ、邮件客户端等。当操作系统接收到一个数据包的时候,如果只有IP地址,它没法判断应该发给哪个应用程序,所以,操作系统抽象出socket接口,每个应用程序需要各自对应到不同的Socket,数据包才能根据Socket 正确地发到对应的应用程序。
使用Socket进行网络编程时,本质上就是两个进程之间的网络通信。其中一个进程必须充当服务器端,它会主动监听某个指定的端口,另一个进程必须充当客户端,它必须主动连接服务器的IP地址和指定端口,如果连接成功,服务器端和客户端就成功地建立了一个TCP连接,双方后续就可以随时发送和接收数据。
因此,当Socket连接成功地在服务器端和客户端之间建立后:·对服务器端来说,它的 Socket是指定的IP地址和指定的端口号;·对客户端来说,它的Socket是它所在计算机的IP地址和一个由操作系统分配的随机端口号。
三、案例—图片上传服务器端、客户端
服务器端:
要使用 Socket 编程,我们首先要编写服务器端程序。Java标准库提供了ServerSocket来实现对指定IP和指定端口的监听。
图片上传服务器端:
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 图片文件服务器端
*/
public class ImageFileServer {
public static void main(String[] args) {
try {
//ServerSocket:服务器进行通信的对象
ServerSocket server = new ServerSocket(8808);
//死循环:不断接收客户端的连接
while(true) {
//服务器进入"等待"状态
//如果有客户端连接时,该方法返回此客户端的client
Socket client = server.accept();
InetAddress clientNetAddress = client.getInetAddress();
System.out.println("客户端" + clientNetAddress.getHostAddress() + "开始连接。。。。");
//接收来自客户端上传的图片
//输入流:读取来自客户端发送的图片文件流
//输出流:写入本地图片
String imageName = clientNetAddress.getHostAddress().replaceAll("\\.", "-") + ".jpg";
try(InputStream in = client.getInputStream();
BufferedInputStream bis = new BufferedInputStream(in);
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("E:\\test\\img\\" + imageName))){
//每次读取来自客户端的图片文件流
//写入本地
byte[] buff = new byte[1024];
int len = -1;
while((len = bis.read(buff)) != -1) {
bos.write(buff, 0, len);
}
System.out.println("图片读取完毕!!");
//输出提示信息 => 客户端
try(BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()))){
writer.write("upload success !");
writer.newLine();
}
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务器通过上述代码,在指定端口8808监听,如果ServerSocket监听成功,我们就使用一个无限循环来处理客户端的连接,注意到代码server.accept()表示每当有新的客户端连接进来后,就返回一个 socket实例,这个Socket实例就是用来和刚连接的客户端进行通信的。
如果没有客户端连接进来,accept()方法会阻塞并一直等待。如果有多个客户端同时连接进来,Serversocket会把连接扔到队列里,然尼一个一个处理。对于Java程序而言,只需要通过循环不断调用accept()就可以获取新的连接。
客户端:
相比服务器端,客户端程序就简单许多
图片上传客户端:
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* 上传图片客户端
*/
public class UploadImageClient {
public static void main(String[] args) {
//Socket:客户端进行通信的组件
//本地图片读取 =>通过输出流(发送)至服务器
//OutputStream:输出流,将读取到的本地图片文件流,发送(输出)至服务器
//BufferedInputStream:输入流,读取本地图片
try (Socket client = new Socket("192.168.254.137",8808);
OutputStream out = client.getOutputStream();
BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("E:\\test\\girl.jpg"))
){
//每次读取1024个字节
byte[] buff = new byte[1024];
int len = -1;
while((len = bis.read(buff)) != -1) {
//将读取到的内容,通过输入流发送至服务器
out.write(buff);
}
//"输出"暂时结束(Socket没有关闭)
client.shutdownOutput();
//读取来自服务器的反馈
try(BufferedReader reader = new BufferedReader(
new InputStreamReader(client.getInputStream()))){
String reply = reader.readLine();
System.out.println("服务器的反馈: " + reply);
}
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端程序通过下述代码,连接到服务器端,注意上述代码的服务器地址是"192.168.254.137",端口号是8808。如果连接成功,将返回一个Socket实例,用于后续通信。
四、拓展:Socket流
当socket 连接创建成功后,无论是服务器端,还是客户端,我们都使用 Socket实例进行网络通信。因为TCP是一种基于流的协议,因此,Java标准库使用工nputstream和outputstream来封装 Socket的数据流,这样我们使用Socket的流,和普通IO流类似:
// 用于读取网络数据:
InputStream in = sock.getInputStream();
// 用于写入网络数据:
OutputStream out = sock.getOutputStream();
写入网络数据时,必须要调用flush()方法。如果不调用flush(,我们很可能会发现,客户端和服务器都收不到数据,这并不是Java标准库的设计问题,而是我们以流的形式写入数据的时候,并不是一写入就立刻发送到网络,而是先写入内存缓冲区,直到缓冲区满了以后,才会一次性真正发送到网络,这样设计的目的是为了提高传输效率。如果缓冲区的数据很少,而我们又想强制把这些数据发送到网络,就必须调用flush()强制把缓冲区数据发送出去。
五、总结
使用Java进行TCP编程时,需要使用Socket模型:
·服务器端用ServerSocket监听指定端口;
·客户端使用socket(InetAddress, port)连接服务器;
·服务器端用accept()接收连接并返同Socket实例;
·服务器端通过使用多线程同时处理多个客户端连接,利用线程池可大幅提升效率;
. flush()方法用于强制输出缓冲区到网络。
以上就是对于基于TCP协议的网络编程知识分享,如有不当之处,还请大家多多评论指正,喜欢的话可以留下您的关注和点赞,一起学习,一起进步!