【网络】实现简单的TCP、UDP服务器、TCP多进程/多线程服务器

1.0 一个简单的TCP服务器(只服务一个客户端)

先看代码如下:
server.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>

static void Usage(const char* proc)
{
    printf("Usage:%s[local_ip][local_port]\n",proc);
}


int StartUp(const char* ip,int port)
{
    int sock = socket(AF_INET,SOCK_STREAM,0);//创建套接字
    if(sock < 0){
        perror("socket");
        exit(2);
    }   

    struct sockaddr_in local;//IPV4
    local.sin_family = AF_INET;
    local.sin_port = htons(port);
    local.sin_addr.s_addr = inet_addr(ip);

    if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0){//绑定套接字
        perror("bind");
        exit(3);
    }

    if(listen(sock,5) < 0){//监听套接字
        perror("listen");
        exit(4);
    }

    return sock;//将最终得到的监听套接字返回
}

int main(int argc,char* argv[])
{
    if(argc != 3){
        Usage(argv[0]);
        return 1;
    }

    int listen_sock = StartUp(argv[1],atoi(argv[2]));//获得监听套接字
    while(1)
    {
        struct sockaddr_in client;//用来获得发送者的信息(ip地址和端口号)
        socklen_t len = sizeof(client);

        //建立连接,得到新套接字进行操作
        int new_sock = accept(listen_sock,(struct sockaddr*)&client,&len);
        if(new_sock < 0){
            perror("accept");//if fail,again listen
            continue;
        }

        printf("get a connect,ip is %s,port is %u\n",\
              inet_ntoa(client.sin_addr),ntohs(client.sin_port));

        while(1)
        {//先读再写
         char buf[1024];
         ssize_t s = read(new_sock,buf,sizeof(buf)-1);

         if(s > 0){//读成功
            buf[s] = '\0';
            printf("client#:%s\n",buf);
            write(new_sock,buf,strlen(buf));//server hui xian
         }
         else if(s == 0){//读到的字节数为0,表明对端结束已经关闭
            printf("client exit\n");
            break;
         }
         else{//读失败
            perror("read");
            break;

          }

        }

        close(new_sock);
    }
    return 0;
}

client.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

static Usage(const char* proc)
{
    printf("Usage:%s[server_ip][server_prot]\n",proc);
}

int main(int argc,char* argv[])
{
    if(argc != 3){
        Usage(argv[0]);
        return 1;
    }

    int sock = socket(AF_INET,SOCK_STREAM,0);//创建套接字
    if(sock < 0){
        perror("socket");
        return 2;
    }

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

    if(connect(sock,(struct sockaddr*)&server,sizeof(server)) < 0){//请求与服务器建立TCP连接
        perror("connect");
        return 3;
    }

    while(1)
    {//先写在读
     printf("client#");
     fflush(stdout);
     char buf[1024];
     ssize_t s = read(0,buf,sizeof(buf)-1);//从标准输入(键盘)中读数据到buf
     if(s > 0){
         buf[s-1] = '\0';
         write(sock,buf,strlen(buf));
         ssize_t _s = read(sock,buf,sizeof(buf)-1);//实现服务器的回显
         if(_s > 0){
         buf[_s] = '\0';
         printf("server echo#%s\n",buf);
         }
    }
     else if(s == 0){
         printf("read ending\n");
         exit(4);
     }
     else{
         perror("read");
         break;
     }
    }
    return 0;
}

····第一次连接的现象
client.c
这里写图片描述

server.c
这里写图片描述

·····client.c结束后的现象
client.c
这里写图片描述

server.c
这里写图片描述

····再次重新连接后的现象
client.c
这里写图片描述

server.c
这里写图片描述

但是有个问题,上面实现的简单服务器,每次只能连接一个客户端,当有多个客户端同时请求与服务器连接时,服务器只能处理一个的请求与一个客户端相连,剩下的连接请求先阻塞在连接队列,当服务器处理完某个客户端的连接,再从队列里拿一个连接进行处理。如下。

37161端口
这里写图片描述

37162端口
这里写图片描述

服务器处理完37161,才处理37162,因为服务器在处理37161的时候,37162给服务器发消息,服务器不作回应。
这里写图片描述

但是只能进行一对一的连接,与我们平常所见到的服务器功能不符,单进程的服务器毕竟来说可服务的对象就很小,所以现在实现一个多进程的服务器。那么接下来我们来实现一个一对多的服务器。

2.0 实现TCP多进程服务器

