Java网络 - 应用篇
👾以下代码均经过本人实测,请放心食用。顺便求个关注,谢谢!!
Socket 篇
简介
Socket是对于TCP协议的封装,在使用Socket的时候,我们需要在服务端创建一个Socket套接字(也就是一个端口),此时我们可以使用Socket客户端请求该IP:端口,就可以访问到Socket套接字。
优点:比较灵活,不局限于http请求,可以发送任何形式的内容,比如JSON、XML、流等,也可以模拟http请求(但不建议)
缺点:使用上的灵活往往意味着开发上更为负责,一般我们需要自己封装一些逻辑才能在项目中更好的使用
代码实现
下面简单实现一段代码,来实现Socket服务端,启动这段代码后,代码会进入到阻塞状态,直到接受到客户端的信息为止。
SockerServer
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author Jim
* @date 2024/06/15
*/
public class SocketServer {
public static void main(String[] args) {
int port = 12345;
try {
// 创建 ServerSocket 对象
ServerSocket serverSocket = new ServerSocket(port);
System.out.println("服务器启动,等待客户端连接...");
// 监听客户端的连接请求
Socket clientSocket = serverSocket.accept();
System.out.println("客户端已连接");
// 获取输入流,接收客户端消息
InputStream input = clientSocket.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
String message = reader.readLine();
System.out.println("客户端消息:" + message);
// 接收完毕,获取输出流,向客户端发送消息
OutputStream output = clientSocket.getOutputStream();
String result = "你好,我是服务端!";
System.out.println("给客户端返回信息:"+result);
output.write(result.getBytes());
// 关闭流和连接
input.close();
output.close();
clientSocket.close();
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
SocketClient
import java.io.*;
import java.net.Socket;
/**
* @author Jim
* @date 2024/06/15
*/
public class SocketClient {
public static void main(String[] args) {
String serverAddress = "localhost";
int port = 12345;
try {
// 创建 Socket 对象,连接到服务器
Socket socket = new Socket(serverAddress, port);
System.out.println("连接到服务器");
// 获取输出流,向服务器发送消息
OutputStream output = socket.getOutputStream();
String message = "Jim.kk\n";
System.out.println("向服务端发送:"+message);
output.write(message.getBytes());
// 获取服务端消息
InputStream input = socket.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
String result = reader.readLine();
System.out.println("服务器消息: " + result);
// 关闭流和连接
output.close();
input.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
RestTemplate 篇
简介
RestTemplate 是一种支持RESTful 服务的模板类,它简化了对 HTTP 资源的访问,并提供了一组丰富的方法来处理 HTTP 请求和响应。
支持多种 HTTP 方法:
GET
、POST
、PUT
、DELETE
、HEAD
、OPTIONS
、PATCH
等,可以根据需要选择合适的 HTTP 方法进行请求。处理请求和响应:提供了方法来处理请求体、响应体、请求头和响应头等信息,使得与 RESTful 服务的交互变得简单和灵活。
支持响应数据绑定:可以将响应的 JSON 或 XML 数据绑定到 Java 对象,使用 Jackson 或者 JAXB 等进行序列化和反序列化。
错误处理:支持处理不同的 HTTP 状态码和错误,可以定义错误处理逻辑以及异常处理。
HTTP 连接池和请求工厂:内置了 HTTP 连接池和请求工厂,提高了性能并允许对 HTTP 客户端的配置进行优化。
拦截器:可以添加请求和响应的拦截器,用于在发送请求和处理响应之前或之后执行自定义逻辑。
…ForEntity 与 …ForObject 对比
首先两者都可以接收我们在服务端要返回的内容,但是区别在于
...ForObject
方法仅仅接收服务端返回的内容,而``xxxForEntity方法则会返回一个
ResponseEntity<返回内容类型>`的对象,其中包含响应码、响应描述、响应内容(body)以及一个响应头信息。
示例 1 | 发送 Get 请求
服务端(接收方)代码见[附录1]
本次示例使用
getForEntity
与getForObject
来发送请求,除说明使用方式外,还可以对比二者之间的区别。
public static void main(String[] args) {
// 方法 1 | forEntity 获取完整的返回响应
RestTemplate rest = new RestTemplate();
ResponseEntity<String> response = rest.getForEntity("http://127.0.0.1:8080/test/get/{id}", String.class,321);
System.out.println("forEntity -- " response); // forEntity -- <200,返回ID为321的信息,[Content-Type:"text/plain;charset=UTF-8", Content-Length:"23", Date:"Sun, 16 Jun 2024 16:49:16 GMT", Keep-Alive:"timeout=60", Connection:"keep-alive"]>
// 方法 2 | forObject 仅获取返回内容
String result = rest.getForObject("http://127.0.0.1:8080/test/get/{id}", String.class,321);
System.out.println("forObject -- " + result); // forObject -- 返回ID为321的信息
}
执行结果:
示例 2 | 发送 Post 请求
本次请求传递一个User对象,接收端代码与User类请见[附录2]
public static void main(String[] args) {
User user = new User("Jim.kk","123");
RestTemplate rest = new RestTemplate();
ResponseEntity<String> response = rest.postForEntity("http://127.0.0.1:8080/test/post",user,String.class);
System.out.println("forEntity -- " + response); // forEntity -- <200,接收成功,[Content-Type:"text/plain;charset=UTF-8", Content-Length:"12", Date:"Sun, 16 Jun 2024 17:02:01 GMT", Keep-Alive:"timeout=60", Connection:"keep-alive"]>
String result = rest.postForObject("http://127.0.0.1:8080/test/post",user, String.class);
System.out.println("forObject -- " + result); // forObject -- 接收成功
}
执行结果:
示例 3 | 发送 Put 请求
RestTemplate提供的PUT方法没有返回值信息,除了更新的对象信息以外,还必须包含一个更新的资源ID,该请求没有返回值,但是好在我们可以使用exchange方法来模拟PUT请求并获取返回值信息(后文有介绍)。使用put方法发送请求代码如下。
关于PUT请求的接收接口请见[附录3]。
public static void main(String[] args) {
User user = new User("Jim.kk","123");
RestTemplate rest = new RestTemplate();
rest.put("http://127.0.0.1:8080/test/put/{id}",user,String.class,123);
}
请求结果如下所示:
示例 4 | 发送 Delete 请求
我们知道Delete在最初被设计出来的时候,仅用于删除服务器上的一个“资源”,因此该方法只有一个地址值与一个资源ID,在服务器端我们需要通过
@PthVariable
(地址变量值)来接收这个参数,与put方法一样,该请求没有返回值,如果希望访问Delete方法并且得到一个返回值的话,可以使用exchange方法来模拟Delete方法(后文有介绍)。
关于Delete方法的接收端请见[附录4]
public static void main(String[] args) {
RestTemplate rest = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
rest.delete("http://localhost:8080/test/delete/{id}",1); // NULL
}
示例 5 | 发送携带 Params 的请求
在后端接口中,我们经常用到
(@RequestParam("xx1") String xx1,.....)
的请求,我们知道在浏览器中要是访问该方法,需要再URL后面跟?
拼接参数,多个参数之间用&
符号连接,在RestTemplate中也是一样的。
关于接收端方法详见[附录5]。
(此处以Get方法做事例)
public static void main(String[] args) throws UnsupportedEncodingException {
RestTemplate rest = new RestTemplate();
User user = new User("Jim.kk","123");
ResponseEntity<String> result = rest.exchange("http://localhost:8080/test/param?username=Jim.kk&password=123", HttpMethod.GET, new HttpEntity<>(user), String.class);
System.out.println(result.getBody()); // OKK
}
示例 6 | 上传文件
关于上传文件接口请见[附录6]。
上传接口会接受一个
MultipartFile对象(名为file)
,一个String对象(名为description描述)
,因此客户端需要在HttpEntity.body中添加这两个对象,并且上传到服务器即可。
public static void main(String[] args) {
// 创建RestTemplate对象
RestTemplate restTemplate = new RestTemplate();
// 设置文件上传的URL
String uploadUrl = "http://localhost:8080/test/upload";
// 准备上传的文件
File file = new File("/path/to/your/source/wallhaven-l3kq9y.png");
FileSystemResource fileSystemResource = new FileSystemResource(file);
// 设置请求体,包含文件和其他参数
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("file", fileSystemResource);
body.add("description", "一张图");
// 设置请求头部信息
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
// 创建HttpEntity,包含请求头部和请求体
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
// 发送POST请求,上传文件
ResponseEntity<String> response = restTemplate.exchange(
uploadUrl,
HttpMethod.POST,
requestEntity,
String.class);
// 处理响应结果
if (response.getStatusCode() == HttpStatus.OK) {
System.out.println("文件上传成功,服务器返回:" + response.getBody());
} else {
System.err.println("文件上传失败,状态码:" + response.getStatusCode());
}
}
执行结果:
文件上传成功后,该图已经被存储在了相应路径中:
示例 7 | 文件下载接口
关于文件下载接口的事例请看[附录7]。
文件下载接口会返回一个
ResponseEntity<Resource>
对象,Resource中放了一个使用ByteArrayResource
(二进制数组)读取的文件内容。
public static void main(String[] args) {
// 创建RestTemplate对象
RestTemplate restTemplate = new RestTemplate();
// 文件下载的URL
String fileUrl = "http://127.0.0.1:8080/test/download";
// 发送GET请求,接收文件内容
ResponseEntity<Resource> response = restTemplate.exchange(
fileUrl,
HttpMethod.GET,
null,
Resource.class);
// 处理响应结果
if (response.getStatusCode() == HttpStatus.OK) {
try {
// 获取文件名
String fileName = extractFileName(response);
// 保存文件到本地
saveFileToLocal(response.getBody(), fileName);
System.out.println("文件下载成功,保存为:" + fileName);
} catch (IOException e) {
System.err.println("保存文件失败:" + e.getMessage());
}
} else {
System.err.println("文件下载失败,状态码:" + response.getStatusCode());
}
}
// 提取文件名
private static String extractFileName(ResponseEntity<Resource> response) {
String contentDisposition = response.getHeaders().getFirst(HttpHeaders.CONTENT_DISPOSITION);
if (contentDisposition != null && !contentDisposition.isEmpty()) {
return contentDisposition.replaceFirst("(?i)^.*filename=\"?([^\"]+)\"?.*$", "$1");
}
return "downloaded.file";
}
// 将文件保存到本地
private static void saveFileToLocal(Resource body, String fileName) throws IOException {
String path = "/path/to/save/";
byte[] bytes = new byte[body.getInputStream().available()];
body.getInputStream().read(bytes);
try (FileOutputStream fos = new FileOutputStream(path + fileName)) {
fos.write(bytes);
}
}
文件保存结果如下所示:
使用 exchange 模拟各种请求
上文说到一些方法没有返回值,因此我们无法判断我们的请求是成功还是失败,但是我们可以通过exchange来模拟各种请求,以达到获取返回信息的目的,以下是模拟各种请求的示例代码。
模拟 GET 请求
关于GET请求的接收方代码可以查看[附录1]
public static void main(String[] args) {
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.exchange(
"http://127.0.0.1:8080/test/get/1",
HttpMethod.GET,
HttpEntity.EMPTY,
String.class
);
System.out.println(response);
}
相应结果如下:
模拟 Post 请求
在发送Post请求时,除了携带请求体以外,我们还可以发送一个请求头,以下代码中我们携带请求头一起发送。
接收端代码请看[附录8]
public static void main(String[] args) {
User user = new User("Jim.kk","123");
HttpHeaders headers = new HttpHeaders();
headers.add("xxx","testHeaderMsg");
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.exchange(
"http://127.0.0.1:8080/test/post2",
HttpMethod.POST,
new HttpEntity<>(user,headers),
String.class
);
System.out.println(response);
}
相应结果如下:
模拟 PUT 请求
本次请求仅传递一个
RequestBody
对象,接收端代码详见[附录9]
public static void main(String[] args) {
User user = new User("Jim.kk","123");
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.exchange(
"http://127.0.0.1:8080/test/put2",
HttpMethod.PUT,
new HttpEntity<>(user),
String.class
);
System.out.println(response);
}
执行结果为:
模拟 Delete 请求
关于接收端代码详见[附录4]
public static void main(String[] args) {
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.exchange(
"http://127.0.0.1:8080/test/delete/1",
HttpMethod.DELETE,
HttpEntity.EMPTY,
String.class
);
System.out.println(response);
}
执行结果为:
OpenFeign 篇
简介
OpenFeign是在SpringCloud中常用的一个Http协议通讯工具,在普通的项目中也可以使用,它比Template更加高层抽象和声明式,不过也更加的方便使用。
使用OpenFeign就像是将远程的接口在本地做了一个“替身”,每次我们仅需要访问这个“替身”,即可向远程的接口发送数据。此是代码访问可以像访问本地接口一样访问远程接口。
OpenFeign分为客户端与服务端,这里主要介绍客户端,服务端一般只会在SpringCloud项目中使用,在此不做赘述。
使用OpenFeign之前,我们需要在启动类上添加上@EnableFeignClients
注解,里面可以填写你的Feign代码所在的包路径,如果可以扫描到的话,不写路径(仅仅写一个注解)也可以。
@SpringBootApplication
@EnableFeignClients("com.jim.springbootdemo.feign")
public class SpringBootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
}
}
示例 1 | 发送 Get 请求
以访问[附录1]为例,我们在项目下创建
feign/TestFeign.java
接口,并在类中添加以下代码,其中:
@FeignClient
:中要为该接口命名,以及写上接口的URL,此处是IP+端口
@GetMapping("/test/{id}")
:表示当前方法用于发送一个Get请求,请求地址为接口上的URL+该注解中的地址
@PathVariable("id")
:表示该远程接口存在一个可变地址值参数,我们调用该接口的时候需要传入该参数
@FeignClient(name = "testFeign", url = "http://localhost:8080")
public interface TestFeign {
@GetMapping("/get/{id}")
String getData(@PathVariable("id") String id);
}
如果对比[附录1]中的代码,我们可以发现该抽象方法所使用的所有注解以及参数,都与远程方法一致,这就是OpenFeign的好处,我们仅需要在该接口类中“复制”一个远程接口的“影子”,我们就能直接调用该远程方法。下面的代码中我们调用该方法,以实现访问远程方法。
@RestController
@RequestMapping("/feign")
public class FeignController {
@Autowired
TestFeign testFeign;
@GetMapping("/get")
public String feign1Get() {
return testFeign.getData();
}
}
执行结果:略
示例 2 | 发送 Post 请求
在同一个接口类中,我们定义一个用于访问[附录2]的Feign接口,如上文所说,我们仅需要拷贝一个远程接口的“影子”即可,代码如下:
@FeignClient(name = "testFeign", url = "http://localhost:8080")
public interface TestFeign {
@PostMapping("/test/post")
String postData(@RequestBody User user);
}
同样,对比[附录2]的接收端接口,会发现该成员方法上的注解与其也是一致的,随后我们也像示例1中一样,调用一下该方法:
@RestController
@RequestMapping("/feign")
public class FeignController {
@Autowired
TestFeign testFeign;
@GetMapping("/post")
public String feign2Post() {
User user = new User("Jim.kk","123");
testFeign.postData(user);
return "OKK";
}
}
执行结果:略
示例 3 | 发送 Put 请求
与上面一样,创建[附录3]的影子接口,随后访问即可,Feign代码如下:
@FeignClient(name = "testFeign", url = "http://localhost:8080")
public interface TestFeign {
@PutMapping("/test/put/{id}")
String putData(@PathVariable("id") String id, @RequestBody User user);
}
调用如下:
@RestController
@RequestMapping("/feign")
public class FeignController {
@Autowired
TestFeign testFeign;
@GetMapping("/put")
public String feign3Put() {
return testFeign.putData("1",new User("Jim.kk","1234"));
}
}
执行结果:略
示例 4 | 发送 Delete 请求
复制[示例4]的影子接口,代码如下:
@FeignClient(name = "testFeign", url = "http://localhost:8080")
public interface TestFeign {
@DeleteMapping("/test/delete/{id}")
String deleteData(@PathVariable("id") String id);
}
调用代码如下:
@RestController
@RequestMapping("/feign")
public class FeignController {
@Autowired
TestFeign testFeign;
@GetMapping("/delete")
public String feign3Del() {
return testFeign.deleteData("1");
}
}
执行结果:略
示例 5 | 接收多个Params
先拷贝[示例5]的影子接口,随后调用即可,Feign代码如下:
@FeignClient(name = "testFeign", url = "http://localhost:8080")
public interface TestFeign {
@GetMapping("/test/param")
String sndParams(@RequestParam("username") String username,
@RequestParam("password") String password);
}
调用代码如下:
@RestController
@RequestMapping("/feign")
public class FeignController {
@Autowired
TestFeign testFeign;
@GetMapping("/params")
public String feign4Params(){
return testFeign.sndParams("1","2");
}
}
示例 6 | 上传文件
一样拷贝[附录6]的影子接口,随后调用,Feign代码如下:
@FeignClient(name = "testFeign", url = "http://localhost:8080")
public interface TestFeign {
@PostMapping(value="/test/upload",consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
ResponseEntity<String> upload(
@RequestPart("file") MultipartFile file,
@RequestParam("description") String description);
}
请注意:请求头上一定要有consumes = MediaType.MULTIPART_FORM_DATA_VALUE
调用如下:
@GetMapping("/upload")
public ResponseEntity<String> feign6Upload() throws IOException {
byte[] bytes = Files.readAllBytes(Paths.get("/Users/hsk/DevCodes/Gitlab/JavaNetworkL/SpringBootDemo/local/feignUpload.png"));
MultipartFile file = new MockMultipartFile("file","a.png","image/png",bytes);
return testFeign.upload(file,"one image");
}
通过以上方式,就可以向[附录6]上传一个文件
示例 7 | 下载文件示例
Feign调用[附录7]实现文件下载功能的代码如下:
@FeignClient(name = "testFeign", url = "http://localhost:8080")
public interface TestFeign {
@GetMapping("/test/download")
ResponseEntity<Resource> downloadFile();
}
调用方式如下:
@GetMapping("/download")
public String feign7Download() throws IOException {
ResponseEntity<Resource> resource = testFeign.downloadFile();
Resource body = resource.getBody();
FileOutputStream os = new FileOutputStream("/Users/hsk/DevCodes/Gitlab/JavaNetworkL/SpringBootDemo/local/" + extractFileName(resource));
byte[] bytes = new byte[body.getInputStream().available()];
os.write(bytes);
return "OKK";
}
private String extractFileName(ResponseEntity<Resource> response) {
String contentDisposition = response.getHeaders().getFirst(HttpHeaders.CONTENT_DISPOSITION);
if (contentDisposition != null && !contentDisposition.isEmpty()) {
return contentDisposition.replaceFirst("(?i)^.*filename=\"?([^\"]+)\"?.*$", "$1");
}
return "downloaded.file";
}
运行结果:略
示例 8 | 携带Http请求头发送报文
这里以访问[附录8]为例
Feign有两种添加请求头的方式,第一种我们可以像是[示例6]一样,直接在定义feign的时候添加
consumes
来定义需要携带的请求头,另外我们也可以通过以下代码传递一个HttpHeaders以达到发送请求头的效果。
@FeignClient(name = "testFeign", url = "http://localhost:8080")
public interface TestFeign {
@PostMapping("/test/post2")
String headers(@RequestHeader HttpHeaders headers, @RequestBody User user);
}
调用代码如下:
@GetMapping("/headers")
public String feign8HttpHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.add("xxx","Jim.kk");
headers.add("yyy","Jim.zzZ");
return testFeign.headers(headers,new User("Jim.kk","123"));
}
执行结果:略
附录
附录 1 | 接收 Get 请求
此处使用
@PathVariable
来接收参数。
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("/get/{id}")
public String get(@PathVariable("id") String id) {
System.out.println(id);
return "返回ID为" + id + "的信息";
}
}
附录 2 | 接收 Post 请求
@RestController
@RequestMapping("/test")
public class TestController {
@PostMapping("/post")
public String post(@RequestBody User user) {
System.out.println("接收成功:" + user);
return "接收成功";
}
}
User类代码如下:
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String username;
private String password;
}
附录 3 | 接收 Put 请求
@RestController
@RequestMapping("/test")
public class TestController {
@PutMapping("/put/{id}")
public String put(@PathVariable("id") String id,@RequestBody User user) {
return "id为"+id+"的用户更新为"+user;
}
}
User类代码如下:
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String username;
private String password;
}
附录 4 | 接收 Delete 请求
@RestController
@RequestMapping("/test")
public class TestController {
@DeleteMapping("/delete/{id}")
public String delete(@PathVariable("id") String id) {
System.out.println(id);
return "id为" + id + "的资源已删除";
}
}
附录 5 | 接收多个 Params
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("/param")
public String params(@RequestParam("username") String username,
@RequestParam("password") String password) {
System.out.println("username:"+username+"password:"+password);
return "OKK";
}
}
附录 6 | 上传文件接口
@RestController
@RequestMapping("/test")
public class TestController {
@PostMapping("/upload")
public ResponseEntity<String> handleFileUpload(
@RequestParam("file") MultipartFile file,
@RequestParam("description") String description) {
String UPLOAD_DIR = "/path/to/save";
// 检查文件是否为空
if (file.isEmpty()) {
return ResponseEntity.badRequest().body("上传的文件为空");
}
// 检查并创建上传文件保存目录
File uploadDir = new File(UPLOAD_DIR);
if (!uploadDir.exists()) {
uploadDir.mkdirs();
}
// 获取文件名
String fileName = file.getOriginalFilename();
Path filePath = Paths.get(UPLOAD_DIR, fileName);
try {
// 保存文件到指定目录
Files.write(filePath, file.getBytes());
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("文件上传失败:" + e.getMessage());
}
// 返回上传成功的消息
return ResponseEntity.ok("文件上传成功,文件描述:" + description);
}
}
附录 7 | 下载文件接口
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("/download")
public ResponseEntity<Resource> downloadFile() throws IOException {
// 读取文件内容
byte[] bytes = Files.readAllBytes(Paths.get("/Users/hsk/DevCodes/Gitlab/JavaNetworkL/SpringBootDemo/file/wallhaven-l3kq9y.png"));
ByteArrayResource resource = new ByteArrayResource(bytes);
// 设置响应头部信息
HttpHeaders headers = new HttpHeaders();
headers.setContentDisposition(ContentDisposition.builder("attachment").filename("a.png").build());
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
// 返回ResponseEntity,包含文件内容和响应头
return ResponseEntity.ok()
.headers(headers)
.body(resource);
}
}
附录 8 | 接收 Post 请求且接受 HttpHeaders
@PostMapping("/post2")
public String post2(@RequestHeader HttpHeaders headers,@RequestBody User user) {
System.out.println("接收成功:" + user);
return "接收成功,头部信息为:" + headers + "User对象为:"+user;
}
附录 9 | 接收 Put 请求
@PutMapping("/put2")
public String put2(@RequestBody User user) {
return "更新用户信息为"+user;
}
结语
当然,除了以上介绍的三种调用方式之外,Java以及相关框架还有许多十分优秀的网络通讯工具,篇幅有限这里不多做介绍,感兴趣的小伙伴可以自己研究一下!!求关注咯!