网络编程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编程----进程及多进程服务端