• 网络编程中的并发控制


    在Linux网络编程中,一般建立在两端之间,服务器端和客户端。客户端是面向用户的应用,而服务器端要处理客户端所提出的请求。通常一个服务器要面向多个客户端,保证对每个客户端都能高效的处理,这时候需要并发操作。实现并发控制的方法有两个,一个是并发服务器,另一个是多路复用I/O,现在就给大家介绍一下这两种方法。

    方法一:并发服务器

    这个方法可以通过进程(线程)来实现,主要根据子进程(子线程)之间并行运行的特点。将对客户端请求的处理工作,交于子进程(子线程)来处理,达到一个服务器同时处理多个客户端的效果。通过2个例子实现一个简单的服务器与客户端的一对多。

    例1:进程实现并发服务器(TCP通信)

    首先,服务器端代码如下:

    #include

    #include

    #include

    #include

    #include

    #include

    #include

    int main()

    {

    int sockfd, newfd, r;

    struct sockaddr_in myaddr;

    struct sockaddr fromaddr;

    socklen_t len = 16;

    char buf[100] = {0};

    pid_t pid;

    sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建TCP通信的套接字--流式套接字

    myaddr.sin_family = AF_INET; // 地址信息填写

    myaddr.sin_port = htons(56666); // 要绑定的端口号

    myaddr.sin_addr.s_addr = inet_addr("127.0.0.1");// 要绑定的地址 这里以 127.0.0.1为例

    r = bind(sockfd, (struct sockaddr *)&myaddr, sizeof(myaddr)); // 绑定地址信息

    if( listen(sockfd, 10) < 0){ // 设置监听 同一时刻能客户端的连接请求的大数

    perror("listen ");return -1;

    }

    while(1) { // 循环

    newfd = accept(sockfd, &fromaddr, &len); // 阻塞接收 客户端的连接请求

    pid = fork(); // 创建新进程

    if(pid == 0){ // 子进程 处理以连接成功的客户端

    while(1){

    r = recv(newfd, buf, 100, 0); //处理客户端 接收信息

    if(r <= 0){ printf("客户端已退出:%d \n",newfd);break; }

    printf("%d : %s\n", newfd, buf);

    bzero(buf, strlen(buf));

    }

    close(newfd); // 关闭 连接

    exit(0); // 处理完 子进程退出

    }else if(pid < 0){ exit(0); }

    }

    close(sockfd);

    }

    客户端代码如下:

    int main()

    {

    int sockfd,r;

    char buf[100] = {0};

    struct sockaddr_in toaddr;

    sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建TCP通信的套接字--流式套接字

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

    toaddr.sin_family = AF_INET; // 地址信息填写

    toaddr.sin_port = htons(56666); // 对方的端口号

    toaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 对方的IP地址

    // 发送连接请求 与对方建立连接

    r = connect(sockfd, (struct sockaddr *)&toaddr, sizeof(toaddr));

    if(r == -1){ perror("connect "); return -1; }

    printf("connect OK\n"); // 连接成功

    while(1){ // 循环 向服务器端发送信息

    scanf("%s", buf);

    send(sockfd, buf, strlen(buf), 0);

    }

    close(sockfd);

    }

    然后,编译服务器端 和 客户端,终端执行如图1命令:

    图1 编译文件

    用一个终端执行服务器,多个终端执行客户端,结果如图2:

    左边第一个是服务器端,先开启;右边2个是客户端,同时访问服务器;服务器能同时处理这些客户端。

    图2 执行结果

    例2:线程实现并发服务器(TCP通信)

    首先,服务器端代码如下:

    ……

    #include

    void * fun(void *p) // 线程处理函数

    {

    int fd = *((int *)p); // 获取传参 得到 套接字描述符

    char buf[100] = {0};

    int r;

    printf("pthread fd = %d start\n", fd);

    while(1){ //循环 接受信息

    r = recv(fd, buf, 100, 0);

    if(r <= 0){ printf("客户端已退出 : %d\n",fd); break; }

    printf("%d : %s\n", fd, buf);

    bzero(buf,strlen(buf));

    }

    close(fd); // 关闭套接字 线程结束

    }

    int main()

    {

    int sockfd, newfd, r;

    struct sockaddr_in myaddr;

    struct sockaddr fromaddr;

    socklen_t len = 16;

    char buf[100] = {0};

    pthread_t tid;

    sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建TCP通信的套接字--流式套接字

    myaddr.sin_family = AF_INET; // 地址信息填写

    myaddr.sin_port = htons(56666); // 端口号

    myaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // IP地址

    r = bind(sockfd, (struct sockaddr *)&myaddr, sizeof(myaddr)); // 绑定

    if( listen(sockfd, 10) < 0) { perror("listen "); return -1; } //监听

    while(1){ // 进程 循环接受客户端的请求

    newfd = accept(sockfd, &fromaddr, &len); // 阻塞 接受 并建立连接

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

    pthread_create(&tid, NULL, fun, &newfd); //创建线程 将连接好的套接字传给线程

    }

    close(sockfd);

    }

    客户端代码,同例1中客户端代码。

    编译服务器和客户端,终端执行命令,如图3:注意线程编译时加载库。

    图3 gcc编译

    用一个终端执行服务器,多个终端执行客户端,结果如图4。左边第一个是服务器端,先开启;右边2个是客户端,同时访问服务器;服务器为客户端创建线程,同时处理这些客户端。

    图4 执行结果

    方法二:多路复用I/O

    基本思想:有一个存储文件描述符的表,有固定的函数(select)可以检测表中的文件描述符状态,当这些文件描述符中的一个或多个已准备好进行I/O时函数才返回。

    函数返回时告诉进程那个描述符已就绪,可以进行I/O操作。

    解决问题:多进程(多线程)情况下程序的复杂性较高,阻塞模式/非阻塞模式下效率低。IO多路复用是更好的方法,逻辑简单、效率高。

    IO多路复用涉及函数 :第一:select函数 功能:检测表中文件描述符的状态

    函数原型 : #include #include #include

    int select(int n, fd_set * read_fds, fd_set *write_fds, fd_set *except_dst, struct timeval *timeout);

    n : 文件描述符大值+1

    read_fds : 所有读文件描述符的集合

    write_fds : 写 文件描述符集合

    except_fds : 其他的 文件描述符集合

    timeout : 阻塞等待的时间 毫秒

    struct timeval t = {5, 600}; &t 5.6秒

    NULL/0 无限等待

    struct timeval t = {0, 0}; &t 0秒 不等待

    返回值 : 就绪描述符的数目

    超时返回 0

    失败返回 -1

    第二:文件描述符操作函数(宏定义)

    void FD_SET(int fd, fd_set *fds); 将文件描述符 添加到 表中

    void FD_CLR(int fd, fd_set *fds); 删除 一个文件描述符

    void FD_ZERO(fd_set *fds); 清零

    int FD_ISSET(int fd, fd_set *fds); 判断 fd 是否已经准备I/O

    服务器端可以采用多路IO复用实现一对多处理,代码如下:

    ……

    #include

    int main()

    {

    int sockfd, newfd, r, i, maxfd;

    struct sockaddr_in myaddr;

    struct sockaddr fromaddr;

    socklen_t len = 16;

    char buf[100] = {0};

    fd_set fds;

    sockfd = socket(AF_INET, SOCK_STREAM, 0);// 创建TCP通信的套接字--流式套接字

    myaddr.sin_family = AF_INET; // 地址信息填写

    myaddr.sin_port = htons(56667); // 端口号

    myaddr.sin_addr.s_addr = inet_addr("127.0.0.1");// IP地址

    r = bind(sockfd, (struct sockaddr *)&myaddr, sizeof(myaddr)); // 绑定

    if( listen(sockfd, 10) < 0){ //监听

    perror("listen "); return -1; }

    FD_ZERO(&fds); // 清空表

    FD_SET(sockfd, &fds); // 添加 套接字描述符 到表中

    maxfd = sockfd; // 记录 描述符 的 大值

    while(1){

    // 阻塞 等待是否 有访问到来

    r = select(maxfd+1, &fds, NULL, NULL, NULL);

    if(r <=0){ return -1; }

    for(i = 0; i <= maxfd;i++){

    if(FD_ISSET(i, &fds)) { //找出 I/O操作的套接字描述符

    if(i == sockfd){ // 客户端 发送 连接请求

    newfd = accept(i, &fromaddr, &len); // 接受 并建立连接

    printf("newfd = %d start\n", newfd);

    FD_SET(newfd, &fds); // 将新套接字描述符 添加到表中

    maxfd = maxfd > newfd ? maxfd : newfd; // 更新 大值

    }

    else{ // 客户端 接收/发送 信息

    r = recv(i, buf, 100, 0);

    if(r <= 0){

    close(i);

    FD_CLR(i, &fds); // 从表中删除该套接字

    }else {

    send(i, buf, strlen(buf), 0);

    printf("%d : %s\n", i, buf);

    bzero(buf, strlen(buf));

    }

    }}}}}

    客户端代码如下:

    int main()

    {

    int sockfd,r;

    char buf[100] = {0};

    struct sockaddr_in toaddr;

    sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建TCP通信的套接字--流式套接字

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

    toaddr.sin_family = AF_INET; // 地址信息填写

    toaddr.sin_port = htons(56667); // 对方的端口号

    toaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 对方的IP地址

    // 发送连接请求 与对方建立连接

    r = connect(sockfd, (struct sockaddr *)&toaddr, sizeof(toaddr));

    if(r == -1){ perror("connect "); return -1; }

    printf("connect OK\n"); // 连接成功

    scanf("%s", buf);

    send(sockfd, buf, strlen(buf), 0); //向服务器端发送信息

    bzero(buf, strlen(buf));

    recv(sockfd, buf, 100, 0); //收取对方的回发信息

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

    close(sockfd);

    }

    然后,gcc编译服务器端和客户端,分别生成可执行文件,在不同终端执行(左边第一个为服务器端,之后的是客户端),执行后结果如图5所示:

    图5 执行结果图

    在多路复用I/O中例子中,服务器端用的是for循环依次遍历描述符表,所以造成后面客户端的等待问题。

    以上就是在网络编程中常用的并发操作,希望可以为你提供一定的帮助。

    嵌入式物联网需要学的东西真的非常多,千万不要学错了路线和内容,导致工资要不上去!

    无偿分享大家一个资料包,差不多150多G。里面学习内容、面经、项目都比较新也比较全!某鱼上买估计至少要好几十。(点击找小助理领取)

  • 相关阅读:
    防静电门禁闸机管理系统的优点有哪些
    【网络】抓包工具Wireshark下载安装和基本使用教程
    爬虫 | 【实践】Best Computer Science Scientists数据爬取
    算法学习和实践 0.算法基础
    搞笑短视频如何撰写脚本?分享简单小技巧
    KF-GINS 和 OB-GINS 的 Earth类 和 Rotation 类
    含重复元素取不重复子集[如何取子集?如何去重?]
    深度学习基础网络整理----AlexNet
    C. The Third Problem Codeforces Round #804 (Div. 2)
    nmap的用法大全
  • 原文地址:https://blog.csdn.net/m0_70888041/article/details/127671116