linux socket的多线程编成来实现多个客户端的连接

本文介绍了一种使用多线程实现服务器与多个客户端并发通信的方法。服务器为每个新连接的客户端创建一个线程,线程负责处理客户端发送的数据并将处理后的结果返回。文章提供了服务器和客户端的代码示例。

在socket的网络编程中常常采用多线程的方法来进行与多个客户端的通信,使服务器与多个客户端的通信并发、并行地进行。相比于多进程,多线程的好处是共用一块内存空间,下面我们来看一个简单的例子,就是多个客户端将字符串发送给服务器,服务器再将字符串反转后回复给客户端
服务器 server.c

    #include <stdio.h>   
    #include <string.h>         
    #include <strings.h>  
    #include <stdlib.h>       
    #include <unistd.h>          
    #include <sys/types.h> 
    #include <sys/socket.h> 
    #include <netinet/in.h> 
    #include <arpa/inet.h>
    #include<pthread.h>
    #define PORT 1234 
    #define BACKLOG 1 





void *start_routine( void *ptr) 
{
    int fd = *(int *)ptr;
    char buf[100];
        int numbytes;
    int i,c=0;
    printf("this is a new thread,you got connected\n");
    printf("fd=%d\n",fd);


    if ((numbytes=recv(fd,buf,100,0)) == -1){ 
    printf("recv() error\n"); 
    exit(1); 
    } 

    char str[numbytes];
    char buffer[numbytes];

    //将收到的字符串反转
    for(c=0;c<(numbytes-1);c++)
    {
    buffer[c]=buf[c];
    }



        printf("receive message:%s\n",buf);

    printf("numbytes=%d\n",numbytes);

    for(i=0;i<numbytes;i++)
    {
        str[i]=buf[numbytes-1-i];

    }


    printf("server will send:%s\n",str);
    numbytes=send(fd,str,sizeof(str),0); 
    printf("send numbytes=%d\n",numbytes);
    close(fd);
}


int  main()  
{ 

    int listenfd, connectfd;    
    struct sockaddr_in server; 
    struct sockaddr_in client;      
    int sin_size; 
    sin_size=sizeof(struct sockaddr_in); 



    pthread_t thread; //定义一个线程号

    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) 
    {   
        perror("Creating socket failed.");
        exit(1);
    }


    int opt = SO_REUSEADDR;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    bzero(&server,sizeof(server));  

    server.sin_family=AF_INET; 
    server.sin_port=htons(PORT); 
    server.sin_addr.s_addr = htonl (INADDR_ANY); 

    // 绑定
    if (bind(listenfd, (struct sockaddr *)&server, sizeof(struct sockaddr)) == -1) { 
    perror("Bind error.");
    exit(1); 
    }   

    // 监听 
    if(listen(listenfd,BACKLOG) == -1){  /* calls listen() */ 
    perror("listen() error\n"); 
    exit(1); 
    } 

    while(1)
    {
        // accept 
        if ((connectfd = accept(listenfd,(struct sockaddr *)&client,&sin_size))==-1) {
        perror("accept() error\n"); 
        exit(1); 
        }



        printf("You got a connection from %s\n",inet_ntoa(client.sin_addr) ); 


        pthread_create(&thread,NULL,start_routine,(void *)&connectfd);

    }
    close(listenfd);

}