server.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#include<sys/types.h>
#include<sys/socket.h>
#include<fcntl.h>
#include<arpa/inet.h>
#include<netinet/in.h>

static void Usage(const char* proc)
{
    printf("Usage:%s[local_ip][local_port]\n",proc);
}


int StartUp(const char* ip,int port)
{
    int sock = socket(AF_INET,SOCK_STREAM,0);//create socket
    if(sock < 0){
        perror("socket");
        exit(2);
    }   

    struct sockaddr_in local;//IPV4
    local.sin_family = AF_INET;
    local.sin_port = htons(port);
    local.sin_addr.s_addr = inet_addr(ip);

    if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0){//bind
        perror("bind");
        exit(3);
    }

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

    return sock;//return an "listen_sock";
}

int main(int argc,char* argv[])
{
    if(argc != 3){
        Usage(argv[0]);
        return 1;
    }

    int listen_sock = StartUp(argv[1],atoi(argv[2]));//get listen_sock
    while(1)
    {
        struct sockaddr_in client;//get sender's information
        socklen_t len = sizeof(client);

        //get real socket that we will operator
        int new_sock = accept(listen_sock,(struct sockaddr*)&client,&len);
        if(new_sock < 0){
            perror("accept");//if fail,again listen
            continue;
        }

        printf("get a connect,ip is %s,port is %u\n",\
              inet_ntoa(client.sin_addr),ntohs(client.sin_port));

        pid_t id = fork();
        if(id < 0){
            perror("fork");
            close(new_sock);
        }
        else if(id == 0)
        {//child
            close(listen_sock);
            if(fork() > 0){//again fork , create sun zi process and child process end
             exit(0);
             }   

            //sun zi process working
            while(1)
            {//first read,then write
             char buf[1024];
             ssize_t s = read(new_sock,buf,sizeof(buf)-1);

             if(s > 0){//read successful
                 buf[s] = '\0';
                 printf("client#:%s\n",buf);
                 write(new_sock,buf,strlen(buf));//server hui xian
             }
             else if(s == 0){//read to file's end
                 printf("client exit\n");
                 break;
             }
             else{//read fail
                 perror("read");
                 break;
             }
            }
             close(new_sock);
             exit(0);
        }
        else
        {//father
         close(new_sock);
         waitpid(id,NULL,0);
        }
    }
    return 0;
}

client.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#include<sys/types.h>
#include<sys/socket.h>
#include<fcntl.h>
#include<netinet/in.h>
#include<arpa/inet.h>

static Usage(const char* proc)
{
    printf("Usage:%s[server_ip][server_prot]\n",proc);
}

int main(int argc,char* argv[])
{
    if(argc != 3){
        Usage(argv[0]);
        return 1;
    }

    int sock = socket(AF_INET,SOCK_STREAM,0);//create socket
    if(sock < 0){
        perror("socket");
        return 2;
    }

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

    if(connect(sock,(struct sockaddr*)&server,sizeof(server)) < 0){//connect
        perror("connect");
        return 3;
    }

    while(1)
    {//first write,then read
         printf("client#");
         fflush(stdout);
         char buf[1024];
         ssize_t s = read(0,buf,sizeof(buf)-1);//read data from stdin
         if(s > 0){
             buf[s-1] = '\0';
             write(sock,buf,strlen(buf));
             ssize_t _s = read(sock,buf,sizeof(buf)-1);//shi xian server hui xian
             if(_s > 0){
             buf[_s] = '\0';
             printf("server echo#%s\n",buf);
            }
        }
         else if(s == 0){
             printf("read ending\n");
             exit(4);
         }
         else{
             perror("read");
             break;
         }
    }
    close(sock);
    return 0;
}

    在server.c中,我们先fock了两个进程父进程和子进程。父进程的工作是不断地返回accept处拿new_sock,子进程的工作是不断地去处理new_sock。也就是说父进程只管拿,子进程只管处理。所以父进程关闭不需要的套接字new_sock,而只留下listen_sock ; 子进程关闭不需要的listen_sock,而留下new_sock.
    细心地你也许会发现,可是为什么还在子进程中再次fock创建一个孙子进程?这也正是这段代码的巧妙之处。
     通过之前的学习我们知道,子进程退出后变成僵尸进程,直到父进程调用wait或者waitpid来对子进程资源进行回收。所以在第一次fork完成后,我们父进程会调用waitpid等待函数去等待子进程结束,但是它目前是阻塞式等待。为了解决阻塞问题,我们在子进程中又创建了一个孙子进程,并且让子进程退出,让孙子进程去完成子进程的工作。而对于孙子进程来说,子进程的退出使孙子进程变成孤儿进程,被1号进程Init收养,若孙子进程退出也是由Init进程回收它,与父进程中的waitpid无关。这样当子进程退出时,父进程的waitpid对子进程资源进行了回收,并且回收后因为子进程已经不存在了,waitpid也失去了它的作用,就不会阻塞了。这就是这段代码的巧妙之处。

