Linux C语言 网络编程(二)

前面介绍了关于连接linux服务端方式,但是服务端的资源是有限的,所以我们通常需要重新思考,设计一套服务器模型来处理对应的客户端的请求。

第一种:并发服务器,通过主进程统一处理客户端的连接,当客户端连接过后,临时fork()进程,由子进程处理客户端请求,将连接请求和业务进行了分离。

server.c

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#define BUFFLEN 1024
#define SERVER_PORT 8888
#define BACKLOG 5
static void handle_request(int s_c)
{
    time_t now;                                 /*时间*/
    char buff[BUFFLEN];                         /*收发数据缓冲区*/
    int n = 0;
    memset(buff, 0, BUFFLEN);                   /*清零*/
    n = recv(s_c, buff, BUFFLEN,0);         /*接收发送方数据*/
    if(n > 0 && !strncmp(buff, "TIME", 4))      /*判断是否合法接收数据*/
    {
        memset(buff, 0, BUFFLEN);               /*清零*/
        now = time(NULL);                       /*当前时间*/
        sprintf(buff, "%24s\r\n",ctime(&now));  /*将时间复制入缓冲区*/
        send(s_c, buff, strlen(buff),0);        /*发送数据*/
    }
    /*关闭客户端*/
    close(s_c);
}
static int handle_connect(int s_s)
{

    int s_c;                                /*客户端套接字文件描述符*/
    struct sockaddr_in from;                /*客户端地址*/
    socklen_t len = sizeof(from);

    /*主处理过程*/
    while(1)
    {
        /*接收客户端连接*/
        s_c = accept(s_s, (struct sockaddr*)&from, &len);
        if(s_c > 0)                         /*客户端成功连接*/
        {
            /*创建进程进行数据处理*/
            if(fork() > 0){                 /*父进程*/
                close(s_c);                 /*关闭父进程的客户端连接套接字*/
            }else{
                handle_request(s_c);        /*处理连接请求*/
                return(0);
            }
        }
    }
}
int main(int argc, char *argv[])
{
    int s_s;                                /*服务器套接字文件描述符*/
    struct sockaddr_in local;               /*本地地址*/    

    /*建立TCP套接字*/
    s_s = socket(AF_INET, SOCK_STREAM, 0);

    /*初始化地址*/
    memset(&local, 0, sizeof(local));       /*清零*/
    local.sin_family = AF_INET;             /*AF_INET协议族*/
    local.sin_addr.s_addr = htonl(INADDR_ANY);  /*任意本地地址*/
    local.sin_port = htons(SERVER_PORT);    /*服务器端口*/

    /*将套接字文件描述符绑定到本地地址和端口*/
    bind(s_s, (struct sockaddr*)&local, sizeof(local));
    listen(s_s, BACKLOG);               /*侦听*/

    /*处理客户端连接*/
    handle_connect(s_s);

    close(s_s);

    return 0;
}

代码比较详细,容易理解。

下面介绍客户端代码,后面的客户端代码都是一样的。

client.c

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#define BUFFLEN 1024
#define SERVER_PORT 8888
int main(int argc, char *argv[])
{
    int s;                                      /*服务器套接字文件描述符*/
    struct sockaddr_in server;                  /*本地地址*/
    char buff[BUFFLEN];                         /*收发数据缓冲区*/
    int n = 0;                                  /*接收字符串长度*/

    /*建立TCP套接字*/
    s = socket(AF_INET, SOCK_STREAM, 0);

    /*初始化地址*/
    memset(&server, 0, sizeof(server));     /*清零*/
    server.sin_family = AF_INET;                /*AF_INET协议族*/
    server.sin_addr.s_addr = htonl(INADDR_ANY);/*任意本地地址*/
    server.sin_port = htons(SERVER_PORT);       /*服务器端口*/   

    /*连接服务器*/
    connect(s, (struct sockaddr*)&server,sizeof(server));
    memset(buff, 0, BUFFLEN);                   /*清零*/
    strcpy(buff, "TIME");                       /*复制发送字符串*/
    /*发送数据*/
    send(s, buff, strlen(buff), 0);
    memset(buff, 0, BUFFLEN);                   /*清零*/
    /*接收数据*/
    n = recv(s, buff, BUFFLEN, 0);
    /*打印消息*/
    if(n >0){
        printf("TIME:%s",buff);
    }
    close(s);

    return 0;
}

