Linux 小实验之并发服务器的设计

         Linux中的服务器设计一般分为循环服务器和并发服务器,按照所使用的协议可以分为TcpTransmission Control Protocol 传输控制协议)和UdpUser Datagram Protocol 用户数据报协议)。但是循环服务器在linux上用的不多,它在同一时刻只能响应一个客户端的请求,效率很低,所以一般都采用并发服务器,可以同时处理多个客户端的请求。

         众所周知,使用tcp协议来传输数据,有可靠性、和安全性的保障,但是效率没有udp的高。事物都是二面性的,tcp通过它的3次握手和4次挥手确保了数据的稳定传输,但牺牲了它的效率。具体协议内容不在多说,这都可以在RFC文档里面找到。

 

         下面就来用代码实现一下并发tcp服务器。在写之前我们需要了解IP地址、端口号(Port和套接字(Socket)的关系,以及在计算机内是怎么存放的。在使用IP地址和Port时要将其转化成统一的格式,及大端字节序,这是为了避免不同类别主机之间在数据交换时由于字节序不同而导致的差错。在转换时我们一般按照下面这么做,其中argv[1]argv[2]表示我输入的IP地址和端口号,server_addrsockaddr_in结构体变量。

server_addr.sin_family = AF_INET;       //地址族IPV4

server_addr.sin_port   = htons(atoi(argv[2]));  //端口号转换

server_addr.sin_addr.s_addr = inet_addr(argv[1]);  //ip地址转换

        Socket是一个编程接口,也是一种特殊的文件描述符,由于TCP/IP协议是一种国际规范,所以被写入到了内核中,但socket不仅限于TCP/IP,它有三种类型,分别为流式套接字、数据报套接字、原始套接字,不同的类型,所走的协议不同。

先介绍下两个重要的数据类型:sockaddrsockaddr_in,他们都用来保存socket的信息,但是为了方便我们一般使用sockaddr_in数据类型,然后通过对地址的强制转换变成sockaddr类型。


        实现并发服务器可以用线程和进程来创建,他们有一点区别,在用线程创建时,有时候可能会由于客户端过多,而文件描述符只有一个空间,使得系统分配出错,导致创建的套接字被覆盖,因此需要用malloc函数动态分配地址存放套接字。这里我们用进程来实现。

流程如下:

服务器端:

1.创建流式套接字,socket()

2.IPPORT,转成网络字节序,函数分别为inet_addrIP)和htonsatoiPORT))

3.IPPORT进行绑定,函数为bind()

4.设置套接字为监听模式函数为listen()

5.accept函数提取客户端的请求。注意accept成功返回的是新的套接字,千万记住

 6.来一路请求则创建子进程与客户端交互,父进程则处理僵尸态子进程并关闭accept返回的文件描述符,避免浪费

客户端:     

1.创建流式套接字,socket()

 2.IPPORT,转成网络字节序,函数分别为inet_addrIP)和htonsatoiPORT)),这里写的是服务器的IPPORT

3.客户端可以不用绑定,系统会自动分配一个端口号

4.通过connect函数发起连接请求

上面的一些函数在网络编程中会一直使用,务必要记住,不知道怎么使用可以查看MAN手册。


参数说明:domain是地址族,就是你使用的什么协议。

          type是套接字类型

          protocol是协议编号,一般设为0,表示让系统去找。


参数说明:sockfdsocket成功返回的套接字的文件描述符

          addr:  是上面定义的结构体变量名,注意强转和取地址

          addrlen:是结构体变量的长度,用sizeof可以求出 


参数说明:sockfdsocket成功返回的套接字的文件描述符

          backlog:监听队列的最大长度,一般设置为5


参数说明:sockfdsocket成功返回的套接字的文件描述符

          addr:  是上面定义的结构体变量名,注意强转和取地址

          addrlen:是结构体变量的长度,用sizeof可以求出 ,注意要取地址


参数说明:sockfdsocket成功返回的套接字的文件描述符

          addr:  是上面定义的结构体变量名,注意强转和取地址

          addrlen:是结构体变量的长度,用sizeof可以求出 

   这些函数使用并不困难,只需要对照传参就好,每个参数的说明在man手册里面都可以查到。

