服务器在执行 accept 等待客户端连接时,会阻塞。客户端连接成功后 accept 函数返回,执行后面的代码。
如果想要服务器同时为多个客户端服务,有以下几种方式:
- 多进程:每来一个客户端,就开一个进程与其通讯。
- 多线程:跟多进程类似,但每个客户端对应一个单独的线程
- IO多路复用:借助 select、poll 函数实现
多进程服务器
多进程模型中,为了避免产生僵尸进程,父进程必须回收子进程资源。通常有两种方式:
- 创建一个专门的子进程监听连接,父进程只用于创建和回收进程。
- 父进程通过 accept 阻塞式监听连接前,通过 signal 注册 SIGCHLD 子进程退出信号的回调函数。这样每个子进程退出时都可以通过信号触发函数执行。
多进程服务器 C 代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#define SERV_PORT 8888
void child_exit(int sig) {
wait(NULL);
}
int main() {
int sfd, cfd;
int ret;
int pid;
int n;
char buf[BUFSIZ];
struct sockaddr_in saddr, caddr;
char cip[BUFSIZ];
socklen_t clen;
sfd = socket(AF_INET, SOCK_STREAM, 0);
if (sfd < 0) {
perror("socket error");
exit(EXIT_FAILURE);
}
bzero(&saddr, sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(SERV_PORT);
saddr.sin_addr.s_addr = htonl(INADDR_ANY);
ret = bind(sfd, (struct sockaddr *)&saddr, sizeof(saddr));
if (ret < 0) {
perror("socket error");
exit(EXIT_FAILURE);
}
ret = listen(sfd, 128);
if (ret < 0) {
perror("socket error");
exit(EXIT_FAILURE);
}
signal(SIGCHLD, child_exit);
while(1) {
clen = sizeof(caddr);
cfd = accept(sfd, (struct sockaddr*)&caddr, &clen);
printf("client IP is:%s, client Port is:%d\n", inet_ntop(AF_INET, &caddr.sin_addr.s_addr, cip, sizeof(cip)), ntohs(caddr.sin_port));
if (cfd == -1) {
if (errno == EINTR) {
continue;
}
perror("socket error");
exit(EXIT_FAILURE);
}
pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) {
close(sfd);
while(1) {
n = read(cfd, buf, sizeof(buf));
if (n == 0) {
close(cfd);
exit(0);
}
if (n == -1) {
if (errno == EAGAIN || errno == EINTR) {
continue;
}
perror("read");
exit(EXIT_FAILURE);
}
write(cfd, buf, n);
}
} else {
close(cfd);
continue;
}
}
}
多线程服务器
多线程服务器相比多进程服务器,占用资源更少。
多线程服务器 C 代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <malloc.h>
#define SERV_PORT 8888
struct routine_arg {
int cfd;
};
void *start_routine(void *args) {
printf("hello thread\n");
struct routine_arg *arg = (struct routine_arg*)args;
int n;
char buf[BUFSIZ];
while(1) {
n = read(arg->cfd, buf, sizeof(buf));
if (n == 0) {
close(arg->cfd);
return 0;
} else if (n == -1) {
if (errno == EINTR) {
continue;
}
perror("read");
exit(EXIT_FAILURE);
} else {
write(arg->cfd, buf, strlen(buf));
write(STDOUT_FILENO, buf, strlen(buf));
}
}
free(args);
return (void*)0;
}
int main() {
int ret;
int sfd, cfd;
int n, caddr_len;
char buf[BUFSIZ];
char buf_ip[BUFSIZ];
struct sockaddr_in saddr, caddr;
pthread_t tid;
caddr_len = sizeof(caddr);
sfd = socket(AF_INET, SOCK_STREAM, 0);
if (sfd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
saddr.sin_family = AF_INET;
saddr.sin_port = htons(SERV_PORT);
saddr.sin_addr.s_addr = htonl(INADDR_ANY);
ret = bind(sfd, (struct sockaddr*)&saddr, sizeof(saddr));
if (ret == -1) {
perror("bind");
exit(EXIT_FAILURE);
}
listen(sfd, 128);
while(1) {
cfd = accept(sfd, (struct sockaddr*)&caddr, &caddr_len);
if (cfd == -1) {
perror("accept");
exit(EXIT_FAILURE);
}
printf("client IP is:%s, client Port is:%d\n", inet_ntop(AF_INET, &caddr.sin_addr.s_addr, buf_ip, sizeof(buf_ip)), ntohs(caddr.sin_port));
struct routine_arg *arg = malloc(sizeof(struct routine_arg));
arg->cfd = cfd;
pthread_create(&tid, NULL, start_routine, arg);
pthread_detach(tid);
}
return 0;
}