客户端 client.c

    #include <stdio.h> 
    #include <unistd.h>
    #include <strings.h>
    #include<string.h>
    #include <sys/types.h> 
    #include <sys/socket.h> 
    #include <stdlib.h>
    #include <netinet/in.h> 
    #include <netdb.h>        

    #define PORT 1234   
    #define MAXDATASIZE 100  

    char receiveM[100];
        char sendM[100]; 

    int main(int argc, char *argv[]) 
 { 
    int fd, numbytes;    
    struct hostent *he;       
    struct sockaddr_in server;  

    //检查用户输入,如果用户输入不正确,提示用户正确的输入方法
    if (argc !=2) {         printf("Usage: %s <IP Address>\n",argv[0]); 
    exit(1); 
    } 

    // 通过函数 gethostbyname()获得字符串形式的ip地址,并赋给he
    if ((he=gethostbyname(argv[1]))==NULL){  
    printf("gethostbyname() error\n"); 
    exit(1); 
    } 


    // 产生套接字fd
    if ((fd=socket(AF_INET, SOCK_STREAM, 0))==-1){ 
    printf("socket() error\n"); 
    exit(1); 
    } 

    bzero(&server,sizeof(server));

    server.sin_family = AF_INET; 
    server.sin_port = htons(PORT); 
    server.sin_addr = *((struct in_addr *)he->h_addr);  
    if(connect(fd, (struct sockaddr *)&server,sizeof(struct sockaddr))==-1){  
    printf("connect() error\n"); 
    exit(1); 
    } 



    // 向服务器发送数据


    printf("send message to server:");

        fgets(sendM,100,stdin);
    int send_le;
        send_le=strlen(sendM);
    sendM[send_le-1]='\0';





        send(fd,sendM,strlen(sendM),0); 

    // 从服务器接收数据
    if ((numbytes=recv(fd,receiveM,MAXDATASIZE,0)) == -1){ 
    printf("recv() error\n"); 
    exit(1); 
    } 

        printf("receive message from server:%s\n",receiveM);

    close(fd); 


 }

运行结果如图,这里以本机中开三个终端当作三个客户端:这里写图片描述

这里写图片描述
从运行结果来看,每一个客户端都分配给了一个fd值,即每一个客户端的线程都有各自的socket连接套接字。
在服务器程序中,我们可以看到,主程序中仅定义了一个线程标识符,而且仅创建了一个线程,这样就可以进行多个客户端的连接。个人理解:由于 socket的accept在while循环中,所以每有一个客户端请求连接服务器,都会生成一个新的连接套接字connectfd,而多个客户端连接上服务器后,共享一个线程的内存,各个客户端的线程之间并不是真正的并发、并行,而是线程的转换速度非常快,不断在多个客户端之间切换,(类比数码管的动态显示)。这样以来,就可以看做多个线程并行地和服务器进行通信。
所以说,在创建线程的函数pthread_create(&thread,NULL,start_routine,(void *)&connectfd) 中,我们要传递的参数只有socket的连接套接字connectfd就够了,因为数据的发送和接收函数recv/send只需要连接套接字connectfd,当然,也可以直接将客户端的地址结构体struct sockaddr_in client传递给线程,因为该结构体里面还包含着 协议族、ip地址、端口号等其他信息。要实现这个,服务器的程序可以仿照下面的程序:

    #include <stdio.h>          
    #include <strings.h>
    #include<string.h> 
    #include <stdlib.h>        
    #include <unistd.h>          
    #include <sys/types.h> 
    #include <sys/socket.h> 
    #include <netinet/in.h> 
    #include <arpa/inet.h>
    #include<pthread.h>
    #define PORT 1234 
    #define BACKLOG 1  //  队列数,即可以排队的最大连接数

    void *start_routine(void *arg); // 函数声明

// 定义一个ARG结构体,每一个线程都会定义一个ARG结构体,相当于一块内存
struct ARG {
    struct sockaddr_in client;
        int connfd;
 } ;    


void *start_routine(void *arg)
{
    struct ARG *info;
    info=(struct ARG *)arg; 

    int fd =(*info).connfd;

    send(fd,"Welcome to my server.\n",22,0); 
    printf("this is a new thread\n");
 // 发现了一个很奇怪的现象:线程中printf不加换行符\n就打印不出信息
    close(fd);
    free(arg);

}


