- 非阻塞 IO
- 其實無論是用 Java NIO 還是用 Netty,達到百萬連接都沒有任何難度。因為它們都是非阻塞的 IO,不需要為每個連接創建一個線程了。
- nio 和 io的區別
- nio的核心:Channel 通道,Buffer 緩沖區,Selector 選擇器如果是百萬級測試,怎么去找那么多機器?單機最多可以有 6W 的連接,百萬連接起碼需要17臺機器!
如何才能突破這個限制呢?
- 其實這個限制來自于網卡。 可以通過使用虛擬機,并且把虛擬機的虛擬網卡配置成了橋接模式解決了問題。
根據物理機內存大小,單個物理機起碼可以跑4-5個虛擬機,所以最終百萬連接只要4臺物理機就夠了。
端口受限設置
windows客戶端
- 打開注冊表:regedit HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\ Services\TCPIP\Parameters
新建 DWORD值,name:TcpTimedWaitDelay,value:0(十進制) –> 設置為0
新建 DWORD值,name:MaxUserPort,value:65534(十進制) –> 設置最大連接數65534 重啟系統
liunx客戶端
cat /proc/sys/net/ipv4/ip_local_port_range
值為32768 610
- 大概也就是共61000-32768=28232個端口可以使用,單個IP對外只能發送28232個TCP請求。
- 以管理員身份,把端口的范圍區間增到最大:
echo "1024 65535"> /proc/sys/net/ipv4/ip_local_port_range
現在有64511個端口可用.
- 以上做法只是臨時,系統下次重啟,會還原。 更為穩妥的做法是修改/etc/sysctl.conf文件,增加一行內容
net.ipv4.ip_local_port_range= 1024 65535
sysctl -p
現在可以使用的端口達到64510個(假設系統所有運行的服務器是沒有占用大于1024的端口的,較為純凈的centos系統可以做到),要想達到50萬請求,還得再想辦法。
增加IP地址一般假設本機網卡名稱為 eth0,那么手動再添加幾個虛擬的IP:
ifconfig eth0:1 192.168.190.151
ifconfig eth0:2 192.168.190.152 ......
或者偷懶一些:
for i in seq 1 9; do ifconfig eth0:$i 192.168.190.15$i up ; done
這些虛擬的IP地址,一旦重啟,或者 service network restart 就會丟失。
為了模擬較為真實環境,在測試端,手動再次添加9個vmware虛擬機網卡
代碼
入口
package com.nio.test;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequestEncoder;
import io.netty.handler.codec.http.HttpResponseDecoder;
import io.netty.handler.codec.http.HttpVersion;
import java.net.URI;
import java.nio.charset.StandardCharsets;
public class HttpClient {
public void connect(String host, int port) throws Exception {
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(workerGroup);
b.channel(NioSocketChannel.class);
b.option(ChannelOption.SO_KEEPALIVE, true);
b.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
// 客戶端接收到的是httpResponse響應,所以要使用HttpResponseDecoder進行解碼
ch.pipeline().addLast(new HttpResponseDecoder());
// 客戶端發送的是httprequest,所以要使用HttpRequestEncoder進行編碼
ch.pipeline().addLast(new HttpRequestEncoder());
ch.pipeline().addLast(new HttpClientInboundHandler());
}
});
ChannelFuture f = b.connect(host, port).sync();
URI uri = new URI("http://gc.ditu.aliyun.com:80/geocoding?a=深圳市");
String msg = "Are you ok?";
DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET,
uri.toASCIIString(), Unpooled.wrappedBuffer(msg.getBytes("UTF-8")));
// 構建http請求
request.headers().set(HttpHeaders.Names.HOST, host);
request.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
request.headers().set(HttpHeaders.Names.CONTENT_LENGTH, request.content().readableBytes());
// 發送http請求
f.channel().write(request);
f.channel().flush();
f.channel().closeFuture().sync();
// }
} finally {
workerGroup.shutdownGracefully();
}
}
public void connect_post(String host, int port) throws Exception {
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(workerGroup);
b.channel(NioSocketChannel.class);
b.option(ChannelOption.SO_KEEPALIVE, true);
b.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
// 客戶端接收到的是httpResponse響應,所以要使用HttpResponseDecoder進行解碼
ch.pipeline().addLast(new HttpResponseDecoder());
// 客戶端發送的是httprequest,所以要使用HttpRequestEncoder進行編碼
ch.pipeline().addLast(new HttpRequestEncoder());
ch.pipeline().addLast(new HttpClientInboundHandler());
}
});
ChannelFuture f = b.connect(host, port).sync();
URI uri = new URI("http://gc.ditu.aliyun.com:80/geocoding?a=深圳市");
FullHttpRequest request = new DefaultFullHttpRequest(
HttpVersion.HTTP_1_1, HttpMethod.POST, uri.getRawPath());
// 構建http請求
request.headers().set(HttpHeaders.Names.HOST, host);
request.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE); // or HttpHeaders.Values.CLOSE
request.headers().set(HttpHeaders.Names.ACCEPT_ENCODING, HttpHeaders.Values.GZIP);
request.headers().add(HttpHeaders.Names.CONTENT_TYPE, "application/json");
ByteBuf bbuf = Unpooled.copiedBuffer("{\"jsonrpc\":\"2.0\",\"method\":\"calc.add\",\"params\":[1,2],\"id\":1}", StandardCharsets.UTF_8);
request.headers().set(HttpHeaders.Names.CONTENT_LENGTH, bbuf.readableBytes());
// 發送http請求
f.channel().write(request);
f.channel().flush();
f.channel().closeFuture().sync();
// }
} finally {
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
HttpClient client = new HttpClient();
//請自行修改成服務端的IP
for(int k=0; k<1;k++){
client.connect("http://gc.ditu.aliyun.com/", 80);
System.out.println(k);
}
}
}
監聽代碼
package com.nio.test;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponse;
public class HttpClientInboundHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.print("success!~!~");
if (msg instanceof HttpResponse)
{
HttpResponse response = (HttpResponse) msg;
// System.out.println("CONTENT_TYPE:" + response.headers().get(HttpHeaders.Names.CONTENT_TYPE));
System.out.println("HTTP_CODE:" + response.getStatus());
}
if(msg instanceof HttpContent)
{
HttpContent content = (HttpContent)msg;
ByteBuf buf = content.content();
System.out.println(buf.toString(io.netty.util.CharsetUtil.UTF_8));
buf.release();
}
ctx.close();
}
}