【JAVA基础篇】Socket编程

一、Socket的概念

Socket是一种通讯机制,通常称为套接字。英文原意是插座,顾明思义,Socket像是一个多孔插座,可以提供多个端口的连接服务

ps:至于socket在计算机术语中怎么就翻译成了“套接字”这个令人费解的词,这真是未解之谜。

二、Java Socket编程示例

2.1、基于TCP协议

tcp协议是面向连接的,通常会有服务端和客户端,服务端和客户端先连接,然后传递消息。

SendMsg:用于创建发送消息的线程

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;

/**
 * 发送消息
 * @author cc
 *
 */
public class SendMsg implements Runnable {
	private OutputStream os;

	public SendMsg(OutputStream os) {
		super();
		this.os = os;
	}

	public OutputStream getOs() {
		return os;
	}

	public void setOs(OutputStream os) {
		this.os = os;
	}

	@Override
	public void run() {
		BufferedReader consoleBr = new BufferedReader(new InputStreamReader(System.in));
		PrintWriter pw = new PrintWriter(os);
		String msg = null;
		while (true) {
			try {
				msg = consoleBr.readLine();
				pw.println(msg);
				pw.flush();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

}

RecevieMsg:用于创建接收消息的线程

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

/**
 * 接收消息
 * 
 * @author cc
 *
 */
public class RecevieMsg implements Runnable {
	private InputStream is;

	public RecevieMsg(InputStream is) {
		super();
		this.is = is;
	}

	public InputStream getIs() {
		return is;
	}

	public void setIs(InputStream is) {
		this.is = is;
	}

	@Override
	public void run() {
		BufferedReader netBr = new BufferedReader(new InputStreamReader(is));
		String msg = null;
		while (true) {
			try {
				msg = netBr.readLine();
				System.out.println(Thread.currentThread().getName() + "读到一行数据:" + msg);
			} catch (IOException e) {
				e.printStackTrace();
				System.exit(0);
			}
		}
	}
}

Server:服务端

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
 * 服务端
 * @author cc
 *
 */
public class Server {
	public static void main(String[] args) {
		ServerSocket socket;
		try {
			socket = new ServerSocket(30000);
			System.out.println("我已经开启服务了!");
			Socket client = socket.accept();
			System.out.println("有客户端连接进来!");
			InputStream is = client.getInputStream();
			OutputStream os = client.getOutputStream();
			Thread thread = new Thread(new RecevieMsg(is), "服务端接收线程");
			Thread thread2 = new Thread(new SendMsg(os), "服务端发送线程");
			thread.start();
			thread2.start();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}
}

Client:客户端

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/**
 * 客户端
 * @author cc
 *
 */
public class Client {
	public static void main(String[] args) {
		Socket socket;
		try {
			socket = new Socket("127.0.0.1", 30000);
			InputStream is = socket.getInputStream();
			OutputStream os = socket.getOutputStream();
			Thread thread = new Thread(new RecevieMsg(is), "客户端接收线程");
			Thread thread2 = new Thread(new SendMsg(os), "客户端发送线程");
			thread.start();
			thread2.start();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}
}

启动Server后再启动Client,然后就可以通过两个控制台聊天啦!

扩展:用上面的代码,如果是一个服务端和一个客户端的话通过控制台进行收发消息没有问题,但是一个服务端和多个客户端(Client类执行多次)的话会有问题,理由是:当你用服务端的终端发送消息的时候,这个消息应该发给哪个客户端呢?实际情况是当服务端终端发送消息的条数达到客户端的数量时,数据才会发出去,并且第一条对应的发给第一个客户端。

因此我们不同通过服务端终端来给客户端发送消息,更改了Server类并且新增了ServerHandleClientMsg类

Server

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
 * 服务端
 * @author cc
 *
 */
public class Server {
	public static void main(String[] args) {
		try {
			ServerSocket socket = new ServerSocket(30000);
			System.out.println("我已经开启服务了!");
			while(true){
				Socket client = socket.accept();
				System.out.println("有客户端连接进来!");
				InputStream is = client.getInputStream();
				OutputStream os = client.getOutputStream();
				Thread thread = new Thread(new ServerHandleClientMsg(is,os), "服务端处理客户端信息");
				thread.start();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
 
	}
}

ServerHandleClientMsg

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;

/**
 * 服务端处理客户端信息
 * @author cc
 *
 */
public class ServerHandleClientMsg implements Runnable{
	
	private InputStream is;
	
	private OutputStream os;

	public void setInputStream(InputStream inputStream) {
		this.is = inputStream;
	}

	public void setOutputStream(OutputStream outputStream) {
		this.os = outputStream;
	}

	public ServerHandleClientMsg(InputStream inputStream, OutputStream outputStream) {
		super();
		this.is = inputStream;
		this.os = outputStream;
	}

	boolean endFlag = false;
	
	@Override
	public void run() {
		BufferedReader netBr = new BufferedReader(new InputStreamReader(is));
		String msg = null;
		while (true) {
			try {
				msg = netBr.readLine();
				System.out.println("线程名为"+Thread.currentThread().getName()+"、线程ID为"+
						Thread.currentThread().getId()+"的线程读到一行数据:"+msg);
				PrintWriter pw = new PrintWriter(os);
				pw.println("服务端已收到您的消息:"+msg);
				pw.flush();
			} catch (IOException e) {
				e.printStackTrace();
				endFlag = true;
			}
			if(endFlag){
				break;
			}
		}
	}

}

2.2、基于UDP协议

udp协议是无连接的,并且是不可靠的,直接向网络发送数据报。

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketAddress;

public class Receive {
    public static void main(String[] args) {
        try {
            // 确定接受方的IP和端口号,IP地址为本地机器地址
            InetAddress ip = InetAddress.getLocalHost();
            int port = 8888;

            // 创建接收方的套接字,并指定端口号和IP地址
            DatagramSocket getSocket = new DatagramSocket(port, ip);

            // 确定数据报接受的数据的数组大小
            byte[] buf = new byte[1024];

            // 创建接受类型的数据报,数据将存储在buf中
            DatagramPacket getPacket = new DatagramPacket(buf, buf.length);

            // 通过套接字接收数据
            getSocket.receive(getPacket);

            // 解析发送方传递的消息,并打印
            String getMes = new String(buf, 0, getPacket.getLength());
            System.out.println("对方发送的消息:" + getMes);

            // 通过数据报得到发送方的IP和端口号,并打印
            InetAddress sendIP = getPacket.getAddress();
            int sendPort = getPacket.getPort();
            System.out.println("对方的IP地址是:" + sendIP.getHostAddress());
            System.out.println("对方的端口号是:" + sendPort);

            // 通过数据报得到发送方的套接字地址
            SocketAddress sendAddress = getPacket.getSocketAddress();

            // 确定要反馈发送方的消息内容,并转换为字节数组
            String feedback = "接收方说:我收到了!";
            byte[] backBuf = feedback.getBytes();

            // 创建发送类型的数据报
            DatagramPacket sendPacket = new DatagramPacket(backBuf,
                    backBuf.length, sendAddress);

            // 通过套接字发送数据
            getSocket.send(sendPacket);

            // 关闭套接字
            getSocket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class Sender {
    public static void main(String[] args) {
        try {
            // 创建发送方的套接字,IP默认为本地,端口号随机
            DatagramSocket sendSocket = new DatagramSocket();

            // 确定要发送的消息:
            String mes = "你好!接收方!";

            // 数据报的数据是以字节数组的形式存储的
            byte[] buf = mes.getBytes();

            // 确定发送方的IP地址及端口号,地址为本地机器地址
            int port = 8888;
            InetAddress ip = InetAddress.getLocalHost();

            // 创建发送类型的数据报:
            DatagramPacket sendPacket = new DatagramPacket(buf, buf.length, ip, port);

            // 通过套接字发送数据:
            sendSocket.send(sendPacket);

            // 确定接受反馈数据的缓冲存储器,即存储数据的字节数组
            byte[] getBuf = new byte[1024];

            // 创建接受类型的数据报
            DatagramPacket getPacket = new DatagramPacket(getBuf, getBuf.length);

            // 通过套接字接受数据
            sendSocket.receive(getPacket);

            // 解析反馈的消息,并打印
            String backMes = new String(getBuf, 0, getPacket.getLength());
            System.out.println("接受方返回的消息:" + backMes);

            // 关闭套接字
            sendSocket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

先启动Receive,在启动Sender

上面的例子是点对点通信,下面我们来看如何进行多播

  
  
import java.net.DatagramPacket;  
import java.net.InetAddress;  
import java.net.MulticastSocket;  
  
public class MulticastListener {  
    private int port;  
    private String host;  
  
    public MulticastListener(String host, int port) {  
        this.host = host;  
        this.port = port;  
    }  
  
    public void listen() {  
        byte[] data = new byte[256];  
        try {  
            InetAddress ip = InetAddress.getByName(this.host);  
            MulticastSocket ms = new MulticastSocket(this.port);  
            ms.joinGroup(ip);  
            DatagramPacket packet = new DatagramPacket(data, data.length);  
            //receive()是阻塞方法,会等待客户端发送过来的信息  
            ms.receive(packet);  
            String message = new String(packet.getData(), 0, packet.getLength());  
            System.out.println(message);  
            ms.close();  
        } catch (Exception e) {  
            e.printStackTrace();  
            System.exit(0);
        }  
    }  
  
    public static void main(String[] args) {  
        int port = 1234;  
        String host = "228.0.0.1";  
        MulticastListener ml = new MulticastListener(host, port);  
        while (true) {  
            ml.listen();  
        }  
    }  
} 
  
  
import java.net.DatagramPacket;  
import java.net.InetAddress;  
import java.net.MulticastSocket;  
  
public class MulticastSender {  
    private int port;  
    private String host;  
    private String data;  
  
    public MulticastSender(String data, String host, int port) {  
        this.data = data;  
        this.host = host;  
        this.port = port;  
    }  
  
    public void send() {  
        try {  
            InetAddress ip = InetAddress.getByName(this.host);  
            DatagramPacket packet = new DatagramPacket(this.data.getBytes(), this.data.length(), ip, this.port);  
            MulticastSocket ms = new MulticastSocket();  
            ms.send(packet);  
            ms.close();  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
  
    public static void main(String[] args) {  
        int port = 1234;  
        String host = "228.0.0.1";  
        String data = "hello world.";  
        MulticastSender ms = new MulticastSender(data, host, port);  
        ms.send();  
    }  
}  

先启动MulticastListener,再启动MulticastSender

ps:多播地址范围224.0.0.0~239.255.255.255

2.3、基于HTTP协议

其实HTTP协议是TCP的一种。下面介绍了Java中基于HTTP协议的两种通信方式。

2.3.1 URLConnection

JDK提供的基于HTTP协议的api实现

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;

public class URLDemo {

	public static void main(String[] args) {
		try {
			URL url = new URL("https://www.baidu.com/");
			URLConnection urlConnection = url.openConnection();
			InputStream is = urlConnection.getInputStream();
			BufferedReader br = new BufferedReader(new InputStreamReader(is));
			String msg = null;
			while ((msg = br.readLine()) != null) {
				System.out.println(msg);
			}
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

}

2.3.2 HttpClient

除此之外HttpClient是Java中另一种基于Http协议的通信方式,相比JDK自带的URLConnection,增加了易用性和灵活性。

下面给出了HttpClient的简单Demo。环境:JDK1.8,用Maven构建,使用SpringBoot框架。

依赖

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.cc</groupId>
	<artifactId>HttpClient</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.4.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
		<!-- springboot的web和test启动库 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
		</dependency>

		<!-- apache httpclient组件 -->
		<dependency>
			<groupId>org.apache.httpcomponents</groupId>
			<artifactId>httpclient</artifactId>
		</dependency>

	</dependencies>

	<build>
		<finalName>${project.artifactId}</finalName>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

应用启动类

package com;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
}

Controller

package com.cc.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * Description: get和post请求测试controller
 * 
 * @author JourWon
 * @date Created on 2018年4月19日
 */
@RestController
@RequestMapping("/hello")
public class HelloWorldController {

	@GetMapping("/get")
	public String get() throws InterruptedException {
		return "get无参请求成功";
	}

	@GetMapping("/getWithParam")
	public String getWithParam(@RequestParam String message) {
		return "get带参请求成功,参数message: " + message;
	}

	@PostMapping("/post")
	public String post(@RequestHeader("User-Agent") String userAgent, @RequestHeader("Accept") String accept,
			@RequestHeader("Accept-Language") String acceptLanguage,
			@RequestHeader("Accept-Encoding") String acceptEncoding, @RequestHeader("Cookie") String cookie,
			@RequestHeader("Connection") String conn) {
		// 打印请求头信息
		System.out.println("Cookie = " + cookie);
		System.out.println("Connection = " + conn);
		System.out.println("Accept = " + accept);
		System.out.println("Accept-Language = " + acceptLanguage);
		System.out.println("Accept-Encoding = " + acceptEncoding);
		System.out.println("User-Agent = " + userAgent);

		return "post无参请求成功";
	}

	@PostMapping("/postWithParam")
	public String postWithParam(@RequestParam String code, @RequestParam String message) {
		return "post带参请求成功,参数code: " + code + ",参数message: " + message;
	}

}

HttpClient响应结果

package com.cc.util;
import java.io.Serializable;

/**
 * Description: 封装httpClient响应结果
 * 
 * @author JourWon
 * @date Created on 2018年4月19日
 */
public class HttpClientResult implements Serializable {

	/**
     * 响应状态码
     */
    private int code;

    /**
     * 响应数据
     */
    private String content;

	public HttpClientResult(int code, String content) {
		super();
		this.code = code;
		this.content = content;
	}
	
	public HttpClientResult(int code) {
		super();
		this.code = code;
	}

	public HttpClientResult() {
		super();
	}

}

核心代码:使用httpclient api发送http请求

package com.cc.util;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

/**
 * Description: httpClient工具类
 * 
 * @author JourWon
 * @date Created on 2018年4月19日
 */
public class HttpClientUtils {

	// 编码格式。发送编码格式统一用UTF-8
	private static final String ENCODING = "UTF-8";

	// 设置连接超时时间,单位毫秒。
	private static final int CONNECT_TIMEOUT = 6000;

	// 请求获取数据的超时时间(即响应时间),单位毫秒。
	private static final int SOCKET_TIMEOUT = 6000;

	/**
	 * 发送get请求;不带请求头和请求参数
	 * 
	 * @param url
	 *            请求地址
	 * @return
	 * @throws Exception
	 */
	public static HttpClientResult doGet(String url) throws Exception {
		return doGet(url, null, null);
	}

	/**
	 * 发送get请求;带请求参数
	 * 
	 * @param url
	 *            请求地址
	 * @param params
	 *            请求参数集合
	 * @return
	 * @throws Exception
	 */
	public static HttpClientResult doGet(String url, Map<String, String> params) throws Exception {
		return doGet(url, null, params);
	}

	/**
	 * 发送get请求;带请求头和请求参数
	 * 
	 * @param url
	 *            请求地址
	 * @param headers
	 *            请求头集合
	 * @param params
	 *            请求参数集合
	 * @return
	 * @throws Exception
	 */
	public static HttpClientResult doGet(String url, Map<String, String> headers, Map<String, String> params)
			throws Exception {
		// 创建httpClient对象
		CloseableHttpClient httpClient = HttpClients.createDefault();

		// 创建访问的地址
		URIBuilder uriBuilder = new URIBuilder(url);
		if (params != null) {
			Set<Entry<String, String>> entrySet = params.entrySet();
			for (Entry<String, String> entry : entrySet) {
				uriBuilder.setParameter(entry.getKey(), entry.getValue());
			}
		}

		// 创建http对象
		HttpGet httpGet = new HttpGet(uriBuilder.build());
		/**
		 * setConnectTimeout:设置连接超时时间,单位毫秒。
		 * setConnectionRequestTimeout:设置从connect Manager(连接池)获取Connection
		 * 超时时间,单位毫秒。这个属性是新加的属性,因为目前版本是可以共享连接池的。
		 * setSocketTimeout:请求获取数据的超时时间(即响应时间),单位毫秒。
		 * 如果访问一个接口,多少时间内无法返回数据,就直接放弃此次调用。
		 */
		RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT)
				.setSocketTimeout(SOCKET_TIMEOUT).build();
		httpGet.setConfig(requestConfig);

		// 设置请求头
		packageHeader(headers, httpGet);

		// 创建httpResponse对象
		CloseableHttpResponse httpResponse = null;

		try {
			// 执行请求并获得响应结果
			return getHttpClientResult(httpResponse, httpClient, httpGet);
		} finally {
			// 释放资源
			release(httpResponse, httpClient);
		}
	}

	/**
	 * 发送post请求;不带请求头和请求参数
	 * 
	 * @param url
	 *            请求地址
	 * @return
	 * @throws Exception
	 */
	public static HttpClientResult doPost(String url) throws Exception {
		return doPost(url, null, null);
	}

	/**
	 * 发送post请求;带请求参数
	 * 
	 * @param url
	 *            请求地址
	 * @param params
	 *            参数集合
	 * @return
	 * @throws Exception
	 */
	public static HttpClientResult doPost(String url, Map<String, String> params) throws Exception {
		return doPost(url, null, params);
	}

	/**
	 * 发送post请求;带请求头和请求参数
	 * 
	 * @param url
	 *            请求地址
	 * @param headers
	 *            请求头集合
	 * @param params
	 *            请求参数集合
	 * @return
	 * @throws Exception
	 */
	public static HttpClientResult doPost(String url, Map<String, String> headers, Map<String, String> params)
			throws Exception {
		// 创建httpClient对象
		CloseableHttpClient httpClient = HttpClients.createDefault();

		// 创建http对象
		HttpPost httpPost = new HttpPost(url);
		/**
		 * setConnectTimeout:设置连接超时时间,单位毫秒。
		 * setConnectionRequestTimeout:设置从connect Manager(连接池)获取Connection
		 * 超时时间,单位毫秒。这个属性是新加的属性,因为目前版本是可以共享连接池的。
		 * setSocketTimeout:请求获取数据的超时时间(即响应时间),单位毫秒。
		 * 如果访问一个接口,多少时间内无法返回数据,就直接放弃此次调用。
		 */
		RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT)
				.setSocketTimeout(SOCKET_TIMEOUT).build();
		httpPost.setConfig(requestConfig);
		// 设置请求头
		/*
		 * httpPost.setHeader("Cookie", ""); httpPost.setHeader("Connection",
		 * "keep-alive"); httpPost.setHeader("Accept", "application/json");
		 * httpPost.setHeader("Accept-Language", "zh-CN,zh;q=0.9");
		 * httpPost.setHeader("Accept-Encoding", "gzip, deflate, br");
		 * httpPost.setHeader("User-Agent",
		 * "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36"
		 * );
		 */
		packageHeader(headers, httpPost);

		// 封装请求参数
		packageParam(params, httpPost);

		// 创建httpResponse对象
		CloseableHttpResponse httpResponse = null;

		try {
			// 执行请求并获得响应结果
			return getHttpClientResult(httpResponse, httpClient, httpPost);
		} finally {
			// 释放资源
			release(httpResponse, httpClient);
		}
	}

	/**
	 * 发送put请求;不带请求参数
	 * 
	 * @param url
	 *            请求地址
	 * @param params
	 *            参数集合
	 * @return
	 * @throws Exception
	 */
	public static HttpClientResult doPut(String url) throws Exception {
		return doPut(url);
	}

	/**
	 * 发送put请求;带请求参数
	 * 
	 * @param url
	 *            请求地址
	 * @param params
	 *            参数集合
	 * @return
	 * @throws Exception
	 */
	public static HttpClientResult doPut(String url, Map<String, String> params) throws Exception {
		CloseableHttpClient httpClient = HttpClients.createDefault();
		HttpPut httpPut = new HttpPut(url);
		RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT)
				.setSocketTimeout(SOCKET_TIMEOUT).build();
		httpPut.setConfig(requestConfig);

		packageParam(params, httpPut);

		CloseableHttpResponse httpResponse = null;

		try {
			return getHttpClientResult(httpResponse, httpClient, httpPut);
		} finally {
			release(httpResponse, httpClient);
		}
	}

	/**
	 * 发送delete请求;不带请求参数
	 * 
	 * @param url
	 *            请求地址
	 * @param params
	 *            参数集合
	 * @return
	 * @throws Exception
	 */
	public static HttpClientResult doDelete(String url) throws Exception {
		CloseableHttpClient httpClient = HttpClients.createDefault();
		HttpDelete httpDelete = new HttpDelete(url);
		RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT)
				.setSocketTimeout(SOCKET_TIMEOUT).build();
		httpDelete.setConfig(requestConfig);

		CloseableHttpResponse httpResponse = null;
		try {
			return getHttpClientResult(httpResponse, httpClient, httpDelete);
		} finally {
			release(httpResponse, httpClient);
		}
	}

	/**
	 * 发送delete请求;带请求参数
	 * 
	 * @param url
	 *            请求地址
	 * @param params
	 *            参数集合
	 * @return
	 * @throws Exception
	 */
	public static HttpClientResult doDelete(String url, Map<String, String> params) throws Exception {
		if (params == null) {
			params = new HashMap<String, String>();
		}

		params.put("_method", "delete");
		return doPost(url, params);
	}

	/**
	 * Description: 封装请求头
	 * 
	 * @param params
	 * @param httpMethod
	 */
	public static void packageHeader(Map<String, String> params, HttpRequestBase httpMethod) {
		// 封装请求头
		if (params != null) {
			Set<Entry<String, String>> entrySet = params.entrySet();
			for (Entry<String, String> entry : entrySet) {
				// 设置到请求头到HttpRequestBase对象中
				httpMethod.setHeader(entry.getKey(), entry.getValue());
			}
		}
	}

	/**
	 * Description: 封装请求参数
	 * 
	 * @param params
	 * @param httpMethod
	 * @throws UnsupportedEncodingException
	 */
	public static void packageParam(Map<String, String> params, HttpEntityEnclosingRequestBase httpMethod)
			throws UnsupportedEncodingException {
		// 封装请求参数
		if (params != null) {
			List<NameValuePair> nvps = new ArrayList<NameValuePair>();
			Set<Entry<String, String>> entrySet = params.entrySet();
			for (Entry<String, String> entry : entrySet) {
				nvps.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
			}

			// 设置到请求的http对象中
			httpMethod.setEntity(new UrlEncodedFormEntity(nvps, ENCODING));
		}
	}

	/**
	 * Description: 获得响应结果
	 * 
	 * @param httpResponse
	 * @param httpClient
	 * @param httpMethod
	 * @return
	 * @throws Exception
	 */
	public static HttpClientResult getHttpClientResult(CloseableHttpResponse httpResponse,
			CloseableHttpClient httpClient, HttpRequestBase httpMethod) throws Exception {
		// 执行请求
		httpResponse = httpClient.execute(httpMethod);

		// 获取返回结果
		if (httpResponse != null && httpResponse.getStatusLine() != null) {
			String content = "";
			if (httpResponse.getEntity() != null) {
				content = EntityUtils.toString(httpResponse.getEntity(), ENCODING);
			}
			return new HttpClientResult(httpResponse.getStatusLine().getStatusCode(), content);
		}
		return new HttpClientResult(HttpStatus.SC_INTERNAL_SERVER_ERROR);
	}

	/**
	 * Description: 释放资源
	 * 
	 * @param httpResponse
	 * @param httpClient
	 * @throws IOException
	 */
	public static void release(CloseableHttpResponse httpResponse, CloseableHttpClient httpClient) throws IOException {
		// 释放资源
		if (httpResponse != null) {
			httpResponse.close();
		}
		if (httpClient != null) {
			httpClient.close();
		}
	}

}

测试类:放在src/test/java包中

import java.util.HashMap;
import java.util.Map;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;

import com.Application;
import com.cc.util.HttpClientResult;
import com.cc.util.HttpClientUtils;

/**
 * Description: HttpClientUtils工具类测试
 * 
 * @author JourWon
 * @date Created on 2018年4月19日
 */
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = Application.class)
@WebAppConfiguration
public class HttpClientUtilsTest {

	/**
	 * Description: 测试get无参请求
	 * 
	 * @throws Exception
	 */
	// @Test
	public void testGet() throws Exception {
		HttpClientResult result = HttpClientUtils.doGet("http://127.0.0.1:8080/hello/get");
		System.out.println(result);
	}

	/**
	 * Description: 测试get带参请求
	 * 
	 * @throws Exception
	 */
	// @Test
	public void testGetWithParam() throws Exception {
		Map<String, String> params = new HashMap<String, String>();
		params.put("message", "helloworld");
		HttpClientResult result = HttpClientUtils.doGet("http://127.0.0.1:8080/hello/getWithParam", params);
		System.out.println(result);
	}

	/**
	 * Description: 测试post带请求头不带请求参数
	 * 
	 * @throws Exception
	 */
	// @Test
	public void testPost() throws Exception {
		Map<String, String> headers = new HashMap<String, String>();
		headers.put("Cookie", "123");
		headers.put("Connection", "keep-alive");
		headers.put("Accept", "application/json");
		headers.put("Accept-Language", "zh-CN,zh;q=0.9");
		headers.put("User-Agent",
				"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36");
		HttpClientResult result = HttpClientUtils.doPost("http://127.0.0.1:8080/hello/post", headers, null);
		System.out.println(result);
	}

	/**
	 * Description: 测试post带参请求
	 * 
	 * @throws Exception
	 */
	@Test
	public void testPostWithParam() throws Exception {
		Map<String, String> params = new HashMap<String, String>();
		params.put("code", "0");
		params.put("message", "helloworld");
		HttpClientResult result = HttpClientUtils.doPost("http://127.0.0.1:8080/hello/postWithParam", params);
		System.out.println(result);
	}

}

三、总结

上面的代码较为繁琐,这里总结一下。

3.1、TCP通信如何实现

1、创建两个线程类,分别用于发送消息和接收消息;

2、基于通信的输出流实例化PringWriter对象,发送消息的线程每次通过PringWriter.println发送一条信息

3、基于通信的输入流实例化BufferReader对象,接收消息的线程每次通过readLine读取一行数据

4、Main线程中,服务端实例化ServerSocket对象、客户端用实例化Socket对象,然后各自获取输入和输出流并创建发送消息和接收消息的线程。(注意服务端要先执行accept方法,监听端口)

3.2、UDP通信如何实现

1、创建一个缓存数据的字节数组,然后基于这个数组实例化DatagramPacket,然后创建接收消息的DatagramSocket,然后调用receive方法

2、将需要发送的数据转换成字节数组,然后创建一个DatagramPacket对象,然后创建发送消息的DatagramSocket,然后调用send方法

3、注意发送和接收的差异:接收方在实例化DatagramSocket时指定ip和端口,发送发在实例化DatagramPacket时指定ip和端口。

多播这里就省略了,跟UDP差不多。

3.3、用JDK原生API

1、实例化URL对象

2、获得一个URLConnection实例

3、获得输入流并包装

3.4、HttpClient

1、首先创建一个默认的httpclient对象(CloseableHttpClient)

2、实例化URIBuilder对象(httpget才有)

3、基于URIBuilder实例化httpget对象

4、httpGet或者httpPost.setConfig

5、设置请求头,设置请求参数(post方法才有)

6、执行请求httpClient.execute(httpMethod),并且返回一个CloseableHttpResponse对象

7、调用httpresponse对象的两个方法getStatusLine、getEntity

8、关闭资源

 

HttpClient代码参考https://www.jianshu.com/p/9504ecc7abad

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值