int  main()  
{ 

    int listenfd, connectfd;    
    struct sockaddr_in server; 
    struct sockaddr_in client;      
    int sin_size; 
    sin_size=sizeof(struct sockaddr_in); 


    struct ARG *arg; //事实证明:ARG结构体放主函数里和放全局变量里并没有区别

    pthread_t thread; //定义一个线程号

    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) 
    {   
        perror("Creating socket failed.");
        exit(1);
    }


    int opt = SO_REUSEADDR;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    bzero(&server,sizeof(server));  

    server.sin_family=AF_INET; 
    server.sin_port=htons(PORT); 
    server.sin_addr.s_addr = htonl (INADDR_ANY); 

    // 绑定
    if (bind(listenfd, (struct sockaddr *)&server, sizeof(struct sockaddr)) == -1) { 
    perror("Bind error.");
    exit(1); 
    }   

    // 监听 
    if(listen(listenfd,BACKLOG) == -1){  /* calls listen() */ 
    perror("listen() error\n"); 
    exit(1); 
    } 

    while(1)
    {
        // accept 
        if ((connectfd = accept(listenfd,(struct sockaddr *)&client,&sin_size))==-1) {
        perror("accept() error\n"); 
        exit(1); 
        }

        //printf("You got a connection from %s\n",inet_ntoa(client.sin_addr) ); 

        arg=malloc(sizeof(struct ARG));
        arg->connfd=connectfd; // 连接描述符
        //这里注意:一定要指明结构体内部变量的指针!!!
        //不能直接指明结构体本身的指针
        memcpy((void *)&arg->client,&client,sizeof(client));


        pthread_create(&thread,NULL,start_routine,(void*)arg);


    }
    close(listenfd);

}


我们可以看,定义了一个结构体并分配一块内存,结构体里面包含连接套接字connectfd和户端的地址结构体struct sockaddr_in client,然后再用内存拷贝函数memcpy将各种信息拷贝至该结构体的对应地址处,用这种方法来传递给线程各个客户端的信息。

