(一)多进程并发服务器基础知识
(1) 多进程并发服务器流程图
(2)涉及函数
2.1 getopt_long()函数
getopt_long()函数:长选项的命令行解析。
目的:就是可以指定任意IP,PORT,避免经常在文件中修改。
#include <getopt.h> //头文件包含
int getopt_long(int argc, char * const argv[],const char *optstring, const struct option *longopts,int *longindex);
参数:
argc和argv[]:用来接收main函数的参数。
optstring:一个字符串,表示可以接受的参数。例如,“a:b:cd”,表示可以接受的参数是a,b,c,d,其中,a和b参数后面跟有更多的参数值。(例如:-a host -b name)。
longopts:一个结构的实例。
longindex:表示当前长参数在longopts中的索引值。
示例:
getopt_long(argc,argv,"p:h",opts,NULL)
2.1.1 option结构体
struct option {
const char *name;
int has_arg;
int *flag;
int val;
}
参数:
name:表示的是长参数名
has_arg(3个值):
①no_argument(或者是0),表示该参数后面不跟参数值
②required_argument(或者是1),表示该参数后面一定要跟个参数值
③optional_argument(或者是2),表示该参数后面可以跟,也可以不跟参数值
flag:用来决定,getopt_long()的返回值到底是什么。如果flag是null(通常情况),则函数会返回与该项option匹配的val值;如果flag不是NULL,则将val值赋予flag所指向的内存,并且返回值设置为0。
val:与flag联合决定返回值
示例:
struct option opts[]={
{"port",required_argument,NULL,'p'},
{"help",no_argument,NULL,'h'},
{0,0,0,0}
};
2.2 setsockopt()函数
setsockopt()函数::用于任意类型、任意状态套接口的设置选项值。尽管在不同协议层上存在选项,但本函数仅定义了最高的“套接口”层次上的选项。
#include <sys/types.h> //包含的头文件
#include <sys/socket.h> //包含的头文件
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
参数 | 功能 |
---|---|
sockfd | 指定套接字的描述符 |
level | 选项定义的层次。例如:SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6等 |
optname | 设置的选项名 |
optval | 指针,指向存放选项待设置的新值的缓冲区(布尔型/整型或结构选项) |
optlen | optval缓冲区长度 |
setsockopt有多种用法,这里我只讲与下面编程有关的用法。
示例:
int on=1;
setsockopt(sock_fd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
目的:当我们运行了程序,并退出后,端口并不会立即释放(会经理TIME_WAIT过程),此时使用setsockopt()则可以实现端口的重用。这样就不会出现经常看到的Address already in use现象了。
setsockopt设置sockfd的用法可以参考下面的博客:
https://blog.csdn.net/godleading/article/de8102814tails/
(二)多进程并发服务器的编程
(1) 用getopt_long实现长选项命令行解析
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<getopt.h>
#define MSG_STR "Hello socket process world \n"
//help printf
void print_usage(char *progname)
{
printf("%s usage:\n ",progname);
printf("-p(--port):sepcify server listen port.\n");
printf("-h(--help):print this help information.\n");
return ;
}
int main(int argc,char **argv)
{
int sock_fd=-1;
int cli_fd=-1;
int rv=-1;
struct sockaddr_in servaddr;
struct sockaddr_in cliaddr;
socklen_t len;
pid_t pid;
int ch;
int on=1;
int port=0;
//write a option to give help
struct option opts[]={
{"port",required_argument,NULL,'p'},
{"help",no_argument,NULL,'h'},
{0,0,0,0}
};
while((ch=getopt_long(argc,argv,"p:h",opts,NULL))!=-1)
{
switch(ch)
{
case'p':
port=atoi(optarg);
break; //不能少,要跳出循环
case'h':
print_usage(argv[0]);
return 0;
}
}
if(!port)
{
print_usage(argv[0]);
return 0;
}
简析:这里简单来说就是做了提供帮助,指定端口的功能。
(2) server创建的四步骤
//1.socket
sock_fd=socket(AF_INET,SOCK_STREAM,0);
if(socket<0)
{
printf("Crate socket failure:%s\n",strerror(errno));
return -1;
}
else
{
printf("Create socket[%d],successfully!\n",sock_fd);
}
setsockopt(sock_fd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));//
//2.bind
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(port);
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
if(rv=bind(sock_fd,(struct sockaddr *)&servaddr,sizeof(servaddr))<0);
{
printf("Socket[%d] bind on port [%d] failure:%s\n",sock_fd,port,strerror(errno));
return -2;
}
//3.listen
listen(sock_fd,13); //队列
printf("Start to listen on port [%d]\n",port);
while(1)
{
printf("Start accept new client incoming..\n");
//4.accept
cli_fd=accept(sock_fd,(struct sockaddr *)&cliaddr,&len);
if(cli_fd<0)
{
printf("Accept new client failure:%s\n",strerror(errno));
return -3;
}
else
{
printf("Accept new client [%s:%d] suceesssfuly\n",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));
}
(3) 当接收客服端连接后创建子进程并进行操作
//子进程:fork()
pid=fork();
if(pid<0) //创建子进程失败
{
printf("fork() crate child process failure :%s\n",strerror(errno));
close(cli_fd);
continue;
}
else if(pid>0) //返回给父进程
{
close(cli_fd);
continue;
}
else if(0==pid) //创建子进程中,并且进行I/O操作读取客户端cli_fd
{
char buf[1024];
close(sock_fd); //
printf("Child process start to commuicate with socket client..\n");
//IO操作读写操作
memset(buf,0,sizeof(buf));
rv=read(cli_fd,buf,sizeof(buf));
if(rv<0)
{
printf("read data from client sockfd[%d] failure:%s\n",cli_fd,strerror(errno));
close(cli_fd);
exit(0); //退出
}
else if(rv==0)
{
printf("Socket[%d] get disconnected\n",cli_fd);
close(cli_fd);
exit(0);
}
else if(rv>0)
{
printf("read %d bytes data from server:%s\n",rv,buf);
}
rv=write(cli_fd,MSG_STR,strlen(MSG_STR));
if(rv<0)
{
printf("write to client by sockfd[%d] failure:%d\n",sock_fd,strerror(errno));
close(cli_fd);
exit(0);
}
sleep(1); //一般不这样处理
printf("close client socket[%d] and child process exit\n",cli_fd);
close(cli_fd);
exit(0);
}
}
close(sock_fd);
return 0;
}
(4) 没有客户端连接运行的结果
./socket_process_server -p 22