网络编程TCP/IP协议----- 多进程多线程服务器

Posted 真挚而纯真

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络编程TCP/IP协议----- 多进程多线程服务器相关的知识,希望对你有一定的参考价值。

1、前言

上一篇已经实现了服务器端与客户端之间最基础通信,但存在一些问题,最大的问题是上篇中一个服务器端只能连接一个客户端,如何让一个服务器端可以连接多个客户端呢?利用多进程多线程实现。

2、优化说明

  • 优化1: 让服务器程序可以绑定在任何的IP地址上。
  • 优化2 :通过程序获取刚建立的socket的客户端的IP地址和端口号。
  • 优化3 :连接多个客户端。
  • 优化4:允许绑定地址快速重用。

3、几个函数

1、IP地址转换函数
1、 本地字节序转化为网络字节序

 #include <arpa/inet.h>
 int inet_pton(int af, const char *src, void *dst);

  • af:地址协议族(IPV4:4:AF_INET或IPV6::AF_INET6)
  • src:是一个指针,填写点分形式的IP地址
  • dest:转换的结果给dst
  • 在服务器端Bind()函数绑定IP地址和端口号,客户端connect()函数填充IP地址和端口号时,是将本地字节序转化为网络字节序。
2、网络字节序转化为本地字节序

 #include <arpa/inet.h>
 int inet_ntop(int af, const char *src, void *dst, socklen_t size);
  • af:参数同上
  • src:从结构体中读取IP地址。该结构体中有IP地址和端口号等信息。
  • dst:读取到的IP地址保存的地址,如果是IPV4,结果为点分形式。
  • 结构体大小
  • 服务器端读取客户端IP地址和端口号时,需要将网络字节序转化为本地字节序。
2、端口字节序转化
       #include <arpa/inet.h>

       uint32_t htonl(uint32_t hostlong);    //本地字节序到网络字节序 4字节

       uint16_t htons(uint16_t hostshort);  //本地字节序到网络字节序 2字节

       uint32_t ntohl(uint32_t netlong);   //网络字节序到本地字节序  4字节

       uint16_t ntohs(uint16_t netshort); // 网络字节序到本地字节序 2字节

3、进程创建函数
 #include <sys/types.h>
 #include <unistd.h>
  pid_t fork(void);
  • 创建一个新的进程,新进程为当前进程的子进程。fork()通过返回值来判断进程是在子进程中,还是父进程中。
  • 返回值 = 0,在子进程中; 返回值 <0,创建子进程失败;返回值 >0,在父进程。
  • 子进程继承父进程的内容。子进程先结束时,父进程需要及时回收。

4、进程回收函数

 #include <sys/types.h>
 #include <sys/wait.h>
 pid_t waitpid(pid_t pid, int *wstatus, int options);

  • pid=-1 等待任何子进程,此时的waitpid()函数就退化成了普通的wait()函数。
  • wstatus指定用于保存子进程返回值和结束方式的地址。
  • options 指定回收方式,0或WNOHANG。WNOHANG为非阻塞方式。
5、线程创建函数
 #include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);

  • 参数thread为指向线程标识符的指针。
  • attr 为线程属性,NULL表示默认。
  • start_routine 线程执行函数。
  • arg为传递给执行函数的参数。

多线程实现-服务器端

#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <strings.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <errno.h>   /*与error相关的*/
#include <arpa/inet.h>
#define  SERV_PORT 5001
#define  SERV_IP_ADDR "192.168.192.143"
#define  BACKLOG  5
#define  QUIT_STR "quit"

void  cli_data_handle(void * arg);
int main()
{
        int fd =-1;
        struct sockaddr_in sin;
        /*第一步:创建socket fd*/
        if( (fd = socket(AF_INET,SOCK_STREAM,0)) < 0){
                perror("socket");
                exit(1);
        }
                /*优化4:允许绑定地址快速重用*/
        int b_reuse = 1;
        setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&b_reuse,sizeof(int));

        /*第二步:绑定*/
        /*填充struct sockaddr_in结构体变量*/
        bzero(&sin,sizeof(sin)); //清空结构体
        sin.sin_family = AF_INET;//TCP/IP协议族
        sin.sin_port = htons(SERV_PORT); //转化为网络字节序端口号
        /*优化1:让服务器程序可以绑定在任何的IP地址上*/