结果如图:
client1
这里写图片描述

client2
这里写图片描述

server
这里写图片描述

3.0 实现TCP多线程服务器
基于多线程的服务器和多进程的差不多。代码如下。
server.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#include<sys/types.h>
#include<sys/socket.h>
#include<fcntl.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<pthread.h>

static void Usage(const char* proc)
{
    printf("Usage:%s[local_ip][local_port]\n",proc);
}


int StartUp(const char* ip,int port)
{
    int sock = socket(AF_INET,SOCK_STREAM,0);//create socket
    if(sock < 0){
        perror("socket");
        exit(2);
    }   

    struct sockaddr_in local;//IPV4
    local.sin_family = AF_INET;
    local.sin_port = htons(port);
    local.sin_addr.s_addr = inet_addr(ip);

    if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0){//bind
        perror("bind");
        exit(3);
    }

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

    return sock;//return an "listen_sock";
}

void* request(void* arg)
{
    int new_sock = (int)arg;
    printf("get a new client\n");
    while(1)
    {//first read,then write
     char buf[1024];
     ssize_t s = read(new_sock,buf,sizeof(buf)-1);

     if(s > 0){//read successful
         buf[s] = '\0';
         printf("client#:%s\n",buf);
         write(new_sock,buf,strlen(buf));//server hui xian
     }
     else if(s == 0){//read to file's end
         printf("client exit\n");
         break;
     }
     else{//read fail
         perror("read");
         break;
     }
    }


}
int main(int argc,char* argv[])
{
    if(argc != 3){
        Usage(argv[0]);
        return 1;
    }

    int listen_sock = StartUp(argv[1],atoi(argv[2]));//get listen_sock

    while(1)
    {
        struct sockaddr_in client;//get sender's information
        socklen_t len = sizeof(client);

        //get real socket that we will operator
        int new_sock = accept(listen_sock,(struct sockaddr*)&client,&len);
        if(new_sock < 0){
            perror("accept");//if fail,again listen
            continue;
        }

        pthread_t id;
        pthread_create(&id,NULL,&request,(void*)new_sock);
        pthread_detach(id);
    }


    return 0;
}

client.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#include<sys/types.h>
#include<sys/socket.h>
#include<fcntl.h>
#include<netinet/in.h>
#include<arpa/inet.h>

static Usage(const char* proc)
{
    printf("Usage:%s[server_ip][server_prot]\n",proc);
}

int main(int argc,char* argv[])
{
    if(argc != 3){
        Usage(argv[0]);
        return 1;
    }

    int sock = socket(AF_INET,SOCK_STREAM,0);//create socket
    if(sock < 0){
        perror("socket");
        return 2;
    }

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

    if(connect(sock,(struct sockaddr*)&server,sizeof(server)) < 0){//connect
        perror("connect");
        return 3;
    }

    while(1)
    {//first write,then read
         printf("client#");
         fflush(stdout);
         char buf[1024];
         ssize_t s = read(0,buf,sizeof(buf)-1);//read data from stdin
         if(s > 0){
             buf[s-1] = '\0';
             write(sock,buf,strlen(buf));
             ssize_t _s = read(sock,buf,sizeof(buf)-1);//shi xian server hui xian
             if(_s > 0){
             buf[_s] = '\0';
             printf("server echo#%s\n",buf);
            }
        }
         else if(s == 0){
             printf("read ending\n");
             exit(4);
         }
         else{
             perror("read");
             break;
         }
    }
    close(sock);
    return 0;
}

thread1
这里写图片描述

thread2
这里写图片描述

server
这里写图片描述

4.0 实现UDP服务器

UDP服务器进行读写时,用的不是read和write,而是以下两个函数。