第二种模型:通过线程来处理,线程比进程占用资源少,效率高,数据共享。通过pthread_create()建立一个连接请求处理,线程处理函数为handle_request().

server.c

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <time.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#define BUFFLEN 1024
#define SERVER_PORT 8888
#define BACKLOG 5
static void handle_request(void *argv)
{
    int s_c = *((int*)argv);
    time_t now;                                 /*时间*/
    char buff[BUFFLEN];                         /*收发数据缓冲区*/
    int n = 0;
    memset(buff, 0, BUFFLEN);                   /*清零*/
    n = recv(s_c, buff, BUFFLEN,0);         /*接收发送方数据*/
    if(n > 0 && !strncmp(buff, "TIME", 4))      /*判断是否合法接收数据*/
    {
        memset(buff, 0, BUFFLEN);               /*清零*/
        now = time(NULL);                       /*当前时间*/
        sprintf(buff, "%24s\r\n",ctime(&now));  /*将时间复制入缓冲区*/
        send(s_c, buff, strlen(buff),0);        /*发送数据*/
    }
    /*关闭客户端*/
    close(s_c);
}
static void handle_connect(int s_s)
{

    int s_c;                                    /*客户端套接字文件描述符*/
    struct sockaddr_in from;                    /*客户端地址*/
    socklen_t len = sizeof(from);
    pthread_t  thread_do;

    /*主处理过程*/
    while(1)
    {
        /*接收客户端连接*/
        s_c = accept(s_s, (struct sockaddr*)&from, &len);
        if(s_c > 0)                         /*客户端成功连接*/
        {
            /*创建线程处理连接*/
            pthread_create(&thread_do,
                    NULL,
                    (void*)handle_request,
                    &s_c);
        }
    }
}
int main(int argc, char *argv[])
{
    int s_s;                                /*服务器套接字文件描述符*/
    struct sockaddr_in local;               /*本地地址*/    

    /*建立TCP套接字*/
    s_s = socket(AF_INET, SOCK_STREAM, 0);

    /*初始化地址和端口*/
    memset(&local, 0, sizeof(local));       /*清零*/
    local.sin_family = AF_INET;             /*AF_INET协议族*/
    local.sin_addr.s_addr = htonl(INADDR_ANY);  /*任意本地地址*/
    local.sin_port = htons(SERVER_PORT);        /*服务器端口*/

    /*将套接字文件描述符绑定到本地地址和端口*/
    bind(s_s, (struct sockaddr*)&local, sizeof(local));
    listen(s_s, BACKLOG);                   /*侦听*/

    /*处理客户端连接*/
    handle_connect(s_s);

    close(s_s);

    return 0;
}

第三种:服务端各线程独自accept(),使用互斥锁,使用pthread_create()建立多个线程组成的线程池,主线程等待程序结束,各个线程独自接收客户端accept,以及后面数据处理。

