1、前言
上一篇已經實作了服務器端與客戶端之間最基礎通信,但存在一些問題,最大的問題是上篇中一個服務器端只能連接一個客戶端,如何讓一個服務器端可以連接多個客戶端呢?利用多行程多執行緒實作,
2、優化說明
- 優化1: 讓服務器程式可以系結在任何的IP地址上,
- 優化2 :通程序式獲取剛建立的socket的客戶端的IP地址和埠號,
- 優化3 :連接多個客戶端,
- 優化4:允許系結地址快速重用,
3、幾個函式

1、IP地址轉換函式
1、 本地位元組序轉化為網路位元組序
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
- af:地址協議族(IPV4:4:AF_INET或IPV6::AF_INET6)
- src:是一個指標,填寫點分形式的IP地址
- dest:轉換的結果給dst
- 在服務器端Bind()函式系結IP地址和埠號,客戶端connect()函式填充IP地址和埠號時,是將本地位元組序轉化為網路位元組序,
2、網路位元組序轉化為本地位元組序
#include <arpa/inet.h>
int inet_ntop(int af, const char *src, void *dst, socklen_t size);
- af:引數同上
- src:從結構體中讀取IP地址,該結構體中有IP地址和埠號等資訊,
- dst:讀取到的IP地址保存的地址,如果是IPV4,結果為點分形式,
- 結構體大小
- 服務器端讀取客戶端IP地址和埠號時,需要將網路位元組序轉化為本地位元組序,
2、埠位元組序轉化
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong); //本地位元組序到網路位元組序 4位元組
uint16_t htons(uint16_t hostshort); //本地位元組序到網路位元組序 2位元組
uint32_t ntohl(uint32_t netlong); //網路位元組序到本地位元組序 4位元組
uint16_t ntohs(uint16_t netshort); // 網路位元組序到本地位元組序 2位元組
3、行程創建函式
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
- 創建一個新的行程,新行程為當前行程的子行程,fork()通過回傳值來判斷行程是在子行程中,還是父行程中,
- 回傳值 = 0,在子行程中; 回傳值 <0,創建子行程失敗;回傳值 >0,在父行程,
- 子行程繼承父行程的內容,子行程先結束時,父行程需要及時回收,
4、行程回收函式
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *wstatus, int options);
- pid=-1 等待任何子行程,此時的waitpid()函式就退化成了普通的wait()函式,
- wstatus指定用于保存子行程回傳值和結束方式的地址,
- options 指定回收方式,0或WNOHANG,WNOHANG為非阻塞方式,
5、執行緒創建函式
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
- 引數thread為指向執行緒識別符號的指標,
- attr 為執行緒屬性,NULL表示默認,
- start_routine 執行緒執行函式,
- arg為傳遞給執行函式的引數,
多執行緒實作-服務器端
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <strings.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <errno.h> /*與error相關的*/
#include <arpa/inet.h>
#define SERV_PORT 5001
#define SERV_IP_ADDR "192.168.192.143"
#define BACKLOG 5
#define QUIT_STR "quit"
void cli_data_handle(void * arg);
int main()
{
int fd =-1;
struct sockaddr_in sin;
/*第一步:創建socket fd*/
if( (fd = socket(AF_INET,SOCK_STREAM,0)) < 0){
perror("socket");
exit(1);
}
/*優化4:允許系結地址快速重用*/
int b_reuse = 1;
setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&b_reuse,sizeof(int));
/*第二步:系結*/
/*填充struct sockaddr_in結構體變數*/
bzero(&sin,sizeof(sin)); //清空結構體
sin.sin_family = AF_INET;//TCP/IP協議族
sin.sin_port = htons(SERV_PORT); //轉化為網路位元組序埠號
/*優化1:讓服務器程式可以系結在任何的IP地址上*/
#if 1
sin.sin_addr.s_addr = htonl(INADDR_ANY);
#else
if(inet_pton(AF_INET,SERV_IP_ADDR,&sin.sin_addr) != 1){//ip地址轉化為網路位元組序
perror("inet_pton");
exit(1);
}
#endif
/*系結,強制轉化為struct sockaddr結構體*/
if(bind(fd,(struct sockaddr *)&sin,sizeof(sin)) < 0){
perror("bind error");
exit(1);
}
/*第三步:呼叫listen()把主動套接字變成被動套接字*/
if(listen(fd,BACKLOG) < 0){ //BACKLOG 通常為 5
perror("listen");
exit(1);
}
/*第四步:阻塞等待客戶端連接請求*/
printf("Server starting......OK\n");
int newfd = -1;
#if 0
newfd = accept(fd,NULL,NULL);//和客戶端連接成功時,回傳一個新的newfd
if(newfd < 0){
perror("accept");
exit(1);
}
#else
/*優化2:通程序式獲取剛建立的socket的客戶端的IP地址和埠號*/
pthread_t tid;
struct sockaddr_in cin; //用于存放客戶端的資訊
socklen_t addrlen = sizeof(cin);
while(1){
if((newfd = accept(fd,(struct sockaddr*)&cin,&addrlen)) < 0){
perror("accept");
exit(1);
}
char ipv4_addr[16];/*字串陣列,用于存放轉化后的IP地址*/
if(! inet_ntop(AF_INET,(void *)&cin.sin_addr,ipv4_addr,sizeof(cin))){
perror("inet_ntop");
exit(1);
}
printf("Client(%s:%d) is connected!\n",ipv4_addr,ntohs(cin.sin_port));
pthread_create(&tid,NULL,(void *)cli_data_handle,(void*)&newfd);//創建執行緒,傳入函式的引數為newfd
}
close(fd);
}
#endif
void cli_data_handle(void * arg){
int newfd = *(int*)arg; //先強制轉換為int型指標,再取值
printf("handler thread:newfd = %d\n",newfd);/**/
int ret = -1;
char buf[BUFSIZ];
while(1){
bzero(buf,BUFSIZ);
do{
ret = read(newfd,buf,BUFSIZ-1);//讀取客戶端寫入到BUF中的資料
}while(ret < 0 && EINTR == errno);
if(ret < 0){ // 出錯
perror("read");
exit(1);
}
if(!ret){ //沒有讀到資料時,退出當前回圈
break;
}
printf("Receive data(client fd:%d):%s\n",newfd,buf);
if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR))){//相等時 回傳0
printf("Client(fd = %d) is exiting!\n",newfd); //該比較函式忽略字母大小寫
break;
}
}
close(newfd);/*退出回圈后,關閉*/
}
多行程實作 服務器端
1 #include <stdio.h>
2 #include <pthread.h>
3 #include <string.h>
4 #include <unistd.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #include <unistd.h>
8 #include <stdlib.h>
9 #include <sys/types.h>
10 #include <sys/wait.h>
11 #include <sys/socket.h>
12 #include <strings.h>
13 #include <netinet/in.h>
14 #include <netinet/ip.h> /* superset of previous */
15 #include <signal.h>
16 #include <errno.h> /*與error相關的*/
17 #include <arpa/inet.h>
18 #define SERV_PORT 5001
19 #define SERV_IP_ADDR "192.168.192.143"
20 #define BACKLOG 5
21 #define QUIT_STR "quit"
22
23 void cli_data_handle(void * arg);
24 /*回收子行程*/
25 void sig_child_handle(int signo){
26 if(SIGCHLD == signo){ //子行程暫停或終止時產生,父行程將收到
27 waitpid(-1, NULL, WNOHANG);
28 }
29
30 }
31 int main()
32 {
33 int fd =-1;
34 struct sockaddr_in sin;
35
36 signal(SIGCHLD,sig_child_handle);
37
38 /*第一步:創建socket fd*/
39 if( (fd = socket(AF_INET,SOCK_STREAM,0)) < 0){
40 perror("socket");
41 exit(1);
42 }
43 /*優化4:允許系結地址快速重用*/
44 int b_reuse = 1;
45 setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&b_reuse,sizeof(int));
46 /*第二步:系結*/
47 /*填充struct sockaddr_in結構體變數*/
48 bzero(&sin,sizeof(sin)); //清空結構體
49 sin.sin_family = AF_INET;//TCP/IP協議族
50 sin.sin_port = htons(SERV_PORT); //轉化為網路位元組序埠號
51 /*優化1:讓服務器程式可以系結在任何的IP地址上*/
52 #if 1
53 sin.sin_addr.s_addr = htonl(INADDR_ANY);
54 #else
55 if(inet_pton(AF_INET,SERV_IP_ADDR,&sin.sin_addr) != 1){//ip地址轉化為網路位元組序
56 perror("inet_pton");
57 exit(1);
58 }
59 #endif
60 /*系結,強制轉化為struct sockaddr結構體*/
61 if(bind(fd,(struct sockaddr *)&sin,sizeof(sin)) < 0){
62 perror("bind error");
63 exit(1);
64 }
65 /*第三步:呼叫listen()把主動套接字變成被動套接字*/
66 if(listen(fd,BACKLOG) < 0){ //BACKLOG 通常為 5
67 perror("listen");
68 exit(1);
69 }
70 /*第四步:阻塞等待客戶端連接請求*/
71 printf("Server starting......OK\n");
72 int newfd = -1;
73 #if 0
74 newfd = accept(fd,NULL,NULL);//和客戶端連接成功時,回傳一個新的newfd
75 if(newfd < 0){
76 perror("accept");
77 exit(1);
78 }
79 #else
80 /*優化2:通程序式獲取剛建立的socket的客戶端的IP地址和埠號*/
81
82 //pthread_t tid;
83 struct sockaddr_in cin;
84 socklen_t addrlen = sizeof(cin);
85 while(1){
86 pid_t pid = -1;
87 if((newfd = accept(fd,(struct sockaddr*)&cin,&addrlen)) < 0){
88 perror("accept");
89 exit(1);
90 }
91
92 if((pid = fork()) < 0){
93 perror("fork");
94 break;
95 }
96 if( 0 == pid)//子行程
97 {
98
99 close(fd);//不用fd
100 char ipv4_addr[16];/*字串陣列,用于存放轉化后的IP地址*/
101 if(! inet_ntop(AF_INET,(void *)&cin.sin_addr,ipv4_addr,sizeof(cin))){
102 perror("inet_ntop");//從網路位元組序轉化為本地位元組序
103 exit(1);
104 }
105 printf("Client(%s:%d) is connected!\n",ipv4_addr,ntohs(cin.sin_port));
106 cli_data_handle(&newfd);
107 return 0;
108 }
109 else{ //pid > 0,父行程
110 close(newfd);
111 }
112
113 }
114 }
115 #endif
116 void cli_data_handle(void * arg){
117
118 int newfd = *(int*)arg; //先強制轉換為int型指標,再取值
119 printf("child handling process:newfd = %d\n",newfd);/**/
120 int ret = -1;
121 char buf[BUFSIZ];
122 while(1){
123 bzero(buf,BUFSIZ);
124 do{
125 ret = read(newfd,buf,BUFSIZ-1);//讀取客戶端寫入到BUF中的資料
126 }while(ret < 0 && EINTR == errno);
127 if(ret < 0){ // 出錯
128 perror("read");
129 exit(1);
130 }
131 if(!ret){ //沒有讀到資料時,退出當前回圈
132 break;
133 }
134 printf("Receive data(client fd:%d):%s\n",newfd,buf);
135 if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR))){//相等時 回傳0
136 printf("Client(fd = %d) is exiting!\n",newfd); //該比較函式忽略字母大小寫
137 break;
138 }
139 }
140 close(newfd);
141 }
142
143
~
~
~
客戶端 執行時輸入 ./client serv_ip serv_port
/*./client serv_ip serv_port*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <strings.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <errno.h> /*與error相關的*/
#include <arpa/inet.h>
#define SERV_PORT 5001
#define SERV_IP_ADDR "192.168.192.143"
#define BACKLOG 5
#define QUIT_STR "quit"
void usage(char *s)
{
printf("\n%s serv_ip serv_port",s);
printf("\n\t serv_ip:server ip address");
printf("\n\t serv_port:server port(>5000)\n\n");
}
int main(int argc,char *argv[])
{
int fd = -1;
int port = -1;
if(argc != 3){
usage(argv[0]);
exit(1);
}
struct sockaddr_in sin;
/*第一步:創建socket fd*/
if( (fd = socket(AF_INET,SOCK_STREAM,0)) < 0){
perror("socket");
exit(1);
}
port = atoi(argv[2]); //輸入的第三個引數 埠號
if(port < 5000){
usage(argv[0]);
exit(1);
}
/*第二步:連接*/
/*填充struct sockaddr_in結構體變數*/
bzero(&sin,sizeof(sin)); //清空結構體
sin.sin_family = AF_INET;//TCP/IP協議族
sin.sin_port = htons(port); //轉化為網路位元組序
if(inet_pton(AF_INET,argv[1],(void*)&sin.sin_addr) != 1){//ip地址轉化為網路位元組序
perror("inet_pton");
exit(1);
}
if(connect(fd,(struct sockaddr *)&sin,sizeof(sin)) < 0){
perror("connect ");
exit(1);
}
/*寫入資料*/
char buf[BUFSIZ];
while(1){
bzero(buf,BUFSIZ);
if(fgets(buf,BUFSIZ-1,stdin) == NULL){ //鍵盤輸入資料到buf
continue;
}
write(fd,buf,strlen(buf));
if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR))){//相等時 回傳0
printf("Client is exiting!\n"); //該比較函式忽略字母大小寫
break;
}
}
/*關閉*/
close(fd);
}
4、編譯運行
/*多行程實作 服務器端*/
book@100ask:~/socket$ ./server3
Server starting......OK
Client(192.168.192.143:36066) is connected! //行程1
child handling process:newfd = 4
Receive data(client fd:4):welcome
Receive data(client fd:4):147
Receive data(client fd:4):Next change client
Client(192.168.192.143:36068) is connected! //行程2
child handling process:newfd = 4
Receive data(client fd:4):hello
Receive data(client fd:4):2021
Receive data(client fd:4):456
Receive data(client fd:4):quit
Client(fd = 4) is exiting!
Client(192.168.192.143:36070) is connected!//行程3
child handling process:newfd = 4
Receive data(client fd:4):11111
Receive data(client fd:4):22222
Receive data(client fd:4):quit
/*多行程實作 客戶端*/
book@100ask:~/socket$ ./client 192.168.192.143 5001 //輸入的三個引數,client 1
welcome
147
Next change client
book@100ask:~/socket$ ./client 192.168.192.143 5001 //client 2
hello
2021
456
quit
Client is exiting!
book@100ask:~/socket$ ./client 192.168.192.143 5001//client 3
11111
22222
quit
Client is exiting!
/*多執行緒實作-服務器端*/
Client(192.168.192.143:36076) is connected! //執行緒1
handler thread:newfd = 4
Receive data(client fd:4):qwe
Receive data(client fd:4):789
Client(192.168.192.143:36078) is connected! //執行緒2
handler thread:newfd = 5
Receive data(client fd:5):000
Receive data(client fd:5):999
Client(192.168.192.143:36080) is connected! //執行緒3
handler thread:newfd = 6
Receive data(client fd:6):abc
Receive data(client fd:6):711
/*多執行緒實作-客戶端*/
book@100ask:~/socket$ ./client 192.168.192.143 5001 //client 1
qwe
789
book@100ask:~/socket$ ./client 192.168.192.143 5001//client 2
000
999
book@100ask:~/socket$ ./client 192.168.192.143 5001//client 3
abc
711
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/293324.html
標籤:其他
上一篇:系統學習nginx(第三天)nginx作為下載器使用
下一篇:docker鏡像和容器的匯入匯出
