目录
一、网络编程基础介绍
现在的手机应用商店上已经很难找到一款纯单机的应用了,你就是想下个单机的记事本它都会跟你要联网权限,虽然笔者还蛮讨厌这种情况的,但不得不说几乎是个软件都需要网络编程,可见其重要程度了。
网络编程是指在计算机网络环境下编写程序的过程。它涉及到网络协议、套接字、数据传输和接收等技术。常见的网络编程语言包括C、Java、Python等。
网络编程的基础是网络协议,它是计算机之间通信的规则和标准。网络协议包括TCP、UDP、IP等,它们定义了数据传输的方式、格式和数据完整性等。编写网络程序时需要遵守相应的协议规范,确保数据能够正确传输和接收。
套接字socket是网络编程中最重要的组件之一,它提供了一种通用的编程接口,用于实现网络通信。套接字可以理解为两个相互通信的端点,其中一个为发送方套接字,另一个为接收方套接字。
总之,网络编程是一个广泛的领域,也是计算机学习中必不可少的一环。
二、经典协议介绍
读者在学习网络编程之前,最好对于IP、端口、网络模型,socket等概念有一个基本的了解,可以参考其他大佬的介绍进行学习。
本文则重点介绍一下日常使用中最常见的三种协议:
TCP/IP协议:TCP/IP是指传输控制协议(TCP)和网际协议(IP)的组合。它是互联网基础通信协议,负责数据的传输和路由。TCP/IP协议是可靠的传输协议,确保数据可靠传输和接收。该协议属于传输层协议,只关心数据传递,不负责数据解析,数据解析一般通过传输层协议进行。
FTP协议:文件传输协议(FTP)是一种用于文件传输的协议,属于应用层协议。它使用 TCP/IP 协议进行数据传输,支持通过网络上传和下载文件。尽管TCP功能较为单一,只能 传输文件,如今使用已经算不上很广泛了,但在网页下载文件的时候还是经常能够遇见的。
HTTP协议:超文本传输协议(HTTP)是用于 Web 上的传输协议,属于应用层协议。HTTP协议是建立在 TCP/IP 协议上的,主要用于从 Web 服务器传输超文本到本地浏览器。目前,HTTP/2 是最新的 HTTP 协议版本,它提供了更高效和更安全的网络传输。HTTP协议应用有多么广泛想必无需笔者赘述,可以说只要你使用浏览器上网就会和它打交道,该协议是十分重要、十分值得学习了解的网络协议。
三、TCP代码示例
TCP协议作为传输层协议,是许多应用层协议(如HTTP,SMTP,IMAP等)的通信基础。本文会通过代码示例介绍该协议。代码复制粘贴后可以直接运行,大家可以自行运行尝试。
3.1设计思路
其实也是TCP协议数据传输的流程
1.【server】服务端启动,创建ServerSocket对象,绑定端口
2.【server】在while死循环中调用ServerSocket对象的accept方法(阻塞),监听连接请求
3.【client】客户端启动,new一个Socket对象,指定服务端的ip和端口(会自动发起连接请求)
4.【server】接收到来自客户端的请求连接,accept方法返回一个Socket对象,创建新线程处理该请求,并在死循环中再次进入下一个accept方法等待新的请求
5.【client】获取Socket对象的OutputStream,向服务端写数据
6.【server】获取Socket对象的InputStream,获取客户端发送来的数据
7.【server】获取Socket对象的OutputStream,向客户端写数据
8.【client】获取Socket对象的InputStream,获取服务端发送来的数据
9.【server或client】调用Socket对象的close方法,关闭连接
3.2代码示例
首先是服务端server代码
public class Server {
public static void main(String[] args) throws IOException {
// new一个ServerSocket,启动服务端
try (ServerSocket ss = new ServerSocket(8888)) {
System.out.println("server is running");
while (true) {
// accept方法是阻塞的,在监听到连接请求之前不会运行后面的代码
Socket sock = ss.accept();
// 创建新子线程来处理该请求,主线程则由while循环进入下一个accept
ServerThread serverThread = new ServerThread(sock);
Thread t = new Thread(serverThread);
t.start();
// 获取请求来源
System.out.println("get connection from:" + sock.getRemoteSocketAddress());
}
}
}
}
然后是server利用子线程处理请求用到的ServerThread
public class ServerThread implements Runnable {
Socket mSocket;
public ServerThread(Socket socket) {
mSocket = socket;
}
@Override
public void run() {
// 尝试获取socket的输入输出流
try (InputStream input = mSocket.getInputStream()) {
try (OutputStream output = mSocket.getOutputStream()) {
// 处理请求内容的方法
receiveData(input, output);
}
} catch (Exception e) {
System.out.println("server异常关闭");
} finally {
if (mSocket != null) {
try {
mSocket.close();
System.out.println("连接已关闭");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void receiveData(InputStream input, OutputStream output) throws IOException {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output));
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
StringBuffer inputData = new StringBuffer();
String line;
// 通过readline循环读取,避免遗漏数据
while ((line = reader.readLine()) != null) {
System.out.println(line);
inputData.append(line);
}
System.out.println("server接受到数据:" + inputData.toString());
// 向客户端发送数据
writer.write("hello client");
writer.flush();
System.out.println("server发送数据成功:hello client");
}
}
最后是客户端client
public class Client {
public static void main(String[] args) throws UnknownHostException, IOException {
Socket socket = new Socket("localhost", 8888);
try (InputStream input = socket.getInputStream()) {
try (OutputStream output = socket.getOutputStream()) {
sendData(input, output,socket);
}
} catch (Exception e) {
System.out.println("client异常关闭");
// TODO: handle exception
}
socket.close();
System.out.println("client关闭");
}
private static void sendData(InputStream input, OutputStream output,Socket socket) throws IOException {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output));
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
writer.write("hello server!\n");
writer.flush();
System.out.println("client发送数据成功:hello server");
// 关闭输出流
socket.shutdownOutput();
// 读取服务端返回的数据
StringBuffer getdata = new StringBuffer();
String line;
while ((line = reader.readLine()) != null) {
getdata.append(line);
}
System.out.println("接受到server返回的数据:" + getdata.toString());
}
}
3.3运行结果
服务端server运行结果
客户端运行结果
可以看出,客户端成功向服务端中发起了连接请求,并与服务端进行了数据传输,最终关闭了连接。
3.4踩坑经验
笔者最开始敲代码的时候,考虑到实际传输的数据量可能很大,为了避免接收数据时有所遗漏,特地采用了while+readline循环读取的方式。但运行程序后发现,代码会一直卡在数据读取那一步,server端始终无法获取到数据并进行下一步。通过断点和查阅资料后发现,此处BufferedReader类的readLine方法是阻塞的,只有获取到换行符\n后才会返回,否则会一直卡在这一步。
知道了原因之后就好解决了,笔者总结了以下解决方法:
1.客户端发送数据时在数据末尾添加换行符,即在原始数据"hello server"后添加"\n"。这是最简单和直接的方法了,但是在实际应用里,我们不能预料到客户端和服务端发送的数据末尾是否包含换行符,因此该方法不适用于实际应用场景。
2.客户端发送完数据之后,加一行代码 socket.shutdownOutput(); 该方法用于关闭输出流,相当于告诉服务端“我的数据发送完了,你快接收一下”,服务端会接收到该通知,并读取所有还在该输出流中的数据。但这种方法并不适用于需要多次通信的场景,因为客户端输出流被关闭之后无法恢复。
3.想要客户端和服务端多次互动通信又不希望获取数据时阻塞?那还是自己乖乖根据实际应用需求写好获取数据的方法,在其中处理好返回获取数据的时机而不至于一直阻塞吧(一般是读取到终止符时返回数据、或是根据头文件中的文件长度与读取到的长度是否相等来判断)。
四、小结
本文介绍了网络编程的基本概念以及三种比较重要和常见的网络协议,并通过代码示例重点介绍了TCP协议的实现过程,同时还分享了笔者学习过程中遇到的一些经验与心得。
笔者萌新一枚,如有错漏之处还望指正。
下一篇准备介绍一下http协议,并用代码写一个示例(随缘更新好吧)。
本文参考文章列表如下:
JAVA Socket编程中关于Read()方法阻塞的问题 (如何跳出循环)_read阻塞时如何退出_科班学渣的博客-CSDN博客