server.c

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <time.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#define BUFFLEN 1024
#define SERVER_PORT 8888
#define BACKLOG 5
#define CLIENTNUM 2
/*互斥量*/
pthread_mutex_t ALOCK = PTHREAD_MUTEX_INITIALIZER;
static void *handle_request(void *argv)
{
    int s_s = *((int*)argv);
    int s_c;                                /*客户端套接字文件描述符*/
    struct sockaddr_in from;                /*客户端地址*/
    socklen_t len = sizeof(from);
    for(;;)
    {
        time_t now;                         /*时间*/
        char buff[BUFFLEN];                 /*收发数据缓冲区*/
        int n = 0;

        pthread_mutex_lock(&ALOCK);         /*进入互斥区*/
        s_c = accept(s_s, (struct sockaddr*)&from, &len);
        /*接收客户端的请求*/
        pthread_mutex_unlock(&ALOCK);       /*离开互斥区*/

        memset(buff, 0, BUFFLEN);           /*清零*/
        n = recv(s_c, buff, BUFFLEN,0); /*接收发送方数据*/
        if(n > 0 && !strncmp(buff, "TIME", 4))  /*判断是否合法接收数据*/
        {
            memset(buff, 0, BUFFLEN);       /*清零*/
            now = time(NULL);               /*当前时间*/
            sprintf(buff, "%24s\r\n",ctime(&now));  /*将时间复制入缓冲区*/
            send(s_c, buff, strlen(buff),0);        /*发送数据*/
        }
        /*关闭客户端*/
        close(s_c);
    }

    return NULL;
}
static void handle_connect(int s)
{
    int s_s = s;
    pthread_t  thread_do[CLIENTNUM];        /*线程ID*/
    int i = 0;
    for(i=0;i<CLIENTNUM;i++)                /*建立线程池*/
    {
        /*创建线程*/
        pthread_create(&thread_do[i],       /*线程ID*/
                    NULL,                   /*属性*/
                    handle_request,         /*线程回调函数*/
                    (void*)&s_s);           /*线程参数*/
    }
    /*等待线程结束*/
    for(i=0;i<CLIENTNUM;i++)
        pthread_join(thread_do[i], NULL);
}
int main(int argc, char *argv[])
{
    int s_s;                                /*服务器套接字文件描述符*/
    struct sockaddr_in local;               /*本地地址*/    

    /*建立TCP套接字*/
    s_s = socket(AF_INET, SOCK_STREAM, 0);

    /*初始化地址和端口*/
    memset(&local, 0, sizeof(local));       /*清零*/
    local.sin_family = AF_INET;             /*AF_INET协议族*/
    local.sin_addr.s_addr = htonl(INADDR_ANY);  /*任意本地地址*/
    local.sin_port = htons(SERVER_PORT);        /*服务器端口*/

    /*将套接字文件描述符绑定到本地地址和端口*/
    bind(s_s, (struct sockaddr*)&local, sizeof(local));
    listen(s_s, BACKLOG);                   /*侦听*/

    /*处理客户端连接*/
    handle_connect(s_s);

    close(s_s);                                 /*关闭套接字*/

    return 0;
}

第四种:IO复用服务器,并发服务器客户端越多,对服务器造成的压力越大,所以还有第四种模型,IO复用函数用select来做。

server.c

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/select.h>
#define BUFFLEN 1024
#define SERVER_PORT 8888
#define BACKLOG 5
#define CLIENTNUM 1024                      /*最大支持客户端数量*/

