IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> 网络编程TCP/IP协议(二)----- 多进程多线程服务器 -> 正文阅读

[网络协议]网络编程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");
129                         exit(1);
130                 }
131                 if(!ret){ //没有读到数据时,退出当前循环
132                         break;
133                 }
134                 printf("Receive data(client fd:%d):%s\n",newfd,buf);
135                 if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR))){//相等时 返回0
136                      printf("Client(fd = %d) is exiting!\n",newfd);  //该比较函数忽略字母大小写
137                       break;
138                 }
139      }
140      close(newfd);
141 }
142
143
~
~
~


客户端 执行时输入 ./client serv_ip serv_port

/*./client serv_ip serv_port*/
#include <stdio.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 usage(char *s)
{
        printf("\n%s serv_ip serv_port",s);
        printf("\n\t serv_ip:server ip address");
        printf("\n\t serv_port:server port(>5000)\n\n");
}
int main(int argc,char *argv[])
{
        int fd = -1;
        int port = -1;
        if(argc != 3){
                usage(argv[0]);
                exit(1);
}
        struct sockaddr_in sin;
        /*第一步:创建socket fd*/
        if( (fd = socket(AF_INET,SOCK_STREAM,0)) < 0){
                perror("socket");
                exit(1);
        }
        port  = atoi(argv[2]);  //输入的第三个参数 端口号
        if(port < 5000){
                usage(argv[0]);
                exit(1);
        }
        /*第二步:连接*/
        /*填充struct sockaddr_in结构体变量*/
        bzero(&sin,sizeof(sin)); //清空结构体
        sin.sin_family = AF_INET;//TCP/IP协议族
        sin.sin_port = htons(port); //转化为网络字节序
        if(inet_pton(AF_INET,argv[1],(void*)&sin.sin_addr) != 1){//ip地址转化为网络字节序
                perror("inet_pton");
                exit(1);
        }
        if(connect(fd,(struct sockaddr *)&sin,sizeof(sin)) < 0){
                perror("connect ");
                exit(1);
        }
        /*写入数据*/
        char buf[BUFSIZ];
        while(1){
        bzero(buf,BUFSIZ);
        if(fgets(buf,BUFSIZ-1,stdin) ==  NULL){  //键盘输入数据到buf
                continue;
        }
        write(fd,buf,strlen(buf));
        if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR))){//相等时 返回0
                        printf("Client is exiting!\n");  //该比较函数忽略字母大小写
                        break;
        }
 }

/*关闭*/
        close(fd);
}

4、编译运行

/*多进程实现 服务器端*/
book@100ask:~/socket$ ./server3
Server starting......OK
Client(192.168.192.143:36066) is connected!    //进程1
child handling process:newfd = 4
Receive data(client fd:4):welcome
Receive data(client fd:4):147
Receive data(client fd:4):Next change client

Client(192.168.192.143:36068) is connected! //进程2
child handling process:newfd = 4
Receive data(client fd:4):hello
Receive data(client fd:4):2021
Receive data(client fd:4):456
Receive data(client fd:4):quit
Client(fd = 4) is exiting!

Client(192.168.192.143:36070) is connected!//进程3
child handling process:newfd = 4
Receive data(client fd:4):11111
Receive data(client fd:4):22222
Receive data(client fd:4):quit

 /*多进程实现 客户端*/
book@100ask:~/socket$ ./client 192.168.192.143 5001  //输入的三个参数,client 1
welcome
147
Next change client
book@100ask:~/socket$ ./client 192.168.192.143 5001 //client 2
hello
2021
456
quit
Client is exiting!
book@100ask:~/socket$ ./client 192.168.192.143 5001//client 3
11111
22222
quit
Client is exiting!
/*多线程实现-服务器端*/
Client(192.168.192.143:36076) is connected!    //线程1
handler thread:newfd = 4
Receive data(client fd:4):qwe
Receive data(client fd:4):789

Client(192.168.192.143:36078) is connected!  //线程2
handler thread:newfd = 5
Receive data(client fd:5):000
Receive data(client fd:5):999

Client(192.168.192.143:36080) is connected!  //线程3
handler thread:newfd = 6
Receive data(client fd:6):abc
Receive data(client fd:6):711
/*多线程实现-客户端*/
book@100ask:~/socket$ ./client 192.168.192.143 5001 //client 1
qwe
789
book@100ask:~/socket$ ./client 192.168.192.143 5001//client 2
000
999
book@100ask:~/socket$ ./client 192.168.192.143 5001//client 3
abc
711


  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-08-11 12:47:32  更:2021-08-11 12:49:27 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/28 5:16:10-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计