#if 1
        sin.sin_addr.s_addr = htonl(INADDR_ANY);
#else
        if(inet_pton(AF_INET,SERV_IP_ADDR,&sin.sin_addr) != 1){//ip地址转化为网络字节序
                perror("inet_pton");
                exit(1);
        }
#endif
        /*绑定,强制转化为struct sockaddr结构体*/
        if(bind(fd,(struct sockaddr *)&sin,sizeof(sin)) < 0){
                perror("bind error");
                exit(1);
        }
        /*第三步:调用listen()把主动套接字变成被动套接字*/
        if(listen(fd,BACKLOG) < 0){ //BACKLOG 通常为 5
                perror("listen");
                exit(1);
        }
        /*第四步:阻塞等待客户端连接请求*/
        printf("Server starting......OK\\n");
        int newfd = -1;
#if 0
        newfd = accept(fd,NULL,NULL);//和客户端连接成功时,返回一个新的newfd
        if(newfd < 0){
                perror("accept");
                exit(1);
        }
#else
        /*优化2:通过程序获取刚建立的socket的客户端的IP地址和端口号*/
        pthread_t  tid;
        struct sockaddr_in cin; //用于存放客户端的信息
        socklen_t addrlen = sizeof(cin);
        while(1){
        if((newfd = accept(fd,(struct sockaddr*)&cin,&addrlen)) < 0){
                perror("accept");
                exit(1);
        }
        char ipv4_addr[16];/*字符串数组,用于存放转化后的IP地址*/
        if(! inet_ntop(AF_INET,(void *)&cin.sin_addr,ipv4_addr,sizeof(cin))){
                perror("inet_ntop");
                exit(1);
        }
        printf("Client(%s:%d) is connected!\\n",ipv4_addr,ntohs(cin.sin_port));
        pthread_create(&tid,NULL,(void *)cli_data_handle,(void*)&newfd);//创建线程,传入函数的参数为newfd
        }

        close(fd);
}
#endif
void  cli_data_handle(void * arg){

        int newfd = *(int*)arg;  //先强制转换为int型指针,再取值
        printf("handler thread:newfd = %d\\n",newfd);/**/

        int ret = -1;
        char buf[BUFSIZ];
        while(1){
                bzero(buf,BUFSIZ);
                do{
                ret  = read(newfd,buf,BUFSIZ-1);//读取客户端写入到BUF中的数据
                }while(ret < 0 && EINTR == errno);
                if(ret < 0){  // 出错
                        perror("read");
                        exit(1);
                }
                if(!ret){ //没有读到数据时,退出当前循环
                        break;
                }
                printf("Receive data(client fd:%d):%s\\n",newfd,buf);
                if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR))){//相等时 返回0
                     printf("Client(fd = %d) is exiting!\\n",newfd);  //该比较函数忽略字母大小写
                      break;
                }
     }
        close(newfd);/*退出循环后,关闭*/

}

