目录
程序流程
程序实例
运行结果
本例主要是让服务器能够同时处理多个客户端的连接请求。
程序流程
1. 创建基本的套接字,并绑定地址信息、设置监听;
2. 循环accept来接收连接请求,每接收一个连接请求,就创建一个子进程;
3. 在子进程中进行客户端与服务端的数据通信;
4. 在父进程中定义一个消息处理程序,用来等待子进程结束,从而防止僵尸进程的产生。
程序实例
#include<iostream>
#include<unistd.h>
#include<string.h>
#include<arpa/inet.h>
#include<errno.h>
#include<signal.h>
#include<sys/socket.h>
#include<sys/wait.h>
using namespace std;
#define SERV_IP "127.1.2.3"
#define SERV_PORT 8888
struct socket_info //将文件描述符与地址结构体绑定在一起
{
struct sockaddr_in sktaddr;
int fd;
};
void sigChild(int sig) //消息处理函数,当子进程退出时执行,用来回收子进程,防止僵尸进程
{
while(waitpid(-1,NULL,WNOHANG) > 0); //最后一个参数用WNOHANG,意味父进程非阻塞式等待子进程结束
}
int main()
{
socket_info listenskt,connskt; //一个用于服务端监听,一个用于服务端收发
pid_t pid;
socklen_t clit_size ;
//注册信号,使用sigaction
struct sigaction newac; //先创建一个sigaction结构体,用来封装信号的处理方式,掩码等信息
newac.sa_handler = sigChild; //指定信号发生时调用句柄(用函数名即可)
sigemptyset(&newac.sa_mask); //清空掩码,掩码为空表示无屏蔽信号
newac.sa_flags = 0;
sigaction(SIGCHLD,&newac,NULL); //注册信号
memset(&listenskt,0,sizeof(listenskt)); //置0
memset(&connskt,0,sizeof(connskt));
if((listenskt.fd = socket(AF_INET,SOCK_STREAM,0)) == -1) //创建套接字
{
cout<<"socket create failed : "<<strerror(errno)<<endl;
return 0;
}
//绑定地址信息
listenskt.sktaddr.sin_family = AF_INET;
listenskt.sktaddr.sin_port = htons(SERV_PORT);
listenskt.sktaddr.sin_addr.s_addr = inet_addr(SERV_IP);
if(bind(listenskt.fd,(sockaddr *)&listenskt.sktaddr,sizeof(listenskt.sktaddr)) == -1 ) //绑定
{
cout<<"bind error : "<<strerror(errno)<<endl;
return 0;
}
if(listen(listenskt.fd,100) == -1) //监听
{
cout<<"listen error : "<<strerror(errno)<<endl;
return 0;
}
cout<<"Init Success ! "<<endl;
cout<<"host ip : "<<inet_ntoa(listenskt.sktaddr.sin_addr)<<" port : "<<ntohs(listenskt.sktaddr.sin_port)<<endl;
cout<<"Waiting for connections ... "<<endl;
while(1)
{
clit_size = sizeof(connskt.sktaddr);
if( (connskt.fd = accept(listenskt.fd,(sockaddr *)&connskt.sktaddr,&clit_size) ) == -1 ) //阻塞接收连接请求
{
if(errno == EINTR)continue ;
/*必须加上这一句,当一个客户端断开时(比如说按下CTRL+C),
父进程和子进程都会引发中断异常,也就是这里的EINTR,父进
程通过这句话来重新accept,不然就会直接退出*/
cout<<"accept error : "<<strerror(errno)<<endl;
return 0;
}
//连接成功
cout<<inet_ntoa(connskt.sktaddr.sin_addr)<<":"<<ntohs(connskt.sktaddr.sin_port)<<" connected ... "<<endl; //显示连接客户端信息
//创建进程
if((pid = fork()) == 0) //子进程
{
close(listenskt.fd); //子进程中监听套接字无用,直接关闭
while(1) //进行数据接收
{
char buf[1024];
int readstate = read(connskt.fd,buf,sizeof(buf));
if(readstate == 0)
{
cout<<inet_ntoa(connskt.sktaddr.sin_addr)<<":"<<ntohs(connskt.sktaddr.sin_port)<<" exit ... "<<endl;
break;
}
else if(readstate < 0)
{
cout<<"connect error : "<<strerror(errno)<<endl;
break;
}
write(STDOUT_FILENO,buf,readstate); //显示收到的信息
cout<<" (From "<<inet_ntoa(connskt.sktaddr.sin_addr)<<":"<<ntohs(connskt.sktaddr.sin_port) <<")"<<endl; //显示信息源地址
for(int i=0;i<readstate;i++)
buf[i] = toupper(buf[i]); // 转为大写后发回
write(connskt.fd,buf,readstate);
}
close(connskt.fd); //子进程结束前关闭连接
return 0; //子进程退出
}
else if(pid > 0) //父进程
{
close(connskt.fd);
}
else //进程创建失败
{
cout<<"fork error : "<<strerror(errno)<<endl;
}
}
return 0;
}
运行结果
客户端依旧使用之前的,先运行服务器:
然后再新建多个终端,运行多个客户端:(左上为服务端,其余三个为客户端)
然后在客户端发送消息测试:
客户端退出: