🔥博客主页: 我要成为C++领域大神
🎥系列专栏:【C++核心编程】 【计算机网络】 【Linux编程】 【操作系统】
❤️感谢大家点赞👍收藏⭐评论✍️本博客致力于知识分享,与更多的人进行学习交流
对于常见的C/S模型,一个服务端通常需要服务多个客户端。如果使用单行的处理模型,当新的客户端请求服务端的服务时,就必须等待比它先到的客户端的请求全部完成。
因此引入多进程并发服务器模型。多进程并发服务器模型的简单流程图如下所示。父进程创建一个套接字,然后与自己的IP地址、端口号进行绑定。之后调用开始监听来自客户端的敲门,当有客户端来敲门时,accept()接收客户端的连接并创建一个新套接字用于与客户端通信。接下来调用fork()函数,当调用fork()函数时,操作系统会复制当前进程的一个副本,包括进程的代码、数据和状态等信息。如果其返回值为负数,表示创建子进程失败。否则他在父子进程中有不同的返回值:如果返回值为0,表示当前代码正在子进程中执行。如果返回值大于0,表示当前代码正在父进程中执行,返回的值是子进程的进程ID。因此可以使用if-else语句来编写子进程的处理代码。
在子进程中,先关闭从父进程中复制下来监听套接字,这个套接字在子进程中没有用了,纯属浪费资源,之后再进行与客户端的通信。而在父进程中,同理关闭accept()创建的新套接字,然后继续监听客户端的连接请求。
而同一进程的所有线程共享相同的内存空间,线程数据共享和通信更加方便,创建和管理线程的资源消耗较少,尤其是内存开销较小。由于线程间通信和进程间通信,多线程模型在处理大量任务时响应速度较快。
在多进程+多线程代码的基础上,将进行accept的进程修改为创建的线程工作,socket通信的功能放在线程工作函数内。
使用服务器测试业务:
客户端向标准输入发送小写字符串,服务端响应回复对应大写字符,"abcAS"->"ABCAS"
客户端向服务端发送关键字localtime,服务端响应回复系统时间、
服务端:
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <string.h>
#include <time.h>
#include <pthread.h>
#include <signal.h>
#include <errno.h>
#include <sys/wait.h>
#include <ctype.h>
#define _SERVER_IP "xxx.xxx.xxx.xxx"
#define _PORT 8080
#define _BACKLOG 128
#define _SHUTDOWN 1
#define _TRUE 1
#define _FALSE 0
#define _IPSIZE 16
#define _RECVLEN 1500
struct client_info
{
int client_fd;
struct sockaddr_in clientAddr;
};
void sig_wait(int n)
{
pid_t zpid;
while ((zpid = waitpid(-1, NULL, WNOHANG)) > 0)
{
printf("wait Thread Tid [0x%x] Wait Successfully,Zombie %d\n", (unsigned int)pthread_self(), zpid);
}
}
void *thread_wait(void *arg)
{
// 设定信号捕捉
pthread_detach(pthread_self());
struct sigaction act, oact;
act.sa_handler = sig_wait;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGCHLD, &act, &oact);
// 解除屏蔽
sigprocmask(SIG_SETMASK, &act.sa_mask, NULL);
printf("wait Thread [0x%x] is Waiting...\n", (unsigned int)pthread_self());
while (1)
sleep(1);
pthread_exit(NULL);
}
void *thread_recv(void *arg)
{
int recvlen;
char Result[_RECVLEN];
char client_ip[_IPSIZE];
time_t tp;
char time_buf[100]; // 存放当前系统时间
int toupper_flag;
struct client_info cinfo = *((struct client_info *)arg);
// Socket通信
bzero(Result, sizeof(Result));
bzero(client_ip, sizeof(client_ip));
inet_ntop(AF_INET, &cinfo.clientAddr.sin_addr.s_addr, client_ip, _IPSIZE);
printf("Connection From :IP[%s],PORT[%d]\n", client_ip, ntohs(cinfo.clientAddr.sin_port));
sprintf(Result, "Hi [%s] Welcome to my TCP test server!service version 1.1.0...", client_ip);
send(cinfo.client_fd, Result, strlen(Result), 0);
bzero(Result, sizeof(Result));
// 读取用户数据,如果用户发的是普通小写字符字符串,转换为大写,如果发送的是local关键字,响应时间
// 持续响应,循环读写
while ((recvlen = recv(cinfo.client_fd, Result, sizeof(Result), 0)) > 0)
{ // 处理客户端业务
printf("Client Say:%s\n", Result);
if (strcmp(Result, "localtime") == 0)
{
tp = time(NULL); // 获取时间种子
ctime_r(&tp, time_buf);
time_buf[strcspn(time_buf, "\n")] = '\0';
printf("[%s]Response SysTime Successfully!\n", client_ip);
send(cinfo.client_fd, time_buf, strlen(time_buf) + 1, 0);
}
else
{
toupper_flag = 0;
while (recvlen > toupper_flag)
{
Result[toupper_flag] = toupper(Result[toupper_flag]);
++toupper_flag;
}
printf("[%s]Response Toupper Successfully!\n", client_ip);
send(cinfo.client_fd, Result, recvlen, 0);
}
}
if (recvlen == 0) // 客户端退出
{
close(cinfo.client_fd);
printf("[%s] is Exiting,Kill Child\n", client_ip);
exit(0);
}
close(cinfo.client_fd);
}
int main()
{
struct sockaddr_in serverAddr, clientAddr;
int server_fd;
int client_fd;
// 主线程设置屏蔽
sigset_t set, oldset;
sigemptyset(&set);
sigaddset(&set, SIGCHLD);
sigprocmask(SIG_SETMASK, &set, &oldset);
pthread_t tid;
pthread_create(&tid, NULL, thread_wait, NULL); // 创建回收线程
socklen_t Addrlen;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(_PORT);
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
server_fd = socket(AF_INET, SOCK_STREAM, 0);
bind(server_fd, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
listen(server_fd, _BACKLOG);
printf("Test TCP Server Version 1.1.0 is Running...\n");
while (_SHUTDOWN)
{
Addrlen = sizeof(clientAddr);
if ((client_fd = accept(server_fd, (struct sockaddr *)&clientAddr, &Addrlen)) > 0)
{
struct client_info cinfo;
cinfo.client_fd = client_fd;
cinfo.clientAddr = clientAddr;
pthread_create(&tid, NULL, thread_recv, &cinfo); // 创建工作线程
}
else
{
// accept失败,测试中断
close(server_fd);
if (errno == EINTR)
{
printf("Accept ERROR Eintr...\n");
exit(0);
}
}
}
close(server_fd);
return 0;
}
客户端:
#ifndef _MYSOCK_H_
#define _MYSOCK_H_
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
int SOCKET(int domain, int type, int protocol);
int BIND(int sockfd, struct sockaddr* addr, socklen_t addrlen);
ssize_t RECV(int sockfd, void* buf, size_t len, int flags);
ssize_t SEND(int sockfd, void* buf, size_t len, int flags);
int CONNECT(int sockfd, struct sockaddr* addr, socklen_t addrlen);
int ACCEPT(int sockfd, struct sockaddr* addr, socklen_t* addrlen);
int LISTEN(int sockfd, int backlog);
char* FGETS(char* s, int size, FILE* stream);
int SELECT(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds,
struct timeval* timeout);
int socket_init();
int return_response(int clientfd, const char* clientip);
//void strDeal(int *client_fd);
// 全局变量声明
char recv_buf[1024];
char time_buf[64];
int serverFd, clientFd;
struct sockaddr_in clientAddr;
fd_set set, oset;
int client_array[1020];
int maxfd, ready;
socklen_t addrlen;
char clientip[16];
time_t tp;
ssize_t recvlen;
int toupper_flag;
#define SHUTDOWN 1
#endif
#include "MySock.h"
//客户端源码编写,连接服务器成功,服务器反馈信息
#define _IP "xxx.xxx.xxx.xxx"
#define _PORT 8080
int main()
{
struct sockaddr_in ServerAddr;
bzero(&ServerAddr,sizeof(ServerAddr));
ServerAddr.sin_family=AF_INET;
ServerAddr.sin_port=htons(_PORT);
inet_pton(AF_INET,_IP,&ServerAddr.sin_addr.s_addr);
int Myfd=SOCKET(AF_INET,SOCK_STREAM,0);
//看需求决定是否要绑定
char Response[1024];//存放服务端反馈信息
ssize_t recvlen;
bzero(Response,sizeof(Response));
char sendbuf[1024];
if((CONNECT(Myfd,(struct sockaddr *)&ServerAddr,sizeof(ServerAddr)))==0)
{
while(1)
{
if((recvlen=RECV(Myfd,Response,sizeof(Response),0))>0)
{
printf("%s\n",Response);
}
printf("Please Type Some text:");//读取标准输入发送给服务端
FGETS(sendbuf,sizeof(sendbuf),stdin);
sendbuf[strcspn(sendbuf,"\n")]='\0';
SEND(Myfd,sendbuf,sizeof(sendbuf),0);
}
}
close(Myfd);
printf("Client is Over\n");
return 0;
}