/*可连接客户端的文件描述符数组*/
int connect_host[CLIENTNUM];
int connect_number =  0;
static void *handle_request(void *argv)
{
    time_t now;                                 /*时间*/
    char buff[BUFFLEN];                         /*收发数据缓冲区*/
    int n = 0;

    int maxfd = -1;                             /*最大侦听文件描述符*/
    fd_set scanfd;                              /*侦听描述符集合*/
    struct   timeval   timeout;                     /*超时*/
    timeout.tv_sec     =   1;                   /* 阻塞1s后超时返回 */
    timeout.tv_usec   =   0;     

    int i = 0;
    int err  = -1;
    for(;;)
    {
        /*最大文件描述符值初始化为-1*/
        maxfd = -1;
        FD_ZERO(&scanfd);                       /*清零文件描述符集合*/
        for(i=0;i<CLIENTNUM;i++)                /*将文件描述符放入集合*/
        {
            if(connect_host[i] != -1)           /*合法的文件描述符*/
            {
                FD_SET(connect_host[i], &scanfd);   /*放入集合*/
                if(maxfd <  connect_host[i])    /*更新最大文件描述符值*/
                {
                    maxfd = connect_host[i];
                }
            }
        }
        /*select等待*/
        err = select(maxfd + 1, &scanfd, NULL, NULL, &timeout) ;
        switch(err)
        {
            case 0:                             /*超时*/
                break;
            case -1:                            /*错误发生*/
                break;
            default:                            /*有可读套接字文件描述符*/
                if(connect_number<=0)
                    break;
                for(i = 0;i<CLIENTNUM;i++)
                {
                    /*查找激活的文件描述符*/
                    if(connect_host[i] != -1)
                    if(FD_ISSET(connect_host[i],&scanfd))
                    {
                        memset(buff, 0, BUFFLEN);/*清零*/
                        n = recv(connect_host[i], buff, BUFFLEN,0);
                        /*接收发送方数据*/
                        if(n > 0 && !strncmp(buff, "TIME", 4))
                        /*判断是否合法接收数据*/
                        {
                            memset(buff, 0, BUFFLEN);           /*清零*/
                            now = time(NULL);       /*当前时间*/
                            sprintf(buff, "%24s\r\n",ctime(&now));
                            /*将时间复制入缓冲区*/
                            send(connect_host[i], buff, strlen(buff),0);
                            /*发送数据*/
                        }
                        /*更新文件描述符在数组中的值*/
                        connect_host[i] = -1;
                        connect_number --;  /*客户端计数器减1*/
                        /*关闭客户端*/
                        close(connect_host[i]);
                    }
                }
                break;
        }
    } 

    return NULL;
}
static void *handle_connect(void *argv)
{
    int s_s = *((int*)argv) ;           /*获得服务器侦听套接字文件描述符*/
    struct sockaddr_in from;
    socklen_t len = sizeof(from);
    /*接收客户端连接*/
    for(;;)
    {
        int i = 0;
        int s_c = accept(s_s, (struct sockaddr*)&from, &len);
        /*接收客户端的请求*/
        printf("a client connect, from:%s\n",inet_ntoa(from.sin_addr));
        /*查找合适位置,将客户端的文件描述符放入*/
        for(i=0;i<CLIENTNUM;i++)
        {
            if(connect_host[i] == -1)           /*找到*/
            {
                /*放入*/
                connect_host[i]= s_c;

                /*客户端计数器加1*/
                connect_number ++;
                /*继续轮询等待客户端连接*/
                break;
            }
        }
    }
    return NULL;
}

int main(int argc, char *argv[])
{
    int s_s;                                /*服务器套接字文件描述符*/
    struct sockaddr_in local;               /*本地地址*/
    int i = 0;
    memset(connect_host, -1, CLIENTNUM);

    /*建立TCP套接字*/
    s_s = socket(AF_INET, SOCK_STREAM, 0);

    /*初始化地址*/
    memset(&local, 0, sizeof(local));           /*清零*/
    local.sin_family = AF_INET;                 /*AF_INET协议族*/
    local.sin_addr.s_addr = htonl(INADDR_ANY);  /*任意本地地址*/
    local.sin_port = htons(SERVER_PORT);        /*服务器端口*/

    /*将套接字文件描述符绑定到本地地址和端口*/
    bind(s_s, (struct sockaddr*)&local, sizeof(local));
    listen(s_s, BACKLOG);                   /*侦听*/

    pthread_t  thread_do[2];/*线程ID*/
    /*创建线程处理客户端连接*/
    pthread_create(&thread_do[0],               /*线程ID*/
                    NULL,                       /*属性*/
                    handle_connect,             /*线程回调函数*/
                    (void*)&s_s);               /*线程参数*/
    /*创建线程处理客户端请求*/
    pthread_create(&thread_do[1],               /*线程ID*/
                    NULL,                       /*属性*/
                    handle_request,             /*线程回调函数*/
                    NULL);                      /*线程参数*/
    /*等待线程结束*/
    for(i=0;i<2;i++)
        pthread_join(thread_do[i], NULL);

    close(s_s);

    return 0;
}

选择合适的服务器模型十分重要,对于编程有很大的影响。

时间: 2024-10-03 23:04:06

Linux C语言 网络编程(二)的相关文章

Linux网络编程(二)

服务套和客户机的信息函数 1.字节转换函数 在网络上面有着许多类型的机器,这些机器在表示数据的字节顺序是不同的, 比如i386芯片是低字节在内存地址的低端,高字节在高端,而alpha芯片却相反. 为了统一起来,在Linux下面,有专门的字节转换函数. unsigned long int htonl(unsigned long int hostlong)     unsigned short int htons(unisgned short int hostshort)     unsigned

