創建鏈接的過程就是三次握手的過程:客戶端發送 SYN 報文給服務器,服務器回覆 SYN+ACK 報文,客戶機再發送 ACK 報文。編程
關閉鏈接的過程:客戶機先發送 FIN 報文,服務器回覆 ACK 報文,服務器再發送 FIN 報文,客戶機再發送響應報文 ACK。服務器
msg.h網絡
1 #ifndef __MSG_H__ 2 #define __MSG_H__ 3 4 #include <sys/types.h> 5 6 typedef struct { 7 /** 協議頭部: 不傳輸任何數據,只包含發送端的一些信息 */ 8 char head[10]; ///< 協議頭部 9 char checknum; ///< 校驗碼 10 11 /**協議體部 */ 12 char buff[512]; ///< 數據 13 }Msg; 14 15 /** 發送一個基於自定義協議的 message,發送的數據存放在 buff 中 */ 16 extern int write_msg(int sockfd, char *buff, ssize_t len); 17 18 /** 讀取一個基於自定義協議的 message, 讀取的數據存放在 buff 中 */ 19 extern int read_msg(int sockfd, char *buff, ssize_t len); 20 21 #endif
msg.c多線程
1 #include "msg.h" 2 #include <unistd.h> 3 #include <string.h> 4 #include <memory.h> 5 #include <sys/types.h> 6 7 8 /** 計算校驗碼 */ 9 static unsigned char msg_check(Msg *message) 10 { 11 unsigned char s = 0; 12 int i; 13 for(i = 0; i < sizeof(message->head); i++){ 14 s += message->head[i]; 15 } 16 17 for(i = 0; i < sizeof(message->buff); i++){ 18 s += message->buff[i]; 19 } 20 21 return s; 22 } 23 24 25 /** 發送一個基於自定義協議的 message,發送的數據存放在 buff 中 */ 26 int write_msg(int sockfd, char *buff, ssize_t len) 27 { 28 Msg message; 29 memset(&message, 0, sizeof(message)); 30 strcpy(message.head, "hello"); 31 memcpy(message.buff, buff, len); 32 message.checknum = msg_check(&message); 33 34 if(write(sockfd, &message, sizeof(message)) != sizeof(message)){ 35 return -1; 36 } 37 38 return 0; 39 } 40 41 /** 讀取一個基於自定義協議的 message, 讀取的數據存放在 buff 中 */ 42 int read_msg(int sockfd, char *buff, ssize_t len) 43 { 44 Msg message; 45 memset(&message, 0, sizeof(message)); 46 47 ssize_t size; 48 if((size = read(sockfd, &message, sizeof(message))) < 0){ 49 return -1; 50 } 51 else if(size == 0){ 52 return 0; 53 } 54 55 /** 進行校驗碼驗證,判斷接收到的 message 是否完整 */ 56 unsigned char s = msg_check(&message); 57 if((s == (unsigned char)message.checknum) && (!strcmp("hello", message.head))){ 58 memcpy(buff, message.buff, len); 59 return sizeof(message); 60 } 61 return -1; 62 63 }
編譯成 .o 文件:gcc -o obj/msg.o -Iinclude -c src/msg.c併發
一個服務器處理多個客戶端的請求,就稱爲服務器的併發。socket
echo_tcp_server.ctcp
1 #include <netdb.h> 2 #include <netinet/in.h> 3 #include <sys/socket.h> 4 #include <sys/wait.h> 5 #include <unistd.h> 6 #include <string.h> 7 #include <stdio.h> 8 #include <stdlib.h> 9 #include <memory.h> 10 #include <signal.h> 11 #include <time.h> 12 #include <arpa/inet.h> 13 #include <errno.h> 14 #include "msg.h" 15 16 17 int sockfd; 18 19 void sig_handler(int signo) 20 { 21 if(signo == SIGINT){ 22 printf("server close\n"); 23 /** 步驟6: 關閉 socket */ 24 close(sockfd); 25 exit(1); 26 } 27 28 if(signo == SIGINT){ 29 printf("child process deaded...\n"); 30 wait(0); 31 } 32 } 33 34 /** 輸出鏈接上來的客戶端相關信息 */ 35 void out_addr(struct sockaddr_in *clientaddr) 36 { 37 /** 將端口從網絡字節序轉換成主機字節序 */ 38 int port = ntohs(clientaddr->sin_port); 39 char ip[16]; 40 memset(ip, 0, sizeof(ip)); 41 /** 將 ip 地址從網絡字節序轉換成點分十進制 */ 42 inet_ntop(AF_INET, &clientaddr->sin_addr.s_addr, ip, sizeof(ip)); 43 printf("client: %s(%d) connected\n", ip, port); 44 } 45 46 void do_service(int fd) 47 { 48 /** 和客戶端進行讀寫操做(雙向通訊) */ 49 char buff[512]; 50 while(1){ 51 memset(buff, 0, sizeof(buff)); 52 printf("start read and write....\n"); 53 ssize_t size; 54 if((size = read_msg(fd, buff, sizeof(buff))) < 0){ 55 perror("protocal error"); 56 break; 57 } 58 else if(size == 0){ 59 break; 60 } 61 else { 62 printf("%s\n", buff); 63 if(write_msg(fd, buff, sizeof(buff)) < 0){ 64 if(errno == EPIPE){ 65 break; 66 } 67 perror("protocal error"); 68 } 69 } 70 } 71 } 72 73 int main(int argc, char *argv[]) 74 { 75 if(argc < 2){ 76 printf("usage: %s #port\n", argv[0]); 77 exit(1); 78 } 79 80 if(signal(SIGINT, sig_handler) == SIG_ERR){ 81 perror("signal sigint error"); 82 exit(1); 83 } 84 85 if(signal(SIGCHLD, sig_handler) == SIG_ERR){ 86 perror("signal sigchld error"); 87 exit(1); 88 } 89 90 /** 步驟1: 建立 socket(套接字) 91 * 注: socket 建立在內核中,是一個結構體. 92 * AF_INET: IPV4 93 * SOCK_STREAM: tcp 協議 94 * AF_INET6: IPV6 95 */ 96 sockfd = socket(AF_INET, SOCK_STREAM, 0); 97 if(sockfd < 0){ 98 perror("socket error"); 99 exit(1); 100 } 101 102 /** 103 * 步驟2: 調用 bind 函數將 socket 和地址(包括 ip、port)進行綁定 104 */ 105 struct sockaddr_in serveraddr; 106 memset(&serveraddr, 0, sizeof(struct sockaddr_in)); 107 /** 往地址中填入 ip、port、internet 地址族類型 */ 108 serveraddr.sin_family = AF_INET; ///< IPV4 109 serveraddr.sin_port = htons(atoi(argv[1])); ///< 填入端口 110 serveraddr.sin_addr.s_addr = INADDR_ANY; ///< 填入 IP 地址 111 if(bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in))){ 112 perror("bind error"); 113 exit(1); 114 } 115 116 /** 117 * 步驟3: 調用 listen 函數啓動監聽(指定 port 監聽) 118 * 通知系統去接受來自客戶端的鏈接請求 119 * (將接受到的客戶端鏈接請求放置到對應的隊列中) 120 * 第二個參數: 指定隊列的長度 121 */ 122 if(listen(sockfd, 10) < 0){ 123 perror("listen error"); 124 exit(1); 125 } 126 127 /** 128 * 步驟4: 調用 accept 函數從隊列中得到一個客戶端的請求鏈接, 並返回新的 129 * socket 描述符 130 * 注意: 若沒有客戶端鏈接,調用此函數後會足則, 直到得到一個客戶端的鏈接 131 */ 132 struct sockaddr_in clientaddr; 133 socklen_t clientaddr_len = sizeof(clientaddr); 134 while(1){ 135 int fd = accept(sockfd, (struct sockaddr *)&clientaddr, &clientaddr_len); 136 if(fd < 0){ 137 perror("accept error"); 138 continue; 139 } 140 141 /** 142 * 步驟5: 啓動子進程去調用 IO 函數(read/write)和鏈接的客戶端進行雙向的通訊 143 */ 144 pid_t pid = fork(); 145 if(pid < 0){ 146 continue; 147 } 148 else if(pid == 0){ 149 /** 子進程 */ 150 out_addr(&clientaddr); 151 do_service(fd); 152 /** 步驟6: 關閉 socket */ 153 close(fd); 154 break; 155 } 156 else{ 157 /** 父進程 */ 158 /** 步驟6: 關閉 socket */ 159 close(fd); 160 } 161 } 162 163 return 0; 164 }
gcc -o bin/echo_tcp_server -Iinclude obj/msg.o src/echo_tcp_server.c 函數
echo_tcp_client.c測試
1 #include <sys/types.h> 2 #include <stdlib.h> 3 #include <stdio.h> 4 #include <memory.h> 5 #include <unistd.h> 6 #include <sys/socket.h> 7 #include <netdb.h> 8 #include <signal.h> 9 #include <string.h> 10 #include <time.h> 11 #include <arpa/inet.h> 12 #include "msg.h" 13 14 15 int main(int argc, char *argv[]) 16 { 17 if(argc < 3){ 18 printf("usage: %s ip port\n", argv[0]); 19 exit(1); 20 } 21 22 /** 步驟1: 建立 socket */ 23 int sockfd = socket(AF_INET, SOCK_STREAM, 0); 24 if(sockfd < 0){ 25 perror("socket error"); 26 exit(1); 27 } 28 29 /** 往 serveraddr 中填入 ip、port 和地址族類型(ipv4) */ 30 struct sockaddr_in serveraddr; 31 memset(&serveraddr, 0, sizeof(struct sockaddr_in)); 32 serveraddr.sin_family = AF_INET; 33 serveraddr.sin_port = htons(atoi(argv[2])); 34 /** 將 ip 地址轉換成網絡字節序後填入 serveraddr 中 */ 35 inet_pton(AF_INET, argv[1], &serveraddr.sin_addr.s_addr); 36 37 /** 38 * 步驟2: 客戶端調用 connect 函數鏈接到服務器端 39 */ 40 if(connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in)) < 0){ 41 perror("connect error"); 42 exit(1); 43 } 44 45 /** 步驟3: 調用 IO 函數(read/write)和服務器端進行雙向通訊 */ 46 char buff[512]; 47 ssize_t size; 48 char *prompt = "==>"; 49 while(1){ 50 memset(buff, 0, sizeof(buff)); 51 write(STDOUT_FILENO, prompt, 3); 52 size = read(STDIN_FILENO, buff, sizeof(buff)); 53 if(size < 0) continue; 54 buff[size - 1] = '\0'; 55 56 if(write_msg(sockfd, buff, sizeof(buff)) < 0){ 57 perror("write msg error"); 58 continue; 59 } 60 else { 61 if(read_msg(sockfd, buff, sizeof(buff)) < 0){ 62 perror("read msg error"); 63 continue; 64 } 65 else { 66 printf("%s\n", buff); 67 } 68 } 69 } 70 71 /** 步驟4: 關閉 socket */ 72 close(sockfd); 73 74 return 0; 75 }
gcc -o bin/echo_tcp_client -Iinclude obj/msg.o src/echo_tcp_client.cspa
開啓兩個終端進行測試,一個運行服務器,一個運行客戶端:
echo_tcp_server_th.c
1 #include <netdb.h> 2 #include <netinet/in.h> 3 #include <sys/socket.h> 4 #include <sys/wait.h> 5 #include <unistd.h> 6 #include <string.h> 7 #include <stdio.h> 8 #include <stdlib.h> 9 #include <memory.h> 10 #include <signal.h> 11 #include <time.h> 12 #include <arpa/inet.h> 13 #include <errno.h> 14 #include "msg.h" 15 #include <pthread.h> 16 17 18 int sockfd; 19 20 void sig_handler(int signo) 21 { 22 if(signo == SIGINT){ 23 printf("server close\n"); 24 /** 步驟6: 關閉 socket */ 25 close(sockfd); 26 exit(1); 27 } 28 } 29 30 void do_service(int fd) 31 { 32 /** 和客戶端進行讀寫操做(雙向通訊) */ 33 char buff[512]; 34 while(1){ 35 memset(buff, 0, sizeof(buff)); 36 ssize_t size; 37 if((size = read_msg(fd, buff, sizeof(buff))) < 0){ 38 perror("protocal error"); 39 break; 40 } 41 else if(size == 0){ 42 break; 43 } 44 else { 45 printf("%s\n", buff); 46 if(write_msg(fd, buff, sizeof(buff)) < 0){ 47 if(errno == EPIPE){ 48 break; 49 } 50 perror("protocal error"); 51 } 52 } 53 } 54 } 55 56 57 void out_fd(int fd) 58 { 59 struct sockaddr_in addr; 60 socklen_t len = sizeof(addr); 61 /** 從 fd 中得到鏈接的客戶端相關信息並放置到 sockaddr_in 當中 */ 62 if(getpeername(fd, (struct sockaddr *)&addr, &len) < 0){ 63 perror("getpeername error"); 64 return; 65 } 66 67 char ip[16]; 68 memset(ip, 0, sizeof(ip)); 69 int port = ntohs(addr.sin_port); 70 inet_ntop(AF_INET, &addr.sin_addr.s_addr, ip, sizeof(ip)); 71 printf("%16s(%5d) closed!\n", ip, port); 72 } 73 74 void *th_fn(void *arg) 75 { 76 int fd = (int)arg; 77 78 do_service(fd); 79 out_fd(fd); 80 close(fd); 81 return (void *)0; 82 } 83 84 int main(int argc, char *argv[]) 85 { 86 if(argc < 2){ 87 printf("usage: %s #port\n", argv[0]); 88 exit(1); 89 } 90 91 if(signal(SIGINT, sig_handler) == SIG_ERR){ 92 perror("signal sigint error"); 93 exit(1); 94 } 95 96 97 /** 步驟1: 建立 socket(套接字) 98 * 注: socket 建立在內核中,是一個結構體. 99 * AF_INET: IPV4 100 * SOCK_STREAM: tcp 協議 101 * AF_INET6: IPV6 102 */ 103 sockfd = socket(AF_INET, SOCK_STREAM, 0); 104 if(sockfd < 0){ 105 perror("socket error"); 106 exit(1); 107 } 108 109 /** 110 * 步驟2: 調用 bind 函數將 socket 和地址(包括 ip、port)進行綁定 111 */ 112 struct sockaddr_in serveraddr; 113 memset(&serveraddr, 0, sizeof(struct sockaddr_in)); 114 /** 往地址中填入 ip、port、internet 地址族類型 */ 115 serveraddr.sin_family = AF_INET; ///< IPV4 116 serveraddr.sin_port = htons(atoi(argv[1])); ///< 填入端口 117 serveraddr.sin_addr.s_addr = INADDR_ANY; ///< 填入 IP 地址 118 if(bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in))){ 119 perror("bind error"); 120 exit(1); 121 } 122 123 /** 124 * 步驟3: 調用 listen 函數啓動監聽(指定 port 監聽) 125 * 通知系統去接受來自客戶端的鏈接請求 126 * (將接受到的客戶端鏈接請求放置到對應的隊列中) 127 * 第二個參數: 指定隊列的長度 128 */ 129 if(listen(sockfd, 10) < 0){ 130 perror("listen error"); 131 exit(1); 132 } 133 134 /** 135 * 步驟4: 調用 accept 函數從隊列中得到一個客戶端的請求鏈接, 並返回新的 136 * socket 描述符 137 * 注意: 若沒有客戶端鏈接,調用此函數後會足則, 直到得到一個客戶端的鏈接 138 */ 139 140 /** 設置線程的分離屬性 */ 141 pthread_attr_t attr; 142 pthread_attr_init(&attr); 143 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 144 145 while(1){ 146 /** 主控線程負責調用 accept 去得到客戶端的鏈接 */ 147 int fd = accept(sockfd, NULL, NULL); 148 if(fd < 0){ 149 perror("accept error"); 150 continue; 151 } 152 153 /** 154 * 步驟5: 啓動子線程去調用 IO 函數(read/write)和鏈接的客戶端進行雙向的通訊 155 */ 156 pthread_t th; 157 int err; 158 /** 以分離狀態啓動子線程 */ 159 if((err = pthread_create(&th, &attr, th_fn, (void *)fd)) != 0){ 160 perror("pthread create error"); 161 } 162 163 pthread_attr_destroy(&attr); 164 165 } 166 167 return 0; 168 }
客戶端程序用上面的客戶端程序便可。