多进程、多线程服务器

本篇博客将介绍基于IPv4的socket网络编程。
一、socket

1.概念:

socket这个词可以表示很多概念:在TCP/IP协议中,“IP地址 + TCP或UDP端口号”唯一标识网络通信中的一个进程,“IP地址 + 端口号”就叫做socket。 在TCP协议中,建立连接的两个进程各自有一个socket来标识,那么这两个socket组成的socket pair就唯一标识一个一个连接,socket本身就有“插座”的意思,因此用来描述网络的一对一关系。

2.socket相关函数

1. socket函数

我们使用系统调用 socket函数是用来获得文件描述符 原型:

#include < sys/types.h>
#include< sys/socket.h>
int socket(int domain, int type, int protocol);

第一个参数domain表示,设置为“AF_INET”。
第二个参数为套接口的类型:SOCK_ STREAM或SOCK_DGRAM。
第三个设置为 0。

2. bind函数

我们在编写服务器的时候,一旦创建套接字,就用bind函数将套接字和本地计算机的一个端口绑定。原型:

#include< sys/types.h>
#include< sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

第一个参数就是socket函数返回的套接字文件描述符。
第二个参数addr是指向数据结构sockaddr的指针。数据结构sockaddr中包含了关于本机地址,端口号和IP地址的信息。
第三个参数 addrlen表示struct sockaddr结构体的大小,可以设置成sizeof(struct sockaddr)

3. connect函数

这个函数在客户端的程序中使用,用来连接所需要访问的服务器。
# include< sys/types.h>
# include< sys/socket.h>
int connect(int sockfd,struct sockaddr* serv_ addr,int addrlen);
第一个参数是套接字文件描述符,由socket函数返回。
第三个参数表示sockaddr结构体大小。使用sizeof(struct sockaddr)得到。

4. listen函数

用来监听自己的端口,如果有主机访问这个端口,那就把此主机信息存储在一个队列中等待。原型:
# inlcude < sys/types.h>
#include < sys/socket.h>
int listen(int sockfd,int backlog);
第一个参数是socket函数返回的套接字文件描述符。
第二参数是进入队列中允许连接的个数。这个值是队列中最多可以请求的个数。大多数系统设置为缺省值20,我们可以设置成10左右。

5. accept函数

在上面的函数中我们说监听到的主机信息存放在一个队列中,而这个accept函数就是将队列中的信息拿出来处理。在调用accept函数后,就返回一个全新的套接字文件描述符来处理这单个连接。这样,对于同一个连接来说,就有了两个文件描述符。原来的文件描述符还是去监听之的端口,新的文件描述符用来读写数据。下面看一下函数
#include < sys/socket.h>
int accept( int sockfd, struct sockaddr* addr, socklen_ t addrlen)
第一个参数是正在监听端口的套接字文件描述符。
第二个参数是个sockaddr(sockaddr_in)结构体指针,是个输入型参数, 就是将所访问主机的IP地址和端口存到这个结构体里面。通过他可以了解哪个主机在哪个端口呼叫你。
第三个参数使用sizeof(sockaddr_in)来获得。

6. 在编写服务器的过程中我们需要用到以下函数:

#include <arpa/inet.h>

   uint32 _ t htonl(uint32_t hostlong);

   uint16_ t htons(uint16_t hostshort);

   uint32_ t ntohl(uint32_t netlong);

   uint16_ t ntohs(uint16_t netshort);

这些库函数用来做网络字节序和主机字节序的转换。这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。如果 主机是小端字节序,这些函数将参数做相应的大小端转换然后返回,如果主机是大端字节序,这些数不做转换,将参数原封不动地返回。还有一个函数:
in_ addr_ t inet_addr(const char *cp);
将点分十进制转换为网络字节序。

二、接下来我们就用上面的函数来实现客户端/服务器程序。

先来说说实现一个服务器的流程吧。实现一个服务器需要以下几步:

首先要知道本机的IP地址,在linux下可用ifconfig命令来查询IP地址。我们还要指定一个端口,将此端口与服务器绑定。(假设程序中指定的端口为8080);

调用socket()函数,为进程分配一个套接字文件描述符。

调用bind()函数将进程和指定端口绑定。(因为别的主机访问的是我的主机的端口,而此时该端口与当前进程所绑定,所以当前进程可以通过此端口与别的主机进行通信。)

调用listen()函数进行监视该端口。当有主机访问该端口时,listen()会将主机信息放入等待队列中排队。

调用accept()函数,从等待队列中将访问的信息拿出来处理,为其分配一个专用的文件描述符。用此描述符来进行两台主机之间的通信。

开始通信(read,write)

实现客户端的流程:

调用socket()函数,为进程分配一个套接字文件描述符。

调用connect函数连接想要访问的服务器。

与服务器连接完成,开始传输数据。

