http://www.javashuo.com/article/p-zolorgfe-r.html 只是簡單的處理,服務器返回客戶端一個時間,而後關閉了socket。html
若是要進行雙向通訊,服務器勢必要調用read函數,而read默認阻塞,那麼若是客戶端不向服務器發送數據,則主線程一直阻塞,其它客戶端沒法鏈接成功。這就須要處理高併發問題。服務器
服務器高併發處理的三種方式網絡
本篇:多進程模型多線程
主線程只負責accept操做,接收來自客戶端的鏈接。併發
收到一個客戶端鏈接後,就fork出來一個子進程,子進程負責具體的I/O操做。app
收到第二個客戶端鏈接後,再fork出來另外一個子進程,子進程負責具體的I/O操做。socket
以此類推。tcp
注意:ide
弊端:啓動進程佔用系統開銷大。函數
因爲本身封裝了消息,故有協議讀寫代碼msg.h 和 msg.c。我作了個簡單的make,固然能夠不用,gcc的時候注意指定msg.c便可。
Makefile
app:obj/time_tcp_server.o obj/time_tcp_client.o obj/msg.o obj/echo_tcp_server_process.o obj/echo_tcp_server_thread.o obj/echo_tcp_client.o gcc obj/time_tcp_server.o -o bin/time_tcp_server gcc obj/time_tcp_client.o -o bin/time_tcp_client gcc -Iinclude obj/echo_tcp_client.o obj/msg.o -o bin/echo_tcp_client gcc -Iinclude obj/echo_tcp_server_process.o obj/msg.o -o bin/echo_tcp_server_process gcc -Iinclude obj/echo_tcp_server_thread.o obj/msg.o -o bin/echo_tcp_server_thread obj/time_tcp_server.o:src/time_tcp_server.c gcc -Iinclude -c $< -o $@ obj/time_tcp_client.o:src/time_tcp_client.c gcc -Iinclude -c $< -o $@ obj/msg.o:src/msg.c gcc -Iinclude -c $< -o $@ obj/echo_tcp_server_process.o:src/echo_tcp_server_process.c gcc -Iinclude -c $< -o $@ obj/echo_tcp_server_thread.o:src/echo_tcp_server_thread.c gcc -Iinclude -c $< -o $@ obj/echo_tcp_client.o:src/echo_tcp_client.c gcc -Iinclude -c $< -o $@ clean: rm bin/* rm obj/*
目錄結構:
msg.h
/* *封裝自定義的協議 * */ #ifndef _MSG_H_ #define _MSG_H_ #include <sys/types.h> typedef struct msg { //協議頭 char head[9]; //校驗碼 char checkcode; //實際的存入數據 char data[1024]; }Msg; //發送一個基於自定義協議的Msg //從fd中讀取內容,寫入到buff extern int read_msg(int sockfd, char* buff, size_t buffsize); ////讀取一個基於自定義協議的Msg //將buff中的內容寫入到fd extern int write_msg(int sockfd, char* buff, size_t buffsize); #endif
msg.c代碼
/* *封裝自定義的協議 * */ #include "msg.h" #include <memory.h> #include <unistd.h> #define HEAD_DESC "elan2019" //得到校驗碼 static unsigned char msg_checkcode(const Msg* message) { unsigned char ret = 0; for (int i = 0; i < sizeof(message->head); ++i) { ret += message->head[i]; } for (int i = 0; i < sizeof(message->data); ++i) { ret += message->data[i]; } return ret; } // 從fd中讀取內容,讀取的數據存放到buff int read_msg(int fd, char* buff, size_t buffsize) { Msg message; memset(&message, 0, sizeof(message)); size_t size = read(fd, &message, sizeof(message)); if( size < 0 ) { return -1; } else if (size == 0) { return 0; } //校驗比對,判斷接收到的數據完整性 unsigned char cc = msg_checkcode(&message); if ((unsigned char)message.checkcode == cc && (strcmp(HEAD_DESC, message.head) == 0)) { memcpy(buff, &message.data, buffsize); return sizeof(message); } return -1; } //將buff中的內容寫入到fd int write_msg(int fd, char* buff, size_t buffsize) { // 構建message Msg message; memset(&message, 0, sizeof(message)); //填充頭 strcpy(message.head, HEAD_DESC); //填充數據 memcpy(&message.data, buff, buffsize); //填充校驗碼 message.checkcode = msg_checkcode(&message); //開始寫入 size_t size; if((size = write(fd, &message, sizeof(message))) < 0) { return -1; } else if(size != sizeof(message)) { return -1; } return sizeof(message); }
服務端代碼 echo_tcp_server_process.c
#include <sys/socket.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <time.h> #include <string.h> #include <netdb.h> #include <arpa/inet.h> #include <errno.h> #include "msg.h" #define SERVER_PORT 8888 #define LISTEN_QUEUE_SISE 10 int socketfd; void signal_handler(int signo) { if (signo == SIGINT) { printf("server close by ctrl c\n"); close(socketfd); exit(1); } else if (signo == SIGCHLD) { //子進程結束 向父進程發送 SIGCHLD 信號 //父進程區 回收殭屍子進程 printf("child process dead....\n"); wait(NULL);//阻塞回收一個子進程 } } void out_clientinfo(const struct sockaddr_in* outsockaddr) { char ipstr[16]; memset(ipstr, 0, sizeof(ipstr)); // 將地址從網絡字節序轉換爲點分十進制 inet_ntop(AF_INET, &outsockaddr->sin_addr.s_addr, ipstr, sizeof(ipstr)); printf("Connected by %s(%d)\n", ipstr, ntohs(outsockaddr->sin_port)); } // 和客戶端雙向通訊 收到客戶端發來的數據立刻返回 void do_something(int fd) { char buff[1024]; while(1) { //讀取客戶端發送過來的內容 memset(buff, 0, sizeof(buff)); size_t size = read_msg(fd, buff, sizeof(buff)); if (size < 0) { // 協議出錯 跳出while 回到main 結束子進程 perror("read_msg error"); break; } else if (size == 0) { /* 客戶端斷開了鏈接 掛掉了 跳出while 回到main往下走,會結束子進程 * 結束子進程 向父進程發送SIGCHLD信號 **/ break; } printf("recev from client:%s\n",buff); //將數據寫回 if (write_msg(fd, buff, sizeof(buff)) < 0) { if(errno == EPIPE) { /* 客戶端斷開了鏈接 * 產生SIGPIPE信號 * 將error設置爲EPIPE * 因此可在此判斷errno 也可捕捉SIGPIPE信號 * 俗稱管道爆裂 * 跳出while 回到main往下走,會結束子進程,結束子進程 向父進程發送SIGCHLD信號 **/ break; } perror("write_msg error"); } } } int main(int argc, char const *argv[]) { //Ctrl+C if (signal(SIGINT, signal_handler) == SIG_ERR) { perror("signal SIGINT error"); exit(1); } //子進程結束 向父進程發送SIGCHLD if (signal(SIGCHLD, signal_handler) == SIG_ERR) { perror("signal SIGCHLD error"); exit(1); } /* 1 sokect * AF_INET ipv4 * SOCK_STREAM tcp **/ if((socketfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket error"); exit(1); } // 2 bind 綁定本地地址和端口 struct sockaddr_in serveraddr; memset(&serveraddr, 0, sizeof(serveraddr)); serveraddr.sin_family = AF_INET;//ipv4 serveraddr.sin_port = htons(SERVER_PORT); //端口 serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);//響應任意網卡的請求 if(bind(socketfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0) { perror("bind error"); exit(1); } // 3 listen 啓動監聽 通知系統接受來自客戶端的鏈接 準備好鏈接隊列 if(listen(socketfd, LISTEN_QUEUE_SISE) < 0) { perror("listen error"); exit(1); } struct sockaddr_in clientaddr; socklen_t clientaddr_len = sizeof(clientaddr); while(1) { // 4 accept 從隊列拿出第一個 // clientaddr獲取客戶端的地址信息,是傳出參數 int clientfd = accept(socketfd, (struct sockaddr*)&clientaddr, &clientaddr_len); if (clientfd < 0) { perror("accept error"); continue; } // 5 建立子進程,在子進程中作讀寫操做 因此注意進程回收 pid_t pid = fork(); if(pid < 0) { //建立進程失敗 繼續下一次循環 perror("fork error"); close(clientfd); continue; } else if(pid == 0) { //子進程 /* 父進程中監聽的套接字再也不使須要 要關閉 * 子進程退出後 父進程在運行 那麼子進程會變成殭屍進程 父進程須要監聽SIGCHLD * 子進程再也不fork 必須break **/ close(socketfd); out_clientinfo(&clientaddr); do_something(clientfd); close(clientfd); break; } else { //父進程 close(clientfd);//不使用 關閉 } } // 6 close return 0; }
客戶端代碼echo_tcp_client.c
#include <sys/socket.h> #include <stdlib.h> #include <sys/types.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <netdb.h> #include "msg.h" #define SERVER_PORT 8888 #define SERVER_IP 127.0.0.1 int main(int argc, char const *argv[]) { //step 1 建立socket int socketfd = socket(AF_INET, SOCK_STREAM, 0); if (socketfd < 0) { perror("socket error"); exit(1); } //step 2 connect struct sockaddr_in serveraddr; memset(&serveraddr, 0, sizeof(serveraddr)); serveraddr.sin_family = AF_INET; serveraddr.sin_port = htons(SERVER_PORT); if(connect(socketfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0 ) { perror("connect error"); exit(1); } //step 3 read write char buf[512]; char* promt = ">"; size_t size; while(1) { memset(buf, 0, sizeof(buf)); //控制檯顯示提示符 write(STDOUT_FILENO, promt, 1); //讀取控制檯輸入 read是阻塞函數 size = read(STDIN_FILENO, buf, sizeof(buf)); if(size < 0) continue; buf[size - 1] = '\0'; if (write_msg(socketfd, buf, size) < 0) { //寫入錯誤 則忽略 進行下一次寫入 perror("write_msg error"); continue; } //等待服務器返回 memset(buf, 0, sizeof(buf)); //read是阻塞函數 若是服務器沒有下發消息,會一直阻塞在這裏,直到收到消息。 size = read_msg(socketfd, buf,sizeof(buf)); if( size > 0) { printf("recev from server:%s\n",buf); } else if(size == 0) { //可能服務器關閉socket了 printf("socket close from server\n"); break; } else { perror("read_msg error"); continue; } } //step 4 close close(socketfd); return 0; }