对于并发服务器,其核心思想是:每提取一个客户端请求,就创建一个子进程或子线程和客户端交互,这就就使得一个客户端对于一个子进程或者线程单独为其进行服务,当多个客户端同时请求服务器时,在服务器上就会有多个多个进程或者线程运行,此时通过CPU的调度算法可以让这些进程、线程轮流执行(单CPU),即:宏观上并行,微观上串行;或者对于多CPU的情况,就会出现真正的并行执行,此时,服务器可以响应各个客户端的请求,以上就是并发服务器的基本原理。
TCP的并发服务器可以使用多进程实现,也可以使用多线程实现;
1.多进程实现的基本流程如下:
socket(...);
bind(...);
listen(...);
while(1) {
accept(...);
if(fork(..)==0) {
process(...);
close(...);
exit(...);
}
close(...);
}
2.多线程实现的基本流程如下:
socket(...);
bind(...);
listen(...);
while(1)
{
pconnect_fd = malloc(sizeof(int));
*pconnect_fd = accept(...);
ret = pthread_create(&tid,NULL,do_client,pconnect_fd);
if(ret != 0){
}
pthread_detach(tid);
}
一、多进程实现代码
服务器代码:
点击(此处)折叠或打开
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <signal.h>
-
#include <sys/socket.h>
-
#include <netinet/in.h>
-
#include <arpa/inet.h>
-
-
-
int do_client(int connect_fd)
-
{
-
int n;
-
char buf[1024];
-
-
while(1)
-
{
-
n = read(connect_fd,buf,sizeof(buf) - 1);
-
if(n <= 0)
-
break;
-
-
buf[n] = '\0';
-
printf("Read %d bytes : %s\n",n,buf);
-
-
if(strncmp(buf,"quit",4)==0)
-
break;
-
}
-
-
//子进程中如果输入“quit”,则子进程必须要结束运行,然后让父进程进行收尸
-
exit(EXIT_SUCCESS);
-
}
-
-
//./server ip port
-
int main(int argc, const char *argv[])
-
{
-
int pid;
-
int sockfd;
-
int connect_fd;
-
struct sockaddr_in server_addr;
-
struct sockaddr_in peer_addr;
-
socklen_t addrlen = sizeof(struct sockaddr);
-
-
if(argc < 3)
-
{
-
fprintf(stderr,"Usage : %s
,argv[0]);\n"
-
exit(EXIT_FAILURE);
-
}
-
-
//防止僵尸态的子进程出现。
-
//设置SIGCHLD信号处理方式:忽略(告诉内核,子进程结束的时候,系统自动回收僵尸态)
-
if(signal(SIGCHLD,SIG_IGN) == SIG_ERR)
-
{
-
perror("Fail to signal");
-
exit(EXIT_FAILURE);
-
}
-
-
//1.创建流式套接字
-
sockfd = socket(AF_INET,SOCK_STREAM,0);
-
if(sockfd < 0){
-
perror("Fail to socket");
-
exit(EXIT_FAILURE);
-
}
-
-
//2.填充服务器的IP地址和端口信息
-
server_addr.sin_family = AF_INET;
-
server_addr.sin_port = htons(atoi(argv[2]));
-
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
-
//3.把服务器的IP地址和端口信息和套接字进行绑定
-
if(bind(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr)) < 0)
-
{
-
perror("Fail to bind");
-
exit(EXIT_FAILURE);
-
}
-
-
//4.设置套接字为监听模式
-
listen(sockfd,128);
-
-
printf("listen ...\n");
-
-
while(1)
-
{
-
//5.提取请求
-
connect_fd = accept(sockfd,(struct sockaddr *)&peer_addr,&addrlen);
-
if(connect_fd < 0){
-
perror("Fail to accept");
-
exit(EXIT_FAILURE);
-
}
-
-
printf("connect_fd : %d\n",connect_fd);
-
-
printf("-----------------------------\n");
-
printf("Port : %d\n",ntohs(peer_addr.sin_port));
-
printf("Ip : %s\n",inet_ntoa(peer_addr.sin_addr));
-
printf("-----------------------------\n");
-
-
//6.请求提取成功之后立刻创建子进程,让子进程来处理该客户端,如果有多个客户端请求,就
-
// 需要创建多个子进程。
-
pid = fork();
-
if(pid < 0){
-
perror("Fail to fork");
-
exit(EXIT_FAILURE);
-
}
-
-
if(pid == 0)
-
{
-
//子进程关闭拷贝的父进程中没用的文件描述符
-
close(sockfd);
-
//在子进程中读取客户端的数据
-
do_client(connect_fd);
-
}
-
-
//防止文件描述符浪费
-
close(connect_fd);
-
}
-
-
return 0;
- }
点击(此处)折叠或打开
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <sys/socket.h>
-
#include <netinet/in.h>
-
#include <arpa/inet.h>
-
-
//./server ip port
-
int main(int argc, const char *argv[])
-
{
-
int n;
-
int sockfd;
-
int connect_fd;
-
char buf[1024];
-
struct sockaddr_in server_addr;
-
struct sockaddr_in peer_addr;
-
socklen_t addrlen = sizeof(struct sockaddr);
-
-
if(argc < 3)
-
{
-
fprintf(stderr,"Usage : %s
,argv[0]);\n"
-
exit(EXIT_FAILURE);
-
}
-
-
//1.创建套接字
-
sockfd = socket(AF_INET,SOCK_STREAM,0);
-
if(sockfd < 0){
-
perror("Fail to socket");
-
exit(EXIT_FAILURE);
-
}
-
-
//2.填充服务器的IP地址和端口信息
-
server_addr.sin_family = AF_INET;
-
server_addr.sin_port = htons(atoi(argv[2]));
-
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
-
-
//3.和服务器进行连接,实质上是进行3次握手,连接成功之后,被连接的套接字就会变成连接套接字
-
// 这个连接套接字就能够和服务器进行通信
-
if(connect(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr)) < 0)
-
{
-
perror("Fail to connect");
-
exit(EXIT_FAILURE);
-
}
-
-
//4.连接成功之后,向服务器端发送数据
-
while(1)
-
{
-
printf("client >");
-
fgets(buf,sizeof(buf),stdin);
-
buf[strlen(buf) - 1] = '\0';
-
-
n = write(sockfd,buf,strlen(buf));
-
if(n < 0){
-
perror("Fail to write");
-
exit(EXIT_FAILURE);
-
}
-
}
-
-
return 0;
- }
点击(此处)折叠或打开
-
ubuntu@ubuntu:~/network_teacher/day02/tcp1/client$ ./client 192.168.127.131 8888
-
client >hello
-
client >world
-
client >quit
-
client >quit
-
client >quit
- ubuntu@ubuntu:~/network_teacher/day02/tcp1/client$
点击(此处)折叠或打开
-
ubuntu@ubuntu:~/network_teacher/day02/tcp1/server$ ./server 192.168.127.131 8888
-
listen ...
-
connect_fd : 4
-
-----------------------------
-
Port : 53987
-
Ip : 192.168.127.131
-
-----------------------------
-
Read 5 bytes : hello
-
Read 5 bytes : world
- Read 4 bytes : quit
客户端的代码完全相同,服务器端的代码做少量的修改,代码如下:
点击(此处)折叠或打开
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <signal.h>
-
#include <pthread.h>
-
#include <string.h>
-
#include <sys/socket.h>
-
#include <netinet/in.h>
-
#include <arpa/inet.h>
-
-
-
void *do_client(void *arg)
-
{
-
int n;
-
int connect_fd = *((int *)arg);
-
char buf[1024];
-
-
while(1)
-
{
-
n = read(connect_fd,buf,sizeof(buf) - 1);
-
if(n <= 0)
-
break;
-
-
buf[n] = '\0';
-
printf("Read %d bytes : %s\n",n,buf);
-
-
if(strncmp(buf,"quit",4)==0)
-
break;
-
}
-
-
//线程处理完之后必须关闭线程要操作的文件描述符,释放分配的内存空间
-
close(connect_fd);
-
free(arg);
-
-
//线程退出
-
pthread_exit(NULL);
-
}
-
-
//./server ip port
-
int main(int argc, const char *argv[])
-
{
-
int ret;
-
int pid;
-
int sockfd;
-
int *pconnect_fd;
-
pthread_t tid;
-
struct sockaddr_in server_addr;
-
struct sockaddr_in peer_addr;
-
socklen_t addrlen = sizeof(struct sockaddr);
-
-
if(argc < 3)
-
{
-
fprintf(stderr,"Usage : %s
,argv[0]);\n"
-
exit(EXIT_FAILURE);
-
}
-
-
//1.创建套接字
-
sockfd = socket(AF_INET,SOCK_STREAM,0);
-
if(sockfd < 0){
-
perror("Fail to socket");
-
exit(EXIT_FAILURE);
-
}
-
-
//2.填充服务器的Ip和端口信息
-
server_addr.sin_family = AF_INET;
-
server_addr.sin_port = htons(atoi(argv[2]));
-
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
-
//3.绑定服务器的IP地址和端口信息到套接字
-
if(bind(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr)) < 0)
-
{
-
perror("Fail to bind");
-
exit(EXIT_FAILURE);
-
}
-
-
//4.设置套接字为监听模式
-
listen(sockfd,128);
-
-
printf("listen ...\n");
-
-
while(1)
-
{
-
pconnect_fd = (int *)malloc(sizeof(int));
-
if(pconnect_fd == NULL){
-
perror("Fail to malloc");
-
exit(EXIT_FAILURE);
-
}
-
-
//5.从请求队列中提取请求
-
*pconnect_fd = accept(sockfd,(struct sockaddr *)&peer_addr,&addrlen);
-
if(connect_fd < 0){
-
perror("Fail to accept");
-
exit(EXIT_FAILURE);
-
}
-
-
printf("connect_fd : %d\n",*pconnect_fd);
-
printf("-----------------------------\n");
-
printf("Port : %d\n",ntohs(peer_addr.sin_port));
-
printf("Ip : %s\n",inet_ntoa(peer_addr.sin_addr));
-
printf("-----------------------------\n");
-
-
//6.提取请求成功之后立刻创建线程,每个客户端的请求都对于一个新创建的线程
-
ret = pthread_create(&tid,NULL,do_client,pconnect_fd);
-
if(ret != 0){
-
fprintf(stderr,"Fail to pthread_create : %s\n",strerror(ret));
-
}
-
-
//分离式线程:系统自动回收,线程结束的时候未释放的资源
-
pthread_detach(tid);
-
}
-
-
return 0;
- }