服务器端源代码:

     1 #include <sys/socket.h>

     2 #include <netinet/in.h>

     3 #include <arpa/inet.h>

     4 #include <stdio.h>

     5 #include <string.h>

     6 #include <unistd.h>

     7 #include <signal.h>

     8 #include <sys/wait.h>

     9 #include <sys/types.h>

    10 #include <stdlib.h>

    11

    12 void signal_handler(int signum)  //非阻塞处理僵尸态子进程

    13 {

    14 waitpid(-1,NULL,WNOHANG);

    15 return;

    16 }

    17

    18 int do_client(int sockfd)

    19 {

    20 int n;

    21 char buf[1024];

    22

    23 while(1)

    24 {

    25 n = recv(sockfd,buf,sizeof(buf) - 1,0);

    26 buf[n] = '\0';

    27 printf("Recv %d bytes : %s.\n",n,buf);

    28 if(strncmp(buf,"quit",4) == 0)

    29 break;

    30 }

    31

    32 exit(EXIT_SUCCESS);

    33 }

    34

    35 //./server ip port

    36 int main(int argc, const char *argv[])

    37 {

    38 int n;

    39 pid_t pid;

    40 int listen_fd;

    41 int connect_fd;

    42 struct sockaddr_in peer_addr;

    43 struct sockaddr_in server_addr;

    44 int addr_len = sizeof(struct sockaddr);

    45

    46 if(argc < 3)

    47 {

    48 fprintf(stderr,"Usage : %s ip prot.\n",argv[0]);

    49 exit(EXIT_FAILURE);

    50 }

    51

    52 if(signal(SIGCHLD,signal_handler) == SIG_ERR)

    53 {

    54 perror("Fail to signal");

    55 exit(EXIT_FAILURE);

    56 }

    57

    58 listen_fd = socket(AF_INET,SOCK_STREAM,0);

    59 if(listen_fd < 0){

    60     perror("Fail to socket");

    61 exit(EXIT_FAILURE);

    62 }

    63

    64 bzero(&server_addr,sizeof(server_addr));

    65 server_addr.sin_family = AF_INET;       

    66 server_addr.sin_port   = htons(atoi(argv[2]));  

    67 server_addr.sin_addr.s_addr = inet_addr(argv[1]);  

    68 if(bind(listen_fd,(struct sockaddr *)&server_addr,sizeof(server_addr)) < 0)

    69 {

    70 perror("Fail to bind");

    71 exit(EXIT_FAILURE);

    72 }

    73

    74 listen(listen_fd,5);

    75 printf("Listen ...\n");

    76

    77 //并发服务器:每提取一个请求就创建一个子进程/子线程和客户端进行交互

    78 while(1)

    79 {

    80 connect_fd = accept(listen_fd,(struct sockaddr *)&peer_addr,&addr_len);

    81 if(connect_fd < 0){

    82 perror("Fail to accept");

    83 exit(EXIT_FAILURE);

    84 }

    85

    86 printf("****************************\n");

    87 printf("Port : %d.\n",ntohs(peer_addr.sin_port));  //打印连接的PORT,转成  主机字节序

    88 printf("Ip   : %s.\n",inet_ntoa(peer_addr.sin_addr));//同上面一样要转换

    89 printf("****************************\n");

    90

    91 if((pid = fork()) < 0)  //提取请求之后创进子进程

    92 {

    93 perror("Fail to fork");

    94 exit(EXIT_FAILURE);

    95 }

    96

    97 if(pid == 0)   //子进程与客户端交互

    98 {

    99 do_client(connect_fd);

   100 }

   101

   102 //避免文件描述符浪费

   103 close(connect_fd);

   104 }

   105

   106 return 0;

   107 }

客户端源代码:

     1 #include <sys/socket.h>

     2 #include <netinet/in.h>

     3 #include <arpa/inet.h>

     4 #include <stdio.h>

     5 #include <string.h>

     6 #include <unistd.h>

     7 #include <signal.h>

     8 #include <sys/wait.h>

     9 #include <sys/types.h>

    10 #include <stdlib.h>

    11

    12 //./server ip port

    13 int main(int argc, const char *argv[])

    14 {

    15 int n;

    16 char buf[1024];

    17 int sockfd;

    18 struct sockaddr_in server_addr;

    19

    20 if(argc < 3)

    21 {

    22 fprintf(stderr,"Usage : %s ip prot.\n",argv[0]);

    23 exit(EXIT_FAILURE);

    24 }

    25

    26 sockfd = socket(AF_INET,SOCK_STREAM,0);

    27 if(sockfd < 0){

    28 perror("Fail to socket");

    29 exit(EXIT_FAILURE);

    30 }

    31

    32 bzero(&server_addr,sizeof(server_addr));

    33 server_addr.sin_family = AF_INET;

    34 server_addr.sin_port   = htons(atoi(argv[2]));

    35 server_addr.sin_addr.s_addr = inet_addr(argv[1]);

    36 if(connect(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr)) < 0)

    37 {

    38     perror("Fail to bind");

    39 exit(EXIT_FAILURE);

    40 }

    41

    42

    43 while(1)

    44 {

    45 fgets(buf,sizeof(buf),stdin);

    46 buf[strlen(buf) - 1] = '\0';

    47

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

    49 }

    50

    51 return 0;

    52 }

实验效果如下:




想想:当我关掉了客户端后,服务器为什么没有阻塞,这种情况在UDP中会出现吗

    

    用2个客户端与服务器交互,客户端发送内容,服务器接受!那么一个简单的tcp并发服务器就完成了。这只是一个简单的小实验,重点在与要掌握一下网络的基本概念,与相关系统函数的调用。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值