#include <sys/socket.h>

ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags, struct sockaddr *from, socklen_t *addrlen);

ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags, const struct sockaddr *to, socklen_t addrlen);
//均返回:若成功则为读或写的字节数,出错为-1
    前三个参数:sockfd, buff, nbytes等同于read和write的前三个参数:描述字,指向读入或者写出缓冲区的指针,读写字节数。
    函数sendto的参数to是一个含有数据将发往的协议地址(例如IP地址和端口号)的套接口地址结构,它的大小由addrlen来指定。函数recvfrom用数据报发送者的协议地址装填由from所指的套接口地址结构,存储在此套接口地址结构中的字节数也以addrlen所指的整数返回给调用者。注意,sendto的最后一个参数是一个整数值,而recvfrom的最后一个参数值是一个指向整数值的指针。
    recvfrom的最后两个参数类似于accept的最后两个参数:返回时套接口地址结构的内容告诉我们是谁发送了数据报(UDP情况下)或是谁发起了连接(TCP情况下)。sendto的最后两个参数类似于connect的最后两个参数:我们用数据报将发往(UDP情况下)或与之建立连接(TCP情况下)的协议地址来装填套接口地址结构。
    写一个长度为0的数据报是可行的,这也意味着对于数据报协议,recvfrom返回0值也是可行的;它不表示对方已经关闭了连接,这与TCP套接口上的read返回0的情况不同。由于UDP服务器也是服务器,所以需要绑定,但因为UDP是无连接的,所以不需要监听和连接,这就没有诸如关闭UDP连接之类的事情。
    PS:默认情况recvfrom函数没有接收到对方数据时候是阻塞的

实现代码如下:
server.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#include<sys/types.h>
#include<sys/socket.h>
#include<fcntl.h>
#include<arpa/inet.h>
#include<netinet/in.h>


static void Usage(const char* proc)
{
    printf("Usage:%s[local_ip][local_port]\n",proc);
}


int main(int argc,char* argv[])
{
    if(argc != 3){
        Usage(argv[0]);
        return 1;
    }

    int sock = socket(AF_INET,SOCK_DGRAM,0);//create socket
    if(sock < 0){
        perror("socket");
        exit(2);
    }   

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

    if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0){//bind
        perror("bind");
        exit(3);
    }

    printf("get a client\n");
    while(1)
    {//先读再写
        printf("[%s:%d]\n",inet_ntoa(local.sin_addr),ntohs(local.sin_port));
        struct sockaddr_in client;//获得发送方的信心(ip地址和端口号)
        socklen_t len = sizeof(client);

        char buf[1024];
        int s  = recvfrom(sock,buf,sizeof(buf)-1,0,(struct sockaddr*)&client,&len);//与accept相似
        if(s < 0){
            perror("recvfrom");
            continue;
        }
        else if(s > 0){
            buf[s] = '\0';
            printf("[%s:%d]# %s\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port),buf);
            sendto(sock,buf,strlen(buf),0,(struct sockaddr*)&client,sizeof(client));//sendto类似于connect,这一步是在进行服务器回显
        }
        else{
            printf("client exit!\n");
            continue;
        }
    }


    return 0;
}

client.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#include<sys/types.h>
#include<sys/socket.h>
#include<fcntl.h>
#include<netinet/in.h>
#include<arpa/inet.h>

static Usage(const char* proc)
{
    printf("Usage:%s[server_ip][server_prot]\n",proc);
}

int main(int argc,char* argv[])
{
    if(argc != 3){
        Usage(argv[0]);
        return 1;
    }

    int sock = socket(AF_INET,SOCK_DGRAM,0);//create socket
    if(sock < 0){
        perror("socket");
        return 2;
    }

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

    while(1)
    {//先写再读
         printf("client#");
         fflush(stdout);
         char buf[1024];

         struct sockaddr_in peer;
         socklen_t len = sizeof(peer);

         ssize_t s = read(0,buf,sizeof(buf)-1);
         if(s > 0){
             buf[s-1] = '\0';
             sendto(sock,buf,strlen(buf),0,(struct sockaddr*)&server,sizeof(server));
             ssize_t _s = recvfrom(sock,buf,sizeof(buf)-1,0,(struct sockaddr*)&peer,&len);//实现服务器回显
             if(_s > 0){
             buf[_s] = '\0';
             printf("[%s:%d]:%s\n",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port),buf);
            }
        }
         else if(s == 0){
             printf("read ending\n");
             exit(4);
         }
         else{
             perror("read");
             break;
         }
    }
    close(sock);
    return 0;
}

client1
这里写图片描述

client2
这里写图片描述

server
这里写图片描述

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值