多进程实现 服务器端

  1 #include <stdio.h>
  2 #include <pthread.h>
  3 #include <string.h>
  4 #include <unistd.h>
  5 #include <stdlib.h>
  6 #include <string.h>
  7 #include <unistd.h>
  8 #include <stdlib.h>
  9 #include <sys/types.h>
 10 #include <sys/wait.h>
 11 #include <sys/socket.h>
 12 #include <strings.h>
 13 #include <netinet/in.h>
 14 #include <netinet/ip.h> /* superset of previous */
 15 #include <signal.h>
 16 #include <errno.h>   /*与error相关的*/
 17 #include <arpa/inet.h>
 18 #define  SERV_PORT 5001
 19 #define  SERV_IP_ADDR "192.168.192.143"
 20 #define  BACKLOG  5
 21 #define  QUIT_STR "quit"
 22
 23 void  cli_data_handle(void * arg);
 24         /*回收子进程*/
 25 void sig_child_handle(int signo){
 26         if(SIGCHLD == signo){ //子进程暂停或终止时产生,父进程将收到
 27         waitpid(-1, NULL, WNOHANG);
 28         }
 29
 30 }
 31 int main()
 32 {
 33         int fd =-1;
 34         struct sockaddr_in sin;
 35
 36         signal(SIGCHLD,sig_child_handle);
 37
 38         /*第一步:创建socket fd*/
 39         if( (fd = socket(AF_INET,SOCK_STREAM,0)) < 0){
 40                 perror("socket");
 41                 exit(1);
 42         }
 43         /*优化4:允许绑定地址快速重用*/
 44         int b_reuse = 1;
 45         setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&b_reuse,sizeof(int));
 46         /*第二步:绑定*/
 47         /*填充struct sockaddr_in结构体变量*/
 48         bzero(&sin,sizeof(sin)); //清空结构体
 49         sin.sin_family = AF_INET;//TCP/IP协议族
 50         sin.sin_port = htons(SERV_PORT); //转化为网络字节序端口号
 51         /*优化1:让服务器程序可以绑定在任何的IP地址上*/
 52 #if 1
 53         sin.sin_addr.s_addr = htonl(INADDR_ANY);
 54 #else
 55         if(inet_pton(AF_INET,SERV_IP_ADDR,&sin.sin_addr) != 1){//ip地址转化为网络字节序
 56                 perror("inet_pton");
 57                 exit(1);
 58         }
 59 #endif
 60         /*绑定,强制转化为struct sockaddr结构体*/
 61         if(bind(fd,(struct sockaddr *)&sin,sizeof(sin)) < 0){
 62                 perror("bind error");
 63                 exit(1);
 64         }
 65         /*第三步:调用listen()把主动套接字变成被动套接字*/
 66         if(listen(fd,BACKLOG) < 0){ //BACKLOG 通常为 5
 67                 perror("listen");
 68                 exit(1);
 69         }
 70         /*第四步:阻塞等待客户端连接请求*/
 71         printf("Server starting......OK\\n");
 72         int newfd = -1;
 73 #if 0
 74         newfd = accept(fd,NULL,NULL);//和客户端连接成功时,返回一个新的newfd
 75         if(newfd < 0){
 76                 perror("accept");
 77                 exit(1);
 78         }
 79 #else
 80         /*优化2:通过程序获取刚建立的socket的客户端的IP地址和端口号*/
 81
 82         //pthread_t  tid;
 83         struct sockaddr_in cin;
 84         socklen_t addrlen = sizeof(cin);
 85         while(1){
 86         pid_t pid = -1;
 87         if((newfd = accept(fd,(struct sockaddr*)&cin,&addrlen)) < 0){
 88                 perror("accept");
 89                 exit(1);
 90         }
 91
 92         if((pid = fork()) < 0){
 93                 perror("fork");
 94                 break;
 95         }
 96         if( 0 == pid)//子进程
 97         {
 98
 99         close(fd);//不用fd
100         char ipv4_addr[16];/*字符串数组,用于存放转化后的IP地址*/
101         if(! inet_ntop(AF_INET,(void *)&cin.sin_addr,ipv4_addr,sizeof(cin))){
102                 perror("inet_ntop");//从网络字节序转化为本地字节序
103                 exit(1);
104              }   
105         printf("Client(%s:%d) is connected!\\n",ipv4_addr,ntohs(cin.sin_port));
106         cli_data_handle(&newfd);
107         return 0;
108         }
109         else{   //pid > 0,父进程
110           close(newfd);
111         }
112
113         }
114 }
115 #endif
116 void  cli_data_handle(void * arg){
117
118         int newfd = *(int*)arg;  //先强制转换为int型指针,再取值
119         printf("child handling process:newfd = %d\\n",newfd);/**/
120         int ret = -1;
121         char buf[BUFSIZ];
122         while(1){
123                 bzero(buf,BUFSIZ);
124                 do{
125                 ret  = read(newfd,buf,BUFSIZ-1);//读取客户端写入到BUF中的数据
126                 }while(ret < 0 && EINTR == errno);
127                 if(ret < 0){  // 出错
128                         perror("read");
Linux下的TCP/IP编程----进程及多进程服务端

多线程 udp通讯和 tcp通讯

day08 多线程socket 编程,tcp粘包处理

Python网络编程 -- TCP/IP

day8---多线程socket 编程,tcp粘包处理

Java 网络编程--------------------基于TCP/IP(加入多线程)