7)Linux程序设计入门--网络编程

8)Linux程序设计入门--网络编程 Linux系统的一个主要特点是他的网络功能非常强大.随着网络的日益普及,基于网络的 应用也将越来越多. 在这个网络时代,掌握了Linux的网络编程技术,将令每一个人处 于不败之地,学习Linux的网络编程,可以让我们真正的体会到网络的魅力. 想成为一 位真正的hacker,必须掌握网络编程技术. 现在书店里面已经有了许多关于Linux网络编程方面的书籍,网络上也有了许多关于 网络编程方面的教材,大家都可以 去看一看的.在这里我会和大家一起来领会Linux网

Linux下TCP网络编程与基于Windows下C#socket编程间通信

一.linux下TCP网络编程基础,需要了解相关函数 Socket():用于套接字初始化. Bind():将 socket 与本机上的一个端口绑定,就可以在该端口监听服务请求. Listen():使socket处于被动的监听模式,并为该  socket  建立一个输入数据队列,将到达的服务器, 请求保存在此队列中,直到程序处理他们. Accept():让服务器接收客户的连接请求. Connect():客户端使用connect函数来配置 socket并与远端服务器建立一个 TCP 连接. Clos

Linux下TCP网络编程与基于Windows下C#socket编程之间通信

一.linux下TCP网络编程基础,需要了解相关函数 Socket():用于套接字初始化. Bind():将 socket 与本机上的一个端口绑定,就可以在该端口监听服务请求. Listen():使socket处于被动的监听模式,并为该  socket  建立一个输入 数据队列,将到达的服务器, 请求保存在此队列中,直到程序处理他们. Accept():让服务器接收客户的连接请求. Connect():客户端使用connect函数来配置 socket并与远端服务器建立一个 TCP 连接. Clo

给PHP扩展/C语言/网络编程初学者推荐的几本书

Linux/Unix系统 深入理解计算机系统 UNIX环境高级编程 深入理解Linux内核 网络通信编程 UNIX网络编程 TCP/IP详解 Linux多线程服务端编程 数据结构与算法 算法导论 <数据结构>(C语言版) C程序设计语言 给PHP扩展/C语言/网络编程初学者推荐的几本书

winform网络编程(二)

mnesia在频繁操作数据的过程可能会报错:** WARNING ** Mnesia is overloaded: {dump_log, write_threshold},可以看出,mnesia应该是过载了.这个警告在mnesia dump操作会发生这个问题,表类型为disc_only_copies .disc_copies都可能会发生. 如何重现这个问题,例子的场景是多个进程同时在不断地mnesia:dirty_write/2 mnesia过载分析 1.抛出警告是在mnesia 增加dump

安卓第八天笔记--网络编程二

安卓第八天笔记--网络编程二 1.网络图片查看器 /** * 网络图片查看器 * 1.获取输入的URL地址,判断是否为空 * 2.建立子线程,获取URl对象new URL(path) * 3.打开连接获取HttpURLConnection conn = (HttpURLConnection) url.openConnection(); * 4.设置连接超时时间conn.setConnectionTimeOut(5000)毫秒 * 5.设置请求方式setRequestMethod * GET或者P

嵌入式 Linux C语言(十二)——单链表

嵌入式 Linux C语言(十二)--单链表 一.单链表简介 1.单链表的结构 单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素. 链表中的数据是以节点来表示的,每个节点由两部分构成:一个是数据域,存储数据值,另一个是指针域,存储指向下一个节点的指针. 2.单链表的节点 单链表节点的数据结构如下: typedef struct data { unsigned int id;//学生身份ID char name[LENGTH];//学生姓名 char subject[

网络编程(二)

网络编程(二)    >>>思维导图>>>中二青年 基于TCP协议的Socket套接字 server端 import socket sk = socket.socket() sk.bind(('127.0.0.1',8898)) # 把地址绑定到套接字 sk.listen() # 监听链接 conn,addr = sk.accept() # 接受客户端链接 ret = conn.recv(1024) # 接收客户端信息 print(ret) # 打印客户端信息 conn