在深入理解計算機系統第11章中,講到一個echo服務器,基於tcp字節流的小型服務器,利用socket套接字接口,完成了一個簡易的服務器。
具體細節可參考網絡編程(編寫一個小型服務器)–csapp11章。
不過這個小型服務器有一個難以接受的缺點在於:它是單進程的服務器,也就是它只能服務一個客戶,我們先不去討論這個功能強弱,我們知道在現實生活中,一個網站,一個服務器必須要能夠服務多個用戶,如果只能服務一個用戶,那簡直不可想象。
之所以只能服務一個用戶,是由於設計時,只有一個執行流(也就是單進程),串行化完成任務,說白也就是當一個客戶發起請求,該服務器去相應然後服務客戶,客戶退出才能服務其他人。
因此,想要實現併發性,服務多個用戶,那需要實現多進程或者多線程服務器,即一個主線程(主進程)去執行等待客戶,另一個去服務客戶。
思路很簡單,那麼問題來了:如何選擇多線程還是多進程服務器?這時候,我們就要看看它們利弊了。
線程是CPU調度的基本單位,它是一個執行流的不同分支,運行在進程的地址空間之中,共享進程的資源,它強調共享性,因此無論是創建線程,切換線程,終止線程,退出線程代價要小得多,效率相對來說要高很多,在併發問題中,多線程確實是一個提高效率的好辦法。
而對於進程,由於進程是分配資源的基本單位,每個進程都具有自己獨立的進程地址空間,創建,切換,終止,退出等等效率不如線程快,那麼是不是就用多線程好呢?未必,進程強調獨立性,就帶來一個好處,穩定性,一個進程如果崩潰不會影響其他進程,而線程就不同了,多個線程運行在同一個進程的地址空間之中,因此一旦有一個線程崩潰了,其他線程也會崩潰,事實上,對於一個公司來說,服務器線程是絕對不能掛的(掛了也要立即重啓),因此選擇多進程也是可以考慮的。
綜上考慮,我們各實現一份多進程,多線程的服務器,其實代碼主邏輯有很多類似之處。具體如下:
圖示如下:
多進程服務器
tcp_server
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<sys/wait.h>
void usage(const char* arg)
{
printf("correct usage : %s [ip] [port]\n", arg);
}
int start_up(const char* ip, int port)
{
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == -1)
{
fprintf(stderr, "socket failure\n");
exit(-1);
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
inet_aton(ip, &local.sin_addr);
//local.sin_addr.s_addr = inet_addr(ip);
int opt = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0)
{
fprintf(stderr, "setsockopt failure\n");
exit(-1);
}
int flag = bind(sock, (struct sockaddr*)&local, sizeof(local));
if (flag == -1)
{
fprintf(stderr, "bind failure\n");
exit(-1);
}
flag = listen(sock, 6);
if (flag == -1)
{
fprintf(stderr, "listen failure\n");
exit(-1);
}
return sock;
}
int main(int argc, char* argv[])
{
// ./tcp_server ip port
if (argc != 3)
{
usage(argv[0]);
exit(1);
}
int listen_sock = start_up(argv[1], atoi(argv[2]));
while (1)
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
// int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int new_fd = accept(listen_sock, (struct sockaddr*)&client, &len);
if (new_fd == -1)
{
//fprintf(stderr, "accept failure\n");
continue;
}
pid_t pid = fork();
if (pid < 0)
{
fprintf(stderr, "fork failure\n");
close(new_fd);
}
else if (pid == 0)//child
{
close(listen_sock);
if (fork() > 0)
{
exit(0);
}
else
{
printf("%s:%d connects the server\n", inet_ntoa(client.sin_addr), client.sin_port);
while (1)
{
char buf[1024];
ssize_t read_size = read(new_fd, buf, sizeof(buf)-1);
if (read_size > 0)
{
buf[read_size] = '\0';
printf("client : %s\n", buf);
write(new_fd, buf, strlen(buf));
}
else
{
printf("client quits...\n");
break;
}
}
close(new_fd);
}
}
else //father
{
close(new_fd);
waitpid(-1, NULL, WNOHANG);
}
}
return 0;
}
tcp_client
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<unistd.h>
void usage(const char* arg)
{
printf("correct usage: %s [remote_ip] [remote_port]\n", arg);
}
int open_clientfd(const char* ip, int port)
{
int clientfd = socket(AF_INET, SOCK_STREAM, 0);
if (clientfd == -1)
{
fprintf(stderr, "socket failure\n");
exit(-1);
}
struct sockaddr_in remote;
remote.sin_family = AF_INET;
remote.sin_port = htons(port);
inet_aton(ip, &remote.sin_addr);
int flag = connect(clientfd, (struct sockaddr*)&remote, sizeof(remote));
if (flag == -1)
{
fprintf(stderr, "connect failure\n");
exit(-1);
}
return clientfd;
}
int main(int argc, char* argv[])
{
if (argc != 3)
{
usage(argv[0]);
exit(1);
}
int sock_fd = open_clientfd(argv[1], atoi(argv[2]));
while (1)
{
char buf[1024];
printf("please enter # ");
fflush(stdout);
ssize_t s = read(0, buf, sizeof(buf)-1);
if (s > 0)
{
buf[s-1] = '\0';
write(sock_fd, buf, strlen(buf));
s = read(sock_fd, buf, strlen(buf));
if (s > 0)
{
printf("server echo # %s\n", buf);
}
//printf("server # %s\n", buf);
}
}
return 0;
}
makefile
.PHONY:all
all:tcp_server tcp_client
tcp_server:tcp_server.c
gcc -o tcp_server tcp_server.c -static
tcp_client:tcp_client.c
gcc -o tcp_client tcp_client.c
.PHONY:clean
clean:
rm -f tcp_client tcp_server
多線程服務器
tcp_server
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
void usage(const char* arg)
{
printf("correct usage : %s [ip] [port]\n", arg);
}
int start_up(const char* ip, int port)
{
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == -1)
{
fprintf(stderr, "socket failure\n");
exit(-1);
}
// int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
// sockaddr* 相當於 void*
//
// 利用grep -ER 'struct sockaddr_in {' /usr/include 查找
//
// struct sockaddr_in {
// __kernel_sa_family_t sin_family; /* Address family */
// __be16 sin_port; /* Port number */
// struct in_addr sin_addr; /* Internet address */
//
// /* Pad to size of `struct sockaddr'. */
// unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
// sizeof(unsigned short int) - sizeof(struct in_addr)];
// };
//
// struct in_addr {
// __be32 s_addr;
// };
int opt = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0)
{
fprintf(stderr, "setsockopt failure\n");
exit(-1);
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
//local.sin_addr.s_addr = inet_addr(ip);
inet_aton(ip, &local.sin_addr);
int flag = bind(sock, (struct sockaddr*)&local, sizeof(local));
if (flag == -1)
{
fprintf(stderr, "bind failure\n");
exit(-1);
}
flag = listen(sock, 6);
if (flag == -1)
{
fprintf(stderr, "listen failure\n");
exit(-1);
}
return sock;
}
void* thread_run(void* arg)
{
pthread_detach(pthread_self());
int new_fd = (int)arg;
char buf[1024];
memset(buf, 0, sizeof(buf));
while (1)
{
ssize_t s = read(new_fd, buf, sizeof(buf)-1);
if (s > 0)
{
buf[s] = '\0';
printf("client : %s\n", buf);
write(new_fd, buf, strlen(buf));
}
else
{
printf("client quits...\n");
break;
}
}
}
int main(int argc, char* argv[])
{
// ./tcp_server ip port
if (argc != 3)
{
usage(argv[0]);
exit(-1);
}
int listen_sock = start_up(argv[1], atoi(argv[2]));
while (1)
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
// int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int new_fd = accept(listen_sock, (struct sockaddr*)&client, &len);
if (new_fd == -1)
{
//fprintf(stderr, "accept failure\n");
continue;
}
pthread_t tid;
pthread_create(&tid, NULL, thread_run, (void*)new_fd);
//pthread_detach(tid);
}
return 0;
}
tcp_client
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<unistd.h>
void usage(const char* arg)
{
printf("correct usage: %s [remote_ip] [remote_port]\n", arg);
}
int main(int argc, char* argv[])
{
if (argc != 3)
{
usage(argv[0]);
}
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == -1)
{
fprintf(stderr, "socket failure\n");
exit(-1);
}
// int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//
// struct sockaddr_in {
// __kernel_sa_family_t sin_family; /* Address family */
// __be16 sin_port; /* Port number */
// struct in_addr sin_addr; /* Internet address */
//
// /* Pad to size of `struct sockaddr'. */
// unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
// sizeof(unsigned short int) - sizeof(struct in_addr)];
// };
//
// struct in_addr {
// __be32 s_addr;
// };
struct sockaddr_in remote;
remote.sin_family = AF_INET;
remote.sin_port = htons(atoi(argv[2]));
//remote.sin_addr.s_addr = inet_addr(argv[1]);
inet_aton(argv[1], &remote.sin_addr);
int flag = connect(sock, (struct sockaddr*)&remote, sizeof(remote));
if (flag == -1)
{
fprintf(stderr, "connect failure\n");
exit(-1);
}
while (1)
{
char buf[1024];
printf("please enter # ");
fflush(stdout);
ssize_t s = read(0, buf, sizeof(buf)-1);
if (s > 0)
{
buf[s-1] = '\0';
write(sock, buf, strlen(buf));
s = read(sock, buf, strlen(buf));
if (s > 0)
{
printf("server echo # %s\n", buf);
}
}
}
return 0;
}
makefile
.PHONY:all
all:tcp_server tcp_client
tcp_server:tcp_server.c
gcc -o $@ $^ -static -lpthread
tcp_client:tcp_client.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f tcp_client tcp_server