Socket的解释
在开发网络应用程序的时候,会遇到Socket这个概念。Socket是一个抽象概念,一个应用程序通过一个Socket来建立一个远程连接,而Socket内部通过TCP/IP协议把数据传输到网络。
Socket、TCP和部分IP的功能都是由操作系统提供的,不同的编程语言只是提供了对操作系统调用的简单的封装。例如:Java提供的几个Socket相关的类就封装了操作系统提供的接口:ServerSocket类、Socket类。
为什么需要Socket进行网络通信?因为仅仅通过IP地址进行通信是不够的,同一台计算机同一时间会运行多个网络应用程序,例如浏览器、QQ、邮件客户端等。当操作系统接收到一个数据包的时候,如果只有IP地址,它没法判断应该发给哪个应用程序,所以,操作系统抽象出Socket接口,每个应用程序需要各自对应到不同的Socket,数据包才能根据Socket正确地发到对应的应用程序。
一个Socket就是由IP地址和端口号(范围是0~65535)组成,可以把Socket简单理解为IP地址+端口号。端口号总是由操作系统分配,它是一个0~65535之间的数字,其中,小于1024的端口属于特权端口,需要管理员权限,大于1024的端口可以由任意用户的应用程序打开。
所以,如果需要与指定主机进行通信,完整的通信地址是由一个IP地址+端口号组成:
● 101.202.99.2:1201
● 101.202.99.2:1304
● 101.202.99.2:15000
使用Socket进行网络编程时,本质上就是两个进程之间的网络通信。其中一个进程必须充当服务器端,它会主动监听某个指定的端口,另一个进程必须充当客户端,它必须主动连接服务器的IP地址和指定端口,如果连接成功,服务器端和客户端就成功地建立了一个TCP连接,双方后续就可以随时发送和接收数据。
因此,当Socket连接成功地在服务器端和客户端之间建立后:
●对服务器端来说:它的Socket是指定的IP地址和指定的端口号;
●对客户端来说:它的Socket是它所在计算机的IP地址和一个由操作系统分配的随机端口号。
简易实现案例
服务器端
public static void main(String[] args) {
// 创建一个服务器套接字并监听在端口1923上
// 使用try-with-resources确保ServerSocket在方法结束或发生异常时能够被正确关闭
try(ServerSocket serverSocket = new ServerSocket(1923);){
// 无限循环,服务器持续运行
while(true) {
// 阻塞等待直到一个客户端连接到来,然后创建一个Socket实例
Socket clientSocket = serverSocket.accept();
// 获取连接的客户端的IP地址
InetAddress clientAddress = clientSocket.getInetAddress();
// 打印客户端的IP地址,表明客户端正在连接并准备上传图片
System.out.printf("客户端%s正在连接图片服务器,即将上传图片.....\n", clientAddress.getHostAddress());
// 将客户端的IP地址转换为一个合法的文件名(替换点号为短划线)
String img = clientAddress.getHostAddress().replace('.', '-').concat(".jpg");
// 再次使用try-with-resources确保所有打开的流在方法结束或异常时能被正确关闭
try(
java.io.InputStream in = clientSocket.getInputStream(); // 从客户端socket获取输入流
BufferedInputStream bis = new BufferedInputStream(in); // 包装输入流以提高读取效率
BufferedOutputStream bos = new BufferedOutputStream( // 创建输出流到文件
new FileOutputStream("D:\\项目" + img)
);
BufferedWriter writer = new BufferedWriter( // 创建输出流到客户端,用于发送文本消息
new OutputStreamWriter(clientSocket.getOutputStream())
);
){
// 创建一个缓冲数组,用于从输入流中读取数据
byte[] buff = new byte[1024];
// 初始化读取长度,-1表示尚未读取数据
int len = -1;
// 循环读取客户端上传的数据直到读取完毕(注意:这里的终止条件应是len == -1,而不是1)
while((len = bis.read(buff)) != -1) {
// 将读取到的数据写入到文件中
bos.write(buff, 0, len);
}
// 向客户端发送上传成功的确认消息
writer.write("upload success!!!");
}
}
}
// 捕获并处理可能发生的IO异常
catch (IOException e) {
e.printStackTrace();
}
}
客户端
public static void main(String[] args) {
// 这行代码被注释掉了,原本是用来创建一个File对象的,但在此示例中未被使用。
// File f = new File("D:\\项目\\1.jpg");
// 尝试建立一个Socket连接到指定的服务器IP地址和端口号
try (Socket socket = new Socket("192.168.199.157", 8848);
// 创建一个BufferedInputStream用于从本地文件读取数据
BufferedInputStream in = new BufferedInputStream(new FileInputStream("D:\\项目\\1.jpg"));
// 创建一个BufferedOutputStream用于向Socket的输出流写入数据
BufferedOutputStream out = new BufferedOutputStream(socket.getOutputStream());
// 创建一个BufferedReader用于从Socket的输入流读取文本数据
BufferedReader red = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
// 创建一个缓冲区用于存储从文件读取的数据
byte[] buff = new byte[1024];
// 初始化读取长度,-1表示尚未读取数据
int len = -1;
// 循环读取本地文件数据,直到文件结束(read方法返回-1)
while ((len = in.read(buff)) != -1) {
// 将从文件读取的数据写入到Socket的输出流中
out.write(buff, 0, len);
}
// 刷新输出流,确保所有数据都被发送出去
out.flush();
// 关闭Socket的输出流,这通常用于通知接收方数据发送完毕
socket.shutdownOutput();
// 读取服务器通过Socket发送回来的响应,直到没有更多数据可读(readLine返回null)
String line = null;
while ((line = red.readLine()) != null) {
// 打印服务器返回的每一行文本
System.out.println(line);
}
}
// 捕获并处理可能发生的IOException异常
catch (IOException e) {
// 打印异常堆栈跟踪信息
e.printStackTrace();
}
}
小结
使用Java进行TCP编程时,需要使用Socket模型:
●服务器端用ServerSocket监听指定端口;
●客户端使用Socket(InetAddress, port)连接服务器;
●服务器端用accept()接收连接并返回Socket实例;
●双方通过Socket打开InputStream/OutputStream读写数据;
●服务器端通常使用多线程同时处理多个客户端连接,利用线程池可大幅提升效率;
●flush()方法用于强制输出缓冲区到网络。