1.单进程服务器

tcp_server.c(服务器)

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

void usage(char* s)
{
    printf("Please input:%s [in_addr] [port_addr]\n",s);
    //例如:./tcp_server 192.168.43.94 8080  
    (192.168.43.94是本机IP地址,8080是想要指定的端口)
}

int main(int argc, char* argv[])
{
    if(argc != 3)//运行服务器要按指定格式输入
    {
        usage(argv[0]);
        exit(1);
    }
    //1. 调用socket函数获取一个文件描述符
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock < 0)
    {
        perror("socket");
        exit(2);
    }
    //printf("%d\n",sock);

    //2. 调用bind函数将此进程与所指定的端口(8080)绑定
    struct sockaddr_in server_socket;
    struct sockaddr_in client_socket;

    bzero(&server_socket, sizeof(server_socket));

    server_socket.sin_family = AF_INET;
    server_socket.sin_addr.s_addr = inet_addr(argv[1]);
    server_socket.sin_port = htons(atoi(argv[2]));

    int bind_num = bind(sock, (struct sockaddr*)&server_socket, sizeof(struct sockaddr));
    if(bind_num < 0)
    {
        close(sock);
        perror("bind");
        exit(3);
    }

    //3.  用listen函数对端口进行监听
    if(listen(sock, 10) < 0)
    {
        close(sock);
        perror("listen");
        exit(4);
    }


    //4 调用accept函数开始处理信息
    while(1)
    {
        int len = 0;
        int new_fd = accept(sock, (struct sockaddr*)&client_socket, &len);//第二个参数是个输入型参数,是为了获得客户端的ip地址及端口号。
        if(new_fd < 0)
        {
            continue;
        }
        char buf[1024];
        memset(buf, '\0',sizeof(buf));
        inet_ntop(AF_INET, &client_socket.sin_addr, buf, sizeof(buf));//将客户端的ip地址转化成点分十进制
        printf("get client ip :%s\n",buf);//打印客户端ip
        while(1)//这个循环就是开始收发数据
        {
            char buff[1024];
            memset(buff, '\0', sizeof(buff));
            printf("client :# ");
            fflush(stdout);
            ssize_t s1 = read(new_fd, buff, sizeof(buff)-1);
            if( s1 > 0)
            {
                printf("%s\n", buff);
                printf("server :# ");
                printf("%s\n", buff);
                ssize_t s2 = write(new_fd, buff, strlen(buff));
                if(s2 < 0)
                {
                    perror("write");
                }
            }
            else if(s1 == 0)
            {
                printf("client close!\n");
                return 5;
            }
            else
            {
                perror("read");
                return 6;
            }
        }
    }
    close(sock);
    return 0;
}

tcp_client.c(客户端)

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


void usger(char* s)
{
    printf("Please input:%s [in_addr] [port_addr]\n",s);
}

int main(int argc, char* argv[])
{
    printf("1\n");
    if(argc != 3)
    {
        usger(argv[0]);
        exit(1);
    }
    //printf("client\n");
    //1.获得文件描述符
    int sock = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in server_sock;
    server_sock.sin_family = AF_INET;
    server_sock.sin_port = htons(atoi(argv[2]));
    server_sock.sin_addr.s_addr = inet_addr(argv[1]);

    //2.连接服务器,此时调用connect相当于三次握手的第一次握手
    int ret = connect(sock, (struct sockaddr*)&server_sock, sizeof(server_sock));

    if(ret < 0)
    {
        perror("correct");
        return 1;
    }
    //发收数据
    while(1)
    {
        printf("client :# ");
        fflush(stdout);
        char buf[1024];
        int count = read(0, buf, sizeof(buf)-1);
        buf[count] = '\0';
        write(sock, buf, strlen(buf));
        read(sock, buf, sizeof(buf)-1);
        printf("server :# %s\n",buf);
    }
    close(sock);
    return 0;
}

运行结果:

这里写图片描述

这里写图片描述

好了,大概就上面这个样子了0.0。

2.多进程服务器

如果理解了单进程的服务器,那么多进程服务器也就非常简单了,我们只需将通信部分让子进程去处理就好了,其他过程让父进程来处理。但是问题来了,只要服务器一直运行,我们的父进程就不会结束,而子进程随时可能结束,这样一来子进程就变成了僵尸进程,那么这个问题怎么处理呢?其实处理这个问题非常巧妙,我们让子进程再fork一次,得到一个孙子进程,然后结束子进程,这样孙子进程就成了孤儿进程,它会被1号进程回收,在这个过程中并没有产生僵尸进程,这个问题就解决了。我们看看代码中的实现。

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

void usger(char* s)
{
    printf("Please input:%s [in_addr] [port_addr]\n",s);
}

