pid_t fork(void);//创建一个进程,返回一个文件描述符
//文件描述符<0(或者==-1),进程创建失败
//文件描述符==0,表示为子进程
//文件描述符>0,表示为父进程
int main ()
{
pid_t fpid; //fpid表示fork函数返回的值
int count = 0;
fpid = fork();
if (fpid < 0)
printf("进程创建失败");
else if (fpid == 0){//子进程
printf("我是子进程/n");
count++;
}
else{//fpid>0,父进程
printf("我是父进程/n");
count++;
}
printf("统计结果是: %d/n", count);
return 0;
}
//运行结果:
我是子进程
统计结果是:1
我是父进程
统计结果是:1
/*注意哦
两个进程变量不共用(count最后结果为1),相当于完全复制了一份*/
在语句fpid=fork()之前,只有一个进程在执行这段代码,但在这条语句之后,就变成两个进程在执行了,这两个进程的几乎完全相同,将要执行的下一条语句都是if(fpid<0)……
为什么两个进程的fpid不同呢,这与fork函数的特性有关。
fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
1)在父进程中,fork返回新创建子进程的进程ID;对应父进程中fork返回值>0
2)在子进程中,fork返回0;对应子进程中fork返回值==0
3)如果出现错误,fork返回一个负值;
在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。
利用fork创建多个进程,实现并发服务器。父进程用于客户端的请求连接,所以关闭父进程的connfd,子进程用于服务器客户端的通信,因此子进程关闭listenfd
server.cpp
//server.cpp
#include<iostream>
#include<string>
#include<string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<netdb.h>
#include<arpa/inet.h>
#include<netinet/in.h>
using namespace std;
int main(int argc, char *argv[]){
//1.创建套接字socket
int listenfd;
if((listenfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
cout<<"make socket error!"<<endl;
//2.创建ipv4地址,并和socket绑定
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(5188);
servaddr.sin_addr.s_addr = inet_addr("192.168.177.128");//my(seirver) addr
//servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//本机的任意地址
int on = 1;
if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0)
cout<<"setsockopt reuseaddr error!"<<endl;
if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
cout<<"bind error!"<<endl;
//3.监听
if(listen(listenfd,10)<0)
cout<<"listen error!"<<endl;
//4.accept,等待连接(阻塞)
struct sockaddr_in clientaddr;
socklen_t clientlen =sizeof(clientaddr);
//socklen_t是一种数据类型,和int差不多
//32位下,size_t和int的长度相同,都是32 bits,但64位下,size_t(32bits)和int(64 bits)长度不同
//socket编程中的accept函数的第三个参数的长度必须和int的长度相同。于是有了socklen_t类型。
int connfd;
pid_t pid;
while(1){
if((connfd=accept(listenfd,(struct sockaddr*)&clientaddr,&clientlen))<0)
cout<<"accept error!"<<endl;
cout<<"New Connect"<<"\tClient ip:"<<inet_ntoa(clientaddr.sin_addr);
cout<<"\tClient port:"<<ntohs(clientaddr.sin_port)<<endl;
pid = fork();
if(pid==0){//子进程
close(listenfd);
//5.data transfer
char rec[1024];
int iconnect = 0;
while(1){
memset(rec,0,sizeof(rec));
int readlen = read(connfd,rec,sizeof(rec));//set 接收字符串的长度
//cout<<"接收的数据长度:"<<set<<endl;
if(readlen==0){//接收的数据长度为0时,退出子进程
cout<<"Close"<<"\tClient ip:"<<inet_ntoa(clientaddr.sin_addr);
cout<<"\tClient port:"<<ntohs(clientaddr.sin_port)<<endl;
close(connfd);
exit(0);//退出子进程
}
cout<<"From ip:"<<inet_ntoa(clientaddr.sin_addr);
cout<<" port:"<<ntohs(clientaddr.sin_port)<<" ";
cout<<rec<<endl;
write(connfd,rec,readlen);
}
}
else if(pid>0){//父进程
close(connfd);
}
else cout<<"fork error!"<<endl;//pid<0,进程创建失败
}
close(listenfd);
close(connfd);
cout<<"over"<<endl;
return 0;
}
client.cpp
//client.cpp
#include<iostream>
#include<string>
#include<string.h>
#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<netdb.h>
#include<arpa/inet.h>
#include<netinet/in.h>
using namespace std;
int main(int argc,char* argv[]){
//1.make socket
int sockfd;
if((sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
cout<<"make socket error!"<<endl;
//2.make ip addr and connect
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
//void *memset(void *s,int c,size_t n)
//总的作用:将已开辟内存空间 s 的首 n 个字节的值设为值 c。
servaddr.sin_family= AF_INET;
servaddr.sin_port = htons(5188);
servaddr.sin_addr.s_addr = inet_addr("192.168.177.128");
if(connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
cout<<"connect error!"<<endl;
//3.after connect
char sendbuf[1024]= {0};
char recvbuf[1024]= {0};
while(1){
cin>>sendbuf;
cout<<strlen(sendbuf)<<endl;
write(sockfd,sendbuf,strlen(sendbuf));
read(sockfd,recvbuf,sizeof(recvbuf));
cout<<recvbuf<<endl;
memset(sendbuf,0,sizeof(sendbuf));
memset(recvbuf,0,sizeof(recvbuf));
}
close(sockfd);
return 0;
}
thread()
多进程并发服务器:主线程处理listenfd事件,每有一个新的连接创建一个子进程去处理。
多线程并发服务器:主线程处理listenfd事件,每有一个新的连接创建一个子线程去处理。
区别:
每个进程都有自己的资源空间(不共用),在创建子进程时会先自动把父进程的资源全拷贝过来。
子线程和主线程共用一个资源空间,每一次新来的连接都会覆盖客户端信息,所以每个子线程因该自己开辟一个空间存储对应的用户信息(在被覆盖前,从主线程拷贝过来)
//pthread_create是UNIX环境创建线程的函数
#include <pthread.h>
int pthread_create(pthread_t* tid,const pthread_attr_t* attr,void* (*start_rtn)(void*),void* arg);
tidp:事先创建好的pthread_t类型的参数。成功时tid指向的内存单元被设置为新创建线程的线程ID
attr:用于定制各种不同的线程属性,通常直接设为NULL
start_rtn:新创建线程从此函数开始运行,无参数是arg设为NULL即可
arg:start_rtn函数的参数。无参数时设为NULL即可。有参数时输入参数的地址。当多于一个参数时应当使用结构体传入。
返回值:成功返回0,否则返回错误码。
//例子
pthread_t tid;
void do_work();//函数
pthread_create(&tid, NULL, do_work, (void*)&ts);
//线程号tid,调用do_work()函数,将数据ts传给do_work()函数
//针对pthread_create()中第二个参数设置线程分离属性,这样在线程结束时,回收线程资源
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
//设置线程分离属性,将设置好的attr传入,在线程结束时,回收线程资源
pthread_create(&tid,&attr,do_work,&cliInfo);
由于pthread库不是Linux系统默认的库,连接时需要使用库libpthread.a,所以在使用pthread_create创建线程时,在编译中要加-lpthread参数:
对‘pthread_create’未定义的引用
g++ -g mser_thread.cpp wrap.c -lpthread -o mser
server.cpp
#include<iostream>
#include<string>
#include<string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<netdb.h>
#include<pthread.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include"wrap.h"
using namespace std;
//高并发--多线程实现
struct cli_info{//pthread_create()只能传一个参数,所以报要传的数据写出一个结构体
int connfd;
struct sockaddr_in clientaddr;
};
void* do_work(void* arg){
struct cli_info *info = (struct cli_info*)arg;
int connfd=info->connfd;
struct sockaddr_in clientaddr=info->clientaddr;
char rec[256]={0};
while(1){
int readlen=read(connfd,rec,sizeof(rec));
if(readlen==0){//接收的数据长度为0时,退出
cout<<"Close"<<"\tClient ip:"<<inet_ntoa(clientaddr.sin_addr);
cout<<"\tClient port:"<<ntohs(clientaddr.sin_port)<<endl;
close(connfd);
break;
}
cout<<"From ip:"<<inet_ntoa(clientaddr.sin_addr);
cout<<" port:"<<ntohs(clientaddr.sin_port)<<" ";
cout<<rec<<endl;
write(connfd,rec,readlen);
}
}
int main(int argc, char *argv[]){
//1.创建套接字socket
int listenfd;
listenfd = Socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
//2.创建ipv4地址,并和socket绑定
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(5188);
servaddr.sin_addr.s_addr = inet_addr("192.168.109.128");//my(seirver) addr
//servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//本机的任意地址
int on = 1;
if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0)
cout<<"setsockopt reuseaddr error!"<<endl;
Bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
//3.监听
Listen(listenfd,10);
//4.accept,等待连接(阻塞)
struct sockaddr_in clientaddr;
socklen_t clientlen =sizeof(clientaddr);
int connfd;
cli_info cliInfo;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
while(1){
connfd=Accept(listenfd,(struct sockaddr*)&clientaddr,&clientlen);
cout<<"New Connect"<<"\tClient ip:"<<inet_ntoa(clientaddr.sin_addr);
cout<<"\tClient port:"<<ntohs(clientaddr.sin_port)<<endl;
cliInfo.connfd=connfd;
cliInfo.clientaddr=clientaddr;
pthread_t tid;
pthread_create(&tid,&attr,do_work,&cliInfo);
}
close(listenfd);
return 0;
}