Linux系统的服务器并发编程

在网络通信中,大部分情况都会使用TCP协议的网络通信方式,毕竟TCP协议的通信方式安全性高、数据不易丢失、还能远距离传输数据。但TCP协议并不能像UDP协议一样本身就具有并发特性,所以在编写TCP协议网络通信中,通常采用多路复用和多线程/多进程的方式实现服务器的并发,即一个服务器同时服务多个客户端。

多路复用

多路复用的相关函数会在程序中有注释。实现多路复用的程序如下:
头文件com.h

#ifndef     TCP_STRUCT_TEST_H
#define     TCP_STRUCT_TEST_H
#define SERV_PORT  8900
/*网络中绝不发送浮点型数据
*/
struct msg_struct {
    char msgtype;
    int temp;
    int humidty;
    short cnt;
    char des[64];
} __attribute__((packed));      /*告诉编译器, 不要对该结构进行任何优化 取消对齐*/
#endif

服务器程序server.h


#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include "comm.h"
/*
多路复用的实现:用函数select
    int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
    注:fd是文件描述符,是socketaccept函数的返回值
        nfds:所有fd中数字最大的那个+1         
        readfds:读文件的集合。如果你关注某个文件/socket为可读事件,将其加入该集合
        writefds:写文件的集合。如果你关注某个文件/socket为可写事件,将其加入该集合
        exceptfds:异常的集合。如果你关注某个文件/socket为异常事件,将其加入该集合
    注意:集合是双向的参数, 你在调用之前将需要监控的fd加入set,当select返回的时候, 里面的原始数据被抹掉.只保留了发生事件的fd。
        timeout:超时时间
            当所监控的fd集合,超过一定的时间,仍然没有任何fd发生事件也退出
            NULL:永久等待
            timeout.sec=timeout.usec=0,表示非阻塞监控
            timeout.sec = 5;指定超时时间
        返回值:
            -1:失败,并设置errno的值
            >0:表示发生事件fd的个数
            ==0:没有fd发生事件, 超时了 
    void FD_CLR(int fd, fd_set *set);       //将fd从set中踢掉
    int  FD_ISSET(int fd, fd_set *set);     //判断fd是否在set中
    void FD_SET(int fd, fd_set *set);       //将fd加入到set中去
    void FD_ZERO(fd_set *set);              //清空set
*/
/*  监控的读集合  */
fd_set  readfdset;
fd_set  readfdset_backup;
int max;
int socket_fds[64];
int socket_top  = 0;
int serv_do_client_something(int socket_client)//事件发生后要执行的事情
{
    int ret;
    struct msg_struct msg_serv;
    memset(&msg_serv,0,sizeof(msg_serv));
    ret = recv(socket_client,&msg_serv,sizeof(msg_serv),0);
    if(ret <0){
        perror("recv err:");
        return -17;
    }else if(ret == 0){
        printf("peer close it\n");
        close (socket_client);
        return 0;
    }
    printf("serv get data:temp%d hud%d cnt%d des:%s\n",ntohl(msg_serv.temp),ntohl(msg_serv.humidty),ntohs(msg_serv.cnt),msg_serv.des);
    msg_serv.cnt = ntohs(msg_serv.cnt);
    msg_serv.cnt++;
    msg_serv.cnt = htons(msg_serv.cnt);
    strcpy(msg_serv.des,"your msg is done");
    ret = send(socket_client,&msg_serv,sizeof(msg_serv),0);
    if(ret <0){
        perror("send err:");
        return -18;
    }
    return 0;
}
int main(void )
{
    int ret;
    int i;
    int socket_serv;
    int socket_client;
    struct sockaddr_in  serv_addr;
    struct sockaddr_in  client_addr;
    socklen_t  addrlen;
    struct msg_struct msg_serv;
    socket_serv = socket(AF_INET, SOCK_STREAM, 0);
    if(socket_serv < 0){
        perror("serv socket err");
        return -12;
    }
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(SERV_PORT);  
    serv_addr.sin_addr.s_addr =  INADDR_ANY;  //代码自动获取本机ip地址
    ret = bind(socket_serv,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
    if(ret <0){
        perror("bind err");
        return -13;
    }
    ret = listen(socket_serv,5);
    if(ret <0){
        perror("listen err");
        return -14;
    }
    /*将 socket_serv加入 readfdset*/
    FD_SET(socket_serv,&readfdset);
    max = socket_serv;
loop:
    readfdset_backup = readfdset;
    ret = select(max+1,&readfdset_backup,NULL,NULL,NULL);
    if(ret <0){
        perror("select err");
        return -15;
    }
    else if(ret == 0){
        printf("select timeout \n");
        int do_something_others(void);
        goto loop;
    /*代码执行到这里,证明有fd发生事件了*/
    FD_ISSET(socket_serv,&readfdset_backup)){
        addrlen = sizeof(client_addr);
        socket_client = accept(socket_serv,(struct sockaddr *)&client_addr,&addrlen);
        if(socket_client <0){
            perror("accept err:");
            return -16;
            }
        //保存新的套接字通信
        cket_fds[socket_top] = socket_client;
        socket_top++;
        FD_SET(socket_client,&readfdset);   //将新的加入到集合中
        max = max > socket_client ? max :socket_client;
        printf("serv detect connect peer ip:%s port:%d\n",inet_ntoa(client_addr.sin_addr),ntohs( client_addr.sin_port));
        goto loop;
    }
    for(i=0;i<socket_top;i++){
        if(FD_ISSET(socket_fds[i],&readfdset_backup)){
            //deal socket
            serv_do_client_something(socket_fds[i]);
        }
    }
    goto loop;
    /*
        注意, 客户端 在socket和connect之间 有一个操作bind
            客户端可以指定自己的端口号, 但是如果省去
            connect的时候,发现没有端口号,系统自动分配一个.
    */
    while(1){
        /*使用新的 socket发送和接受
            recv
        ssize_t recv(int sockfd, void *buf, size_t len, int flags);
            buf:接收数据存放的位置
            len: buf的大小
            flags: 0
            返回值
                >0 实际接收到的数据
                -1 errno 
                ==0  对方主动或者意外挂掉
        */
        memset(&msg_serv,0,sizeof(msg_serv));
        ret = recv(socket_client,&msg_serv,sizeof(msg_serv),0);
        if(ret <0){
            perror("recv err:");
            return -17;
        }else if(ret == 0){
            printf("peer close it\n");
            close (socket_client);
            return 0;
        }
        printf("serv get data:temp%d hud%d cnt%d des:%s\n",ntohl(msg_serv.temp),ntohl(msg_serv.humidty),ntohs(msg_serv.cnt),msg_serv.des);
        msg_serv.cnt = ntohs(msg_serv.cnt);
        msg_serv.cnt++;
        msg_serv.cnt = htons(msg_serv.cnt);
        strcpy(msg_serv.des,"your msg is done");
        /*
            send    
            ssize_t send(int sockfd, const void *buf, size_t len, int flags);
            len: 是你要发送的数据长度
            返回值 
                >=0 实际发送
                -1   errno
        */
        ret = send(socket_client,&msg_serv,sizeof(msg_serv),0);
        if(ret <0){
        perror("send err:");
            return -18;
        }
    }
    close(socket_client);
}

客户端程序client.c

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include "comm.h"
int main(void )
{
    int ret;
    int socket_serv;
    struct sockaddr_in  serv_addr;
    struct msg_struct msg_cli;
    socket_serv = socket(AF_INET, SOCK_STREAM, 0);
    if(socket_serv < 0){
        perror("client socket err");
        return -12;
    }
    /*
        注意, 客户端 在socket和connect之间 有一个操作bind
            客户端可以指定自己的端口号, 但是如果省去
            connect的时候,发现没有端口号,系统自动分配一个,一般都不写。
    */
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(SERV_PORT);
    serv_addr.sin_addr.s_addr =  inet_addr( "149.28.27.109");
    ret = connect(socket_serv,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
    if(ret <0){
        perror("connect err");
        return -12;
    }
    memset(&msg_cli,0,sizeof(msg_cli));
    msg_cli.temp = htonl(28);
    msg_cli.humidty = htonl(80);
    msg_cli.cnt = htons(0);
    msg_cli.msgtype = 12;
    strcpy(msg_cli.des,"hello msg come from client");
    while(1){
        strcpy(msg_cli.des,"hello msg come from client");
        ret = send(socket_serv,&msg_cli,sizeof(msg_cli),0);
        if(ret <0){
            perror("client send err:");
            return -18;
        }
        memset(&msg_cli,0,sizeof(msg_cli));
        ret = recv(socket_serv,&msg_cli,sizeof(msg_cli),0);
        if(ret <0){
            perror("client recv err:");
            return -17;
        }else if(ret == 0){
            printf("peer close it\n");
            close (socket_serv);
            return 0;
        }
        printf("client get data:temp%d humm%d cnt%d des:%s\n",ntohl(msg_cli.temp),ntohl(msg_cli.humidty),ntohs(msg_cli.cnt),msg_cli.des);
        sleep(1);
    }
    close(socket_serv);
    return 0;
}

多进程并发

多进程并发在嵌入式开发中是最常用的一种服务器并发方式,程序如下:
头文件com.h

#ifndef TCP_STRUCT_TEST_H
#define TCP_STRUCT_TEST_H

#define PORT 7200

struct data_package
{
    int temp;
    int hum;
    short cnt;
    char info[64];
}__attribute__((packed));

#endif

服务器程序server.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>

#include "com.h"

struct pthread_s
{
    int server;
    pthread_t pid;
};

void *pthread_client(void *pth)
{
    struct pthread_s *p = pth;
    int resize;
    struct data_package data;
    int res;
    while(1)
    {
        resize = recv(p->server,&data,sizeof(data),0);
        if(resize < 0)
        {
            perror("recv()");
            close(p->server);
            return NULL;
        }
        else if(resize == 0)
        {
            printf("peer close!\n");
            close(p->server);
            return NULL;
        }

        data.temp = ntohl(data.temp);
        data.hum = ntohl(data.hum);
        data.cnt = ntohs(data.cnt);

        printf("[%d] temperture:%d,humidty:%d,%s\n",data.cnt,data.temp,\
                data.hum,data.info);

        strcpy(data.info,"get your message!");

        //data.cnt = ntohs(data.cnt);
        data.cnt++;
        data.cnt = htons(data.cnt);

        data.temp = htonl(data.temp);
        data.hum = htonl(data.hum);

        res = send(p->server,&data,sizeof(data),0);
        if(res < 0)
        {
            perror("send()");
            close(p->server);
            return NULL;
        }
    }

    close(p->server);
    return NULL;

}

int main(void)
{
    int socket_serv;
    int res;
    int client;
    struct sockaddr_in sockaddr;
    struct sockaddr_in client_addr;

    socklen_t client_len;

    pthread_t pid;
    struct pthread_s *p;

    socket_serv = socket(AF_INET,SOCK_STREAM,0);
    if(socket_serv < 0)
    {
        perror("socket()");
        exit(-1);
    }

    sockaddr.sin_family = AF_INET;
    sockaddr.sin_port = PORT;
    //sockaddr.sin_addr.s_addr = inet_addr("192.168.3.188");
    sockaddr.sin_addr.s_addr = INADDR_ANY;

    res = bind(socket_serv,(struct sockaddr *)&sockaddr,sizeof(sockaddr));
    if(res < 0)
    {
        perror("bind()");
        exit(-2);
    }

    res = listen(socket_serv,5);
    if(res < 0)
    {
        perror("listen()");
        exit(-3);
    }
    while(1)
    {
        client_len=sizeof(client_addr);
        client = accept(socket_serv,(struct sockaddr *)&client_addr,\
                &client_len);
        if(client < 0)
        {
            perror("accept()");
            exit(-4);
        }
        printf("connect port:%d,address:%s\n",ntohs(client_addr.sin_port),\
                inet_ntoa(client_addr.sin_addr));

        p=malloc(sizeof(*p));
        p->server = client;
        pthread_create(&pid,NULL,pthread_client,p);
        p->pid = pid;
    }

    exit(0);
}

客户端程序client.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>

#include "com.h"
#define BUFSIZE 128
int main(void)
{
    int socket_client;
    int res;
    struct sockaddr_in sockaddr;
    int resize;
    socklen_t client_len;
    struct data_package data_c;

    socket_client = socket(AF_INET,SOCK_STREAM,0);
    if(socket_client < 0)
    {
        perror("socket()");
        exit(-1);
    }

    sockaddr.sin_family = AF_INET;
    sockaddr.sin_port = PORT;
    sockaddr.sin_addr.s_addr = inet_addr("192.168.3.188");

    res = connect(socket_client,(struct sockaddr *)&sockaddr,\
            sizeof(sockaddr));
    if(res < 0)
    {
        perror("bind()");
        exit(-2);
    }
    data_c.temp = 28;
    data_c.hum = 59;
    data_c.cnt = 0;

    while(1)
    {
        strcpy(data_c.info,"message have sended!");
        data_c.temp = htonl(data_c.temp);
        data_c.hum = htonl(data_c.hum);
        data_c.cnt = htons(data_c.cnt);
        res = send(socket_client,&data_c,sizeof(data_c),0);
        if(res < 0)
        {
            perror("send()");
            exit(-6);
        }

        memset(&data_c,0,sizeof(data_c));
        resize = recv(socket_client,&data_c,sizeof(data_c),0);
        if(resize < 0)
        {
            perror("recv()");
            exit(-5);
        }

        data_c.temp = ntohl(data_c.temp);
        data_c.hum = ntohl(data_c.hum);
        data_c.cnt = ntohs(data_c.cnt);

        printf("[%d] temperture:%d,humidty:%d,%s\n",data_c.cnt,data_c.temp,\
                data_c.hum,data_c.info);
        sleep(1);

    }
    close(socket_client);

    exit(0);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值