int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        usger(argv[0]);
        exit(1);
    }
    //printf("server\n");



    //1 get socket fd
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock < 0)
    {
        perror("socket");
        exit(2);
    }
    printf("%d\n",sock);



    //2 bind fd for port
    struct sockaddr_in server_socket;
    struct sockaddr_in client_socket;

    bzero(&server_socket, sizeof(server_socket));

    server_socket.sin_family = AF_INET;
    server_socket.sin_addr.s_addr = inet_addr(argv[1]);
    server_socket.sin_port = htons(atoi(argv[2]));

    int bind_num = bind(sock, (struct sockaddr*)&server_socket, sizeof(struct sockaddr));
    if(bind_num < 0)
    {
        close(sock);
        perror("bind");
        exit(3);
    }

    //3  listen
    if(listen(sock, 10) < 0)
    {
        close(sock);
        perror("listen");
        exit(4);
    }


    //4 accept
    while(1)
    {
        int len = 0;
        int new_fd = accept(sock, (struct sockaddr*)&client_socket, &len);
        if(new_fd < 0)
        {
            continue;
        }
        char buf[1024];
        memset(buf, '\0',sizeof(buf));
        inet_ntop(AF_INET, &client_socket.sin_addr, buf, sizeof(buf));
        printf("get client ip :%s\n",buf);


        pid_t id  = fork();//第一次fork
        if (id < 0)
        {
            perror("perror");

        }
        else if (id == 0)
        {
            close(sock);//关闭不用的文件描述符
            pid_t id = fork();//第二此fork
            if (id < 0)
            {
                perror("fork");
                exit(5);

            }
            else if(id == 0)//让孙子进程去处理
            {

                while(1)
                {
                    char buff[1024];
                    memset(buff, '\0', sizeof(buff));
                ssize_t s = read(new_fd, buff, sizeof(buff)-1);
                if(s > 0)
                {
                    printf("client :# ");
                    fflush(stdout);
                    printf("%s\n", buff);
                    printf("server :# ");
                    printf("%s\n", buff);

                    write(new_fd, buff, strlen(buff)+1);
                }
                else if(s == 0)
                {
                    printf("client close!\n");
                    break;
                }
                }
            }
            else
            {
                exit(6);//子进程直接退出
            }
        }
        else
        {
            close(new_fd);//关闭不用的文件描述符
            waitpid( id, NULL, 0);//等待子进程退出(子进程会立即退出)
        }
    }
    close(sock);
    return 0;
}

3.多线程服务器

多线程服务器和多进程服务器都差不多了,多线程服务器就是将通信部分让一个线程去处理,为了避免线程退出时整个进程退出,我们将线程分离出去。

#include<stdlib.h>
#include<sys/types.h>
#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<string.h>
#include<unistd.h>
void usger(char* s)
{
    printf("Please input:%s [in_addr] [port_addr]\n",s);
}

//线程处理函数
void request(void* arg)
{
    int new_fd = (int )arg;
    while(1)
    {
        char buff[1024];
        memset(buff, '\0', sizeof(buff));
        ssize_t s = read(new_fd, buff, sizeof(buff)-1);
        if(s > 0)
        {
            printf("client :# ");
            fflush(stdout);
            printf("%s\n", buff);
            printf("server :# ");
            printf("%s\n", buff);
            write(new_fd, buff, strlen(buff)+1);
        }
        else
        {
            printf("client close!\n");
            break;
        }
    }

}

int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        usger(argv[0]);
        exit(1);
    }
    //printf("server\n");



    //1 get socket fd
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock < 0)
    {
        perror("socket");
        exit(2);
    }
    printf("%d\n",sock);



    //2 bind fd for port
    struct sockaddr_in server_socket;
    struct sockaddr_in client_socket;

    bzero(&server_socket, sizeof(server_socket));

    server_socket.sin_family = AF_INET;
    server_socket.sin_addr.s_addr = inet_addr(argv[1]);
    server_socket.sin_port = htons(atoi(argv[2]));

    int bind_num = bind(sock, (struct sockaddr*)&server_socket, sizeof(struct sockaddr));
    if(bind_num < 0)
    {
        close(sock);
        perror("bind");
        exit(3);
    }

    //3  listen
    if(listen(sock, 10) < 0)
    {
        close(sock);
        perror("listen");
        exit(4);
    }


    //4 accept
    while(1)
    {
        int len = 0;
        int new_fd = accept(sock, (struct sockaddr*)&client_socket, &len);
        if(new_fd < 0)
        {
            continue;
        }
        char buf[1024];
        memset(buf, '\0',sizeof(buf));
        inet_ntop(AF_INET, &client_socket.sin_addr, buf, sizeof(buf));
        printf("get client ip :%s\n",buf);



        pthread_t id;
        pthread_create(&id, NULL, request, (void*)new_fd );//创建线程
        pthread_detach(id);//分离线程
    }
    close(sock);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值