一、多进程并发服务器
实现一个服务器可以连接多个客户端,每当accept函数等待到客户端进行连接时 就创建一个子进程
思路分析:
核心思路:让accept循环阻塞等待客户端,每当有客户端连接时就fork子进程,让子进程去和客户端进行通信,父进程用于监听并使用信号捕捉回收子进程(子进程关闭用于监听的套接字lfd,父进程关闭用于通信的cfd)
服务端server.c代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<ctype.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<strings.h>
#include<errno.h>
#include<pthread.h>
#include<signal.h>
#include<sys/wait.h>
#define SERV_PORT 9527
void sys_err(const char *str)
{
perror(str);
exit(1);
}
void catch_child(int signum)
{
while(waitpid(0,NULL,WNOHANG) > 0);
return ;
}
int main(int argc,char *argv[])
{
int lfd = 0,cfd = 0;
pid_t pid;
int ret;
char buf[BUFSIZ],client_IP[1024];//4096
struct sockaddr_in serv_addr,clit_addr;
socklen_t clit_addr_len;
bzero(&serv_addr,sizeof(serv_addr));//将地址结构清零
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
lfd = socket(AF_INET,SOCK_STREAM,0);
if (lfd == -1)
{
sys_err("socket errno");
}
bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
listen(lfd,128);
clit_addr_len = sizeof(clit_addr);
while(1)
{
cfd = accept(lfd,(struct sockaddr *)&clit_addr,&clit_addr_len);
if (cfd == -1)
{
sys_err("accept error");
}
printf("client ip is:%s port:%d\n",inet_ntop(AF_INET,&clit_addr.sin_addr.s_addr,client_IP,sizeof(client_IP)),ntohs(clit_addr.sin_port));
pid = fork();
if (pid < 0)
{
sys_err("fork error");
}
else if (pid == 0)
{
close(lfd);
break;
}
else
{
struct sigaction act;
act.sa_handler = catch_child;
sigemptyset(&(act.sa_mask));
act.sa_flags = 0;
ret = sigaction(SIGCHLD,&act,NULL);
if (ret != 0)
{
sys_err("sigaction error");
}
close(cfd);
continue;
}
}
if (pid == 0)
{
for(;;)
{
ret = read(cfd,buf,sizeof(buf));
if (ret == 0)
{
close(cfd);
exit(1);
}
for (int i = 0;i<ret;i++)
{
buf[i] = toupper(buf[i]);
}
write(cfd,buf,ret);
write(STDOUT_FILENO,buf,ret);
}
}
return 0;
}
一个服务端三个客户端执行多进程服务器并发输出如下:
补充:
二、多线程并发服务器:
思路分析:
核心思路:设置监听循环由主线程阻塞等待客户端连接,每当接受到新的客户端连接时,主线程会创建一个新的子线程来处理与该客户端的通信,而主线程继续执行监听循环等待其他客户端(主线程专注于接受新连接,子线程专注于处理客户端请求)
服务端server.c代码如下:
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include "wrap.h"
#define MAXLINE 8192
#define SERV_PORT 8000
struct s_info { //定义一个结构体, 将地址结构跟cfd捆绑
struct sockaddr_in cliaddr;
int connfd;
};
void *do_work(void *arg)
{
int n,i;
struct s_info *ts = (struct s_info*)arg;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN]; //#define INET_ADDRSTRLEN 16 可用"[+d"查看
while (1) {
n = Read(ts->connfd, buf, MAXLINE); //读客户端
if (n == 0) {
printf("the client %d closed...\n", ts->connfd);
break; //跳出循环,关闭cfd
}
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)),
ntohs((*ts).cliaddr.sin_port)); //打印客户端信息(IP/PORT)
for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]); //小写-->大写
Write(STDOUT_FILENO, buf, n); //写出至屏幕
Write(ts->connfd, buf, n); //回写给客户端
}
Close(ts->connfd);
return (void *)0; //pthread_exit(0);
}
int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
pthread_t tid;
struct s_info ts[256]; //创建结构体数组.
int i = 0;
listenfd = Socket(AF_INET, SOCK_STREAM, 0); //创建一个socket, 得到lfd
bzero(&servaddr, sizeof(servaddr)); //地址结构清零
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //指定本地任意IP
servaddr.sin_port = htons(SERV_PORT); //指定端口号
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); //绑定
Listen(listenfd, 128); //设置同一时刻链接服务器上限数
printf("Accepting client connect ...\n");
while (1) {
cliaddr_len = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); //阻塞监听客户端链接请求
ts[i].cliaddr = cliaddr; //把文件描述符和客户端地地址结构组织到一个结构体变量中
ts[i].connfd = connfd;
pthread_create(&tid, NULL, do_work, (void*)&ts[i]);//表结构体变量作为参数传递到子线程
pthread_detach(tid); //子线程分离,防止僵线程产生.
i++;
}
return 0;
}
执行输出如下: