服務器在執行 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;
}