<think>我们正在使用JDK8和Netty来实现一个Socket连接管理与连接池的客户端调用工具类。这个工具类需要管理多个连接,并能够复用这些连接,以提高性能并减少资源消耗。 步骤: 1. 创建Netty客户端,用于建立与服务器的长连接。 2. 实现连接池管理,包括连接的创建、获取、释放和关闭。 3. 设计一个工具类,封装连接的获取和请求的发送,以及响应的处理。 我们将按照以下步骤进行: 一、Netty客户端初始化 二、连接池的实现 三、工具类的封装 注意:为了简化,我们假设每个连接都是相同的(即连接到同一个服务器地址和端口)。如果需要连接到不同的服务器,则需要根据不同的地址和端口创建不同的连接池。 一、Netty客户端初始化 我们需要创建一个Netty客户端,用于连接服务器。客户端将使用NioEventLoopGroup,并配置ChannelPipeline,包括编码器、解码器和业务处理器。 二、连接池的实现 连接池将管理一组连接(Channel)。当需要发送请求时,从连接池中获取一个空闲连接,使用完毕后归还。连接池需要支持以下功能: 1. 初始化一定数量的连接。 2. 获取连接:如果没有空闲连接,则创建新的连接(如果未达到最大连接数)或等待。 3. 归还连接:将使用完毕的连接放回连接池,标记为空闲状态。 4. 关闭连接池:关闭所有连接。 三、工具类的封装 提供一个工具类,封装发送请求的方法。该方法从连接池中获取连接,发送请求,并等待响应。由于Netty是异步的,我们需要将异步操作转换为同步操作(例如使用CompletableFuture)。 具体实现: 1. 定义连接池接口和实现类(例如SimpleChannelPool)。 2. 在Netty客户端处理器中,处理响应并将结果返回给调用者。 3. 在工具类中,使用连接池发送请求并获取响应。 考虑到长连接的管理,我们还需要处理连接异常断开的情况,例如重连机制。 下面我们开始逐步实现。 第一步:Netty客户端初始化 我们将创建一个Bootstrap,并设置相关参数。 第二步:连接实现 我们使用一个简单的阻塞队列来管理空闲连接,并设置最大连接数。 第三步:工具类封装 提供一个静态方法,用于发送请求。 由于代码较长,我们将分部分展示。 注意:以下代码示例是简化的,实际生产环境需要更完善的错误处理和资源管理。 首先,添加Netty依赖(假设使用Maven): ```xml <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.68.Final</version> <!-- 请使用最新稳定版本 --> </dependency> ``` 然后,我们开始编写代码。 第一部分:Netty客户端 1. 创建客户端引导类 2. 设置事件循环组、通道类型、处理器等 第二部分:连接池 1. 使用一个队列保存空闲连接(Channel) 2. 提供borrow、return和close方法 第三部分:工具类 1. 封装发送请求的方法,使用连接池获取连接,发送请求并同步等待响应。 由于Netty是异步的,我们需要在发送请求时使用CompletableFuture来等待响应。 在客户端处理器中,当收到响应时,将结果设置到CompletableFuture中。 代码结构: 1. NettyClient: 负责启动客户端连接。 2. NettyClientHandler: 处理消息和异常。 3. ConnectionPool: 连接池管理。 4. NettyClientUtils: 工具类,提供发送消息的方法。 具体实现: 1. NettyClientHandler 需要能够将响应与请求关联起来。我们可以为每个请求设置一个唯一的ID,并在发送时携带这个ID,服务器返回时也携带这个ID。这样我们就可以根据ID将响应与请求对应起来。 但是,为了简化,我们假设每次发送请求后,连接上只会有一次响应,并且我们使用一个CompletableFuture来等待这个响应。因此,每个连接在同一时间只能处理一个请求。这显然不是高效的,所以我们可以使用一个请求ID映射来支持同一个连接上的多个请求。 为了简化,我们这里每个连接同一时间只处理一个请求(即获取连接后,发送请求,等待响应,然后归还连接)。这样就不需要请求ID映射,但会限制连接的并发能力。 另一种方法是:在连接上支持多个请求,这需要为每个请求分配一个唯一ID,并在处理器中维护一个请求ID到CompletableFuture的映射。当收到响应时,根据ID找到对应的CompletableFuture并完成它。 我们采用第二种方法,因为它更高效。 因此,我们需要: 1. 在NettyClientHandler中维护一个Map<Long, CompletableFuture<Object>>,用于存储请求ID和对应的CompletableFuture。 2. 在发送请求时,生成一个唯一的请求ID,并将这个ID和CompletableFuture存入Map,同时将ID放入请求消息中。 3. 当收到响应时,从响应中解析出请求ID,然后从Map中取出对应的CompletableFuture并完成,然后移除该条目。 这里,我们假设请求和响应消息都是自定义协议,并且包含请求ID字段。 为了简化协议设计,我们假设消息格式如下: 请求:4字节长度(不包括自身) + 8字节请求ID + 实际数据 响应:4字节长度(不包括自身) + 8字节请求ID + 实际数据 但是,为了专注于连接池和工具类的实现,我们简化消息格式,只传递字符串,并假设请求和响应都是字符串,且请求ID由客户端生成并管理。 因此,我们定义自己的编码器和解码器。 由于时间有限,我们简化消息格式,使用如下: 请求:请求ID(long) + 请求内容(String) 响应:请求ID(long) + 响应内容(String) 使用Netty的LengthFieldBasedFrameDecoder来处理粘包拆包问题。 具体编码解码器: 编码器:将请求消息编码为字节流,格式为:长度(4字节,表示后面内容的长度) + 请求ID(8字节) + 内容(UTF-8编码的字节) 解码器:使用LengthFieldBasedFrameDecoder,然后解析出请求ID和内容。 但是,我们这里不深入协议细节,而是重点在连接池和工具类上。因此,我们假设已经有一个简单的字符串协议,即发送字符串,并返回字符串。请求ID由我们自己在发送前添加到消息中,并在收到响应时解析出来。 然而,为了支持多请求复用同一个连接,我们必须使用包含请求ID的协议。 因此,我们定义如下协议: 请求消息: long: 请求ID (8字节) int: 数据长度 (4字节) byte[]: 数据 响应消息同样结构。 编码器:将请求ID和内容编码成上述格式。 解码器:先读取8字节的请求ID,再读取4字节长度,然后读取指定长度的数据。 但是,由于Netty的LengthFieldBasedFrameDecoder可以处理长度字段,我们可以这样设计: 消息格式: [总长度4字节][请求ID8字节][数据] 其中,总长度=8字节(请求ID)+数据长度 这样,解码器可以这样设置: new LengthFieldBasedFrameDecoder(1024*1024, 0, 4, 0, 4) // 长度字段在0位置,4字节,然后跳过4字节(因为长度字段只表示后面内容的长度,不包括自身),所以需要调整 但是,这样设计有点复杂。 另一种简单设计:使用LengthFieldPrepender(编码时添加长度头)和LengthFieldBasedFrameDecoder(解码时根据长度头拆包),然后我们在其后添加自定义的处理器。 我们使用: - 编码器:LengthFieldPrepender(4) // 在消息前面添加4字节长度(表示后面内容的长度,不包括这4字节) - 解码器:LengthFieldBasedFrameDecoder(1048576, 0, 4, 0, 4) // 最大长度1M,长度字段在0位置,4字节,然后去掉4字节的长度字段 这样,经过这两个处理器后,我们得到的是完整的消息(不包含长度字段)。然后,我们再添加自定义的编解码器,将消息转换为我们的自定义消息对象(包含请求ID和内容)。 自定义消息对象: ```java public class RequestMessage { private long requestId; private String content; // getter and setter } ``` 自定义编码器:将RequestMessage编码为:long(请求ID)+ int(内容长度)+ byte[](内容) 自定义解码器:读取long(请求ID),再读取int(内容长度),然后读取内容。 但是,由于时间关系,我们简化:我们只发送字符串,并且请求ID由我们自己管理,在发送时添加到消息中,在接收时解析。同时,我们使用一个简单的映射来存储请求ID和CompletableFuture。 下面开始代码实现。 由于代码量较大,我们只展示关键部分。 1. 定义消息对象(包含请求ID和内容) 2. 自定义编码器和解码器 3. 客户端处理器 步骤: 一、创建请求和响应消息的基类(这里为了简化,请求和响应使用同一格式) 二、自定义编码器:将消息对象编码为字节流 三、自定义解码器:将字节流解码为消息对象 四、客户端处理器:处理响应消息,将响应传递给对应的CompletableFuture 由于我们关注的是连接池和工具类,因此我们简化编解码:使用字符串,并且请求ID在消息头。 我们使用以下协议: - 发送:请求ID(long) + 内容长度(int) + 内容(byte[]) - 接收:同样结构 自定义编码器: ```java public class RequestEncoder extends MessageToByteEncoder<RequestMessage> { @Override protected void encode(ChannelHandlerContext ctx, RequestMessage msg, ByteBuf out) throws Exception { out.writeLong(msg.getRequestId()); byte[] bytes = msg.getContent().getBytes(StandardCharsets.UTF_8); out.writeInt(bytes.length); out.writeBytes(bytes); } } ``` 自定义解码器: ```java public class ResponseDecoder extends ByteToMessageDecoder { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { if (in.readableBytes() < 12) { // long(8) + int(4) return; } in.markReaderIndex(); long requestId = in.readLong(); int length = in.readInt(); if (in.readableBytes() < length) { in.resetReaderIndex(); return; } byte[] contentBytes = new byte[length]; in.readBytes(contentBytes); String content = new String(contentBytes, StandardCharsets.UTF_8); ResponseMessage response = new ResponseMessage(); response.setRequestId(requestId); response.setContent(content); out.add(response); } } ``` 客户端处理器: ```java public class NettyClientHandler extends SimpleChannelInboundHandler<ResponseMessage> { private final ConcurrentHashMap<Long, CompletableFuture<String>> pendingRequests = new ConcurrentHashMap<>(); public void addPendingRequest(long requestId, CompletableFuture<String> future) { pendingRequests.put(requestId, future); } @Override protected void channelRead0(ChannelHandlerContext ctx, ResponseMessage msg) throws Exception { CompletableFuture<String> future = pendingRequests.remove(msg.getRequestId()); if (future != null) { future.complete(msg.getContent()); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { // 处理异常 cause.printStackTrace(); ctx.close(); } } ``` 然后,在创建连接时,我们将这些处理器添加到pipeline中。 接下来,我们实现连接池。 连接池接口: ```java public interface ConnectionPool { Channel borrowChannel() throws Exception; void returnChannel(Channel channel); void close(); } ``` 简单实现: ```java public class SimpleConnectionPool implements ConnectionPool { private final BlockingQueue<Channel> idleConnections; private final List<Channel> allConnections = new ArrayList<>(); private final int maxConnections; private final Bootstrap bootstrap; private final String host; private final int port; public SimpleConnectionPool(Bootstrap bootstrap, String host, int port, int maxConnections) { this.bootstrap = bootstrap; this.host = host; this.port = port; this.maxConnections = maxConnections; idleConnections = new ArrayBlockingQueue<>(maxConnections); } @Override public synchronized Channel borrowChannel() throws Exception { Channel channel = idleConnections.poll(); if (channel != null) { return channel; } if (allConnections.size() < maxConnections) { // 创建新连接 ChannelFuture future = bootstrap.connect(host, port).sync(); channel = future.channel(); allConnections.add(channel); return channel; } // 等待空闲连接 return idleConnections.take(); } @Override public void returnChannel(Channel channel) { if (channel != null && channel.isActive()) { idleConnections.offer(channel); } } @Override public void close() { for (Channel channel : allConnections) { if (channel.isActive()) { channel.close(); } } idleConnections.clear(); allConnections.clear(); } } ``` 注意:这个连接实现是简化的,没有处理连接失效的情况。在实际应用中,需要检查连接是否有效,如果无效则移除并创建新的连接。 最后,我们实现工具类,用于发送请求: ```java public class NettyClientUtils { private static final AtomicLong REQUEST_ID = new AtomicLong(0); private static ConnectionPool connectionPool; public static void init(String host, int port, int maxConnections) throws Exception { EventLoopGroup group = new NioEventLoopGroup(); Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new LengthFieldPrepender(4)); pipeline.addLast(new LengthFieldBasedFrameDecoder(1048576, 0, 4, 0, 4)); pipeline.addLast(new RequestEncoder()); pipeline.addLast(new ResponseDecoder()); pipeline.addLast(new NettyClientHandler()); } }); connectionPool = new SimpleConnectionPool(bootstrap, host, port, maxConnections); } public static String sendRequest(String message) throws Exception { Channel channel = connectionPool.borrowChannel(); long requestId = REQUEST_ID.incrementAndGet(); CompletableFuture<String> future = new CompletableFuture<>(); // 获取处理器 NettyClientHandler handler = channel.pipeline().get(NettyClientHandler.class); handler.addPendingRequest(requestId, future); RequestMessage request = new RequestMessage(); request.setRequestId(requestId); request.setContent(message); channel.writeAndFlush(request); String response = future.get(); // 同步等待 connectionPool.returnChannel(channel); return response; } public static void close() { connectionPool.close(); } } ``` 注意:上面的工具类中,init方法需要在使用前调用一次。sendRequest方法是同步的,会阻塞直到响应返回。 以上代码是一个简化的实现,实际生产环境中需要考虑更多因素,例如: 1. 连接的健康检查 2. 超时处理(future.get()可以设置超时) 3. 异常处理 4. 连接重连 5. 资源释放 由于时间有限,我们只提供了一个基础框架。 总结:我们通过Netty建立了长连接,并实现连接池来管理这些连接。工具类封装了发送请求和接收响应的过程,并通过请求ID实现了异步转同步,同时支持同一个连接上的多个请求。 相关问题: 1. 如何优化Netty客户端的性能? 2. 连接池中如何处理失效的连接? 3. 如何实现客户端的负载均衡? 4. 如何处理Netty客户端的重连机制? 5. 如何设计更高效的协议来减少网络开销?</think>基于 JDK8 和 Netty 实现 Socket连接管理与连接池的客户端工具类,需结合连接复用、异步通信和资源管理等技术。以下是分步实现方案: ### 一、核心设计 1. **连接池管理** - 使用 `ChannelPool` 实现连接复用,避免频繁创建连接 - 设置最大连接数和空闲超时时间 - 健康检查机制(心跳包) 2. **异步通信模型** - 通过 `CompletableFuture` 实现异步请求-响应映射 - 使用 `AttributeKey` 绑定请求ID与响应关系 3. **协议设计** - 自定义协议头包含请求ID、数据长度等字段 - 示例协议格式: ``` [4字节长度][8字节请求ID][数据内容] ``` ### 二、关键代码实现 #### 1. 连接实现 ```java public class NettyConnectionPool { private final EventLoopGroup workerGroup = new NioEventLoopGroup(); private final Bootstrap bootstrap; private final GenericObjectPool<Channel> pool; public NettyConnectionPool(String host, int port, int maxConnections) { bootstrap = new Bootstrap() .group(workerGroup) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) { ch.pipeline() .addLast(new LengthFieldPrepender(4)) .addLast(new LengthFieldBasedFrameDecoder(1048576, 0, 4, 0, 4)) .addLast(new ClientHandler()); } }); pool = new GenericObjectPool<>(new BasePooledObjectFactory<>() { @Override public Channel create() throws Exception { return bootstrap.connect(host, port).sync().channel(); } @Override public PooledObject<Channel> wrap(Channel channel) { return new DefaultPooledObject<>(channel); } }); pool.setMaxTotal(maxConnections); pool.setTestOnBorrow(true); // 借出时验证连接 } public Channel borrowChannel() throws Exception { return pool.borrowObject(); } public void returnChannel(Channel channel) { if (channel.isActive()) { pool.returnObject(channel); } else { pool.invalidateObject(channel); } } } ``` #### 2. 客户端处理器(处理响应) ```java public class ClientHandler extends SimpleChannelInboundHandler<ByteBuf> { private static final AttributeKey<Map<Long, CompletableFuture<String>>> REQUEST_MAP_KEY = AttributeKey.newInstance("requestMap"); @Override public void channelActive(ChannelHandlerContext ctx) { ctx.channel().attr(REQUEST_MAP_KEY).set(new ConcurrentHashMap<>()); } @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) { long requestId = msg.readLong(); String response = msg.toString(StandardCharsets.UTF_8); CompletableFuture<String> future = ctx.channel() .attr(REQUEST_MAP_KEY) .get() .remove(requestId); if (future != null) { future.complete(response); } } } ``` #### 3. 工具类封装 ```java public class NettyClientUtil { private static final AtomicLong REQUEST_ID_GEN = new AtomicLong(0); private static NettyConnectionPool pool; public static void init(String host, int port, int maxConnections) { pool = new NettyConnectionPool(host, port, maxConnections); } public static CompletableFuture<String> sendRequest(String message) throws Exception { Channel channel = pool.borrowChannel(); long requestId = REQUEST_ID_GEN.incrementAndGet(); CompletableFuture<String> future = new CompletableFuture<>(); // 绑定请求ID与Future channel.attr(ClientHandler.REQUEST_MAP_KEY) .get() .put(requestId, future); // 构建协议数据 ByteBuf buf = Unpooled.buffer(); buf.writeLong(requestId); buf.writeBytes(message.getBytes(StandardCharsets.UTF_8)); channel.writeAndFlush(buf).addListener(f -> { pool.returnChannel(channel); }); return future; } } ``` ### 三、使用示例 ```java // 初始化连接池 NettyClientUtil.init("127.0.0.1", 8080, 10); // 发送请求 CompletableFuture<String> response = NettyClientUtil.sendRequest("Hello Server"); response.thenAccept(res -> System.out.println("收到响应: " + res)); ``` ### 四、关键技术点 1. **连接复用** - 通过 Apache Commons Pool 管理 `Channel` 对象 - `borrowObject/returnObject` 控制连接生命周期 2. **异步响应映射** - 使用 `AttributeKey` 在 Channel 上绑定 `ConcurrentHashMap<Long, CompletableFuture>` - 请求ID全局唯一,确保响应正确匹配 3. **健壮性设计** - 心跳机制:通过 `IdleStateHandler` 定时发送心跳包 - 异常处理:`exceptionCaught` 中销毁无效连接 - 超时控制:`CompletableFuture.orTimeout(5, TimeUnit.SECONDS)` > **注意事项** > 1. 需添加依赖:`netty-all` 和 `commons-pool2` > 2. 生产环境需增加 SSL/TLS 加密支持 > 3. 建议使用 `EpollEventLoopGroup` 提升 Linux 性能[^2] --- ### 相关问题 1. 如何实现 Netty 客户端的心跳检测机制? 2. 连接池中的连接失效时如何自动重建? 3. 如何优化 Netty 的 ByteBuf 内存管理避免内存泄漏? 4. Netty 如何支持自定义协议编解码器? 5. 在高并发场景下如何调整 Netty 的线程模型?[^2] [^1]: Netty 是事件驱动的异步网络框架,适用于高性能网络应用开发 [^2]: 通过优化线程模型和连接复用可提升并发处理能力
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值