Linux Socket编程(二):多线程实现同时响应多个客户端

服务端可以同时对多个客户端的请求做出响应吗?

上一篇文章:Linux Socket编程(一):简单地实现进程间TCP通信
在上一篇文章中讲述了Linux下通过socket简单地实现进程间TCP通信。现在做一个简单的测试:打开一个服务端,同时打开两个客户端请求与服务端连接。由第一张图可以看到,服务端只能对先连接上的客户端做出响应;第二张图中线连接上的客户端进程关闭,服务端马上响应了与后连接上的客户端之前发送的请求。因此,上一篇文章中实现的客户端并不能同时对多个客户端的请求做出响应。
在这里插入图片描述在这里插入图片描述
上一篇文章中讲到当服务端调用listen()函数后,服务端套接字将处于监听状态,随时准备接收客户端发来的连接请求。此后,服务端调用accept()函数从处于 established 状态的连接队列头部取出一个已经完成的连接,如果这个队列没有已经完成的连接,accept()函数就会阻塞当前线程,直到取出队列中已完成的客户端连接为止。在上面的测试中,有两个客户端先后调用connect()函数,通过Linux内核自动与服务端完成TCP三次握手连接,完成的TCP连接先后进入服务端套接字的连接队列。此时,被accept()函数阻塞的服务端会取出先完成的连接,进行数据的交换,直到该连接断开,再重新调用accept()函数,去取出连接队列中下一个完成的连接。

服务端多线程实现

代码与之前基本没变,将服务端处理客户端请求的内容放在requestHandling()函数中,该函数传入被accept()的客户端的套接字和套接字地址。然后利用requestHandling()函数名创建线程,传入相应参数,并将执行客户端相应的子线程从主线程分离,不阻塞主线程,主线程可以继续运行,去accept()连接队列中下一个完成的连接。


#include <iostream>
#include <stdio.h>
#include <cstring>       // void *memset(void *s, int ch, size_t n);
#include <sys/types.h>   // 数据类型定义
#include <sys/socket.h>  // 提供socket函数及数据结构sockaddr
#include <arpa/inet.h>   // 提供IP地址转换函数,htonl()、htons()...
#include <netinet/in.h>  // 定义数据结构sockaddr_in
#include <ctype.h>       // 小写转大写
#include <unistd.h>      // close()、read()、write()、recv()、send()...
#include <thread>		 // c++11 thread类
using namespace std;

void requestHandling(const int client_sockfd, const struct sockaddr_in& client_addr);

const int flag = 0; // 0表示读写处于阻塞模式
const int port = 8080;
const int buffer_size = 1<<20;

int main(int argc, const char* argv[]){
    // 创建服务器监听的套接字。Linux下socket被处理为一种特殊的文件,返回一个文件描述符。
    // int socket(int domain, int type, int protocol);
    // domain设置为AF_INET/PF_INET,即表示使用ipv4地址(32位)和端口号(16位)的组合。
    int server_sockfd = socket(PF_INET,SOCK_STREAM,0);  
    if(server_sockfd == -1){
        close(server_sockfd);
        perror("socket error!");
    }
    // /* Enable address reuse */
    // int on = 1;
    // int ret = setsockopt( server_sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) );

    // 此数据结构用做bind、connect、recvfrom、sendto等函数的参数,指明地址信息。
    struct sockaddr_in server_addr;
    memset(&server_addr,0,sizeof(server_addr)); // 结构体清零
    server_addr.sin_family = AF_INET;  // 协议
    server_addr.sin_port = htons(port);  // 端口16位, 此处不用htons()或者错用成htonl()会连接拒绝!!
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 本地所有IP
    // 另一种写法, 假如是127.0.0.1
    // inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr.s_addr);


    // int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 
    // bind()函数的主要作用是把ip地址和端口绑定到套接字(描述符)里面
    // struct sockaddr是通用的套接字地址,而struct sockaddr_in则是internet环境下套接字的地址形式,二者长度一样,都是16个字节。二者是并列结构,指向sockaddr_in结构的指针也可以指向sockaddr。
    // 一般情况下,需要把sockaddr_in结构强制转换成sockaddr结构再传入系统调用函数中。
    if(bind(server_sockfd,(struct sockaddr*)&server_addr,sizeof(server_addr)) == -1){
        close(server_sockfd);
        perror("bind error");
    }
    // 第二个参数为相应socket可以排队的准备道来的最大连接个数
    if(listen(server_sockfd, 5) == -1){
        close(server_sockfd);
        perror("listen error");
    }

    printf("Listen on port %d\n", port);
    while(1){
        struct sockaddr_in client_addr;
        socklen_t client_len = sizeof(client_addr);
        // accept()函数从处于established状态的连接队列头部取出一个已经完成的连接,
        // 如果这个队列没有已经完成的连接,accept()函数就会阻塞当前线程,直到取出队列中已完成的客户端连接为止。
        int client_sockfd = accept(server_sockfd, (struct sockaddr*)&client_addr, &client_len);

        
        // 为完成的连接新建立一个线程
        thread tcpThread(requestHandling, client_sockfd, client_addr);
        tcpThread.detach(); // 不阻塞主线程,执行tcp通讯的线程从线程对象分离
    }
    close(server_sockfd);

    return 0;
}

void requestHandling(const int client_sockfd, const struct sockaddr_in& client_addr){
    char ipbuf[128];
    printf("Connect client iP: %s, port: %d\n", inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ipbuf, 
        sizeof(ipbuf)), ntohs(client_addr.sin_port));

    // 实现客户端发送小写字符串给服务端,服务端将小写字符串转为大写返回给客户端
    char buf[buffer_size];
    while(1) {
        // read data, 阻塞读取
        int len = recv(client_sockfd, buf, sizeof(buf),flag);
        if (len == -1) {
            close(client_sockfd);
            // close(server_sockfd);
            perror("read error");
        }else if(len == 0){  // 这里以len为0表示当前处理请求的客户端断开连接
            break;
        }
        printf("Recvive from client iP: %s, port: %d, str = %s\n", inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ipbuf, 
        sizeof(ipbuf)), ntohs(client_addr.sin_port),buf);
        // 小写转大写
        for(int i=0; i<len; ++i) {
            buf[i] = toupper(buf[i]);
        }
        printf("Send to client iP: %s, port: %d, str = %s\n",inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ipbuf, 
        sizeof(ipbuf)), ntohs(client_addr.sin_port), buf);

        // 大写串发给客户端
        if(send(client_sockfd, buf, strlen(buf),flag) == -1){
            close(client_sockfd);
            // close(server_sockfd);
            perror("write error");
        }
        memset(buf,'\0',len); // 清空buf
    }
    close(client_sockfd);
    printf("Disconnect client iP: %s, port: %d\n", inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ipbuf, 
        sizeof(ipbuf)), ntohs(client_addr.sin_port));
}

运行测试

在这里插入图片描述

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Linux网络编程中,使用多线程进行收发操作可以提高网络应用程序的并发性能。下面是一个简单的示例代码,说明如何使用多线程进行收发操作。 ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h> #include <sys/socket.h> #include <arpa/inet.h> #define MAX_CLIENTS 10 void *handle_client(void *arg) { int client_fd = *(int *)arg; char buffer[1024]; memset(buffer, 0, sizeof(buffer)); // 接收客户端数据 ssize_t recv_len = recv(client_fd, buffer, sizeof(buffer), 0); if (recv_len > 0) { printf("Received message from client: %s\n", buffer); } // 发送响应客户端 char response[] = "Hello from server!"; ssize_t send_len = send(client_fd, response, sizeof(response), 0); if (send_len == -1) { perror("send"); } close(client_fd); pthread_exit(NULL); } int main() { int server_fd; struct sockaddr_in server_addr; struct sockaddr_in client_addr; socklen_t client_addr_len = sizeof(client_addr); pthread_t threads[MAX_CLIENTS]; // 创建套接字 server_fd = socket(AF_INET, SOCK_STREAM, 0); if (server_fd == -1) { perror("socket"); exit(1); } // 设置服务器地址 server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(8080); // 绑定套接字到服务器地址 if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) { perror("bind"); exit(1); } // 监听连接 if (listen(server_fd, MAX_CLIENTS) == -1) { perror("listen"); exit(1); } printf("Server started. Listening on port 8080...\n"); // 接受并处理客户端请求 int i = 0; while (1) { int client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len); if (client_fd == -1) { perror("accept"); continue; } // 创建线程处理客户端请求 if (pthread_create(&threads[i], NULL, handle_client, &client_fd) != 0) { perror("pthread_create"); continue; } // 分离线程 pthread_detach(threads[i]); i++; if (i >= MAX_CLIENTS) { break; } } close(server_fd); return 0; } ``` 在上面的示例代码中,首先创建了一个服务器套接字,然后绑定到指定的IP地址和端口号。接着,使用`listen`函数监听来自客户端的连接请求。 在主循环中,通过调用`accept`函数接受客户端连接,并且为每个客户端连接创建一个新的线程来处理收发操作。`handle_client`函数是在线程中执行的任务,负责接收客户端数据并发送响应客户端。 需要注意的是,在多线程编程中,要确保网络资源的正确管理和共享,避免竞争条件和死锁等问题的出现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值