前边已经学习了最基本的TCP
编程,不知道有没有发现一个问题,那就是我们的服务器端每次只能连接一个客户端,并且似乎也只能接受一次连接,这篇笔记就来介绍一下TCP
编程中的三种服务器,看看这三种服务器是否是就可以解决这问题,实现并发。若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
Windows windows11 Ubuntu Ubuntu16.04的64位版本 VMware® Workstation 16 Pro 16.2.3 build-19376536 SecureCRT Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日 开发板 正点原子 i.MX6ULL Linux阿尔法开发板 uboot NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03) linux内核 linux-4.15(NXP官方提供) STM32开发板 正点原子战舰V3(STM32F103ZET6)
点击查看本文参考资料
点击查看相关文件下载
这一篇笔记的所有例程都以前一篇笔记的最后一个双向传输的实例为基础进行来学习TCP
的三种服务器:循环服务器、多线程服务器和多进程服务器。
一、TCP
循环服务器 首先我们介绍一种TCP
循环服务器,这种方式其实在TCP
编程中非常少用,而且个人感觉使用起来比较麻烦,要处理的地方很多,虽然也能用吧,但是不如后面两种服务器,而且至少我还没有见过用到它的地方。
1. 服务器模型 循环服务器程序模型如下:
1 2 3 4 5 6 7 8 9 socket(...); bind(...); listen(...); while (1 ){ accept(...); process(...); close(...); }
2. 使用实例 在此示例中,我只修改了服务器,并未修改客户端,主要目的是为了显示一下会出现的问题,来帮助理解。
2.1 server
服务器端 点击查看实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <strings.h> #include <arpa/inet.h> #include <netinet/in.h> #include <unistd.h> #include <string.h> void usage (char *str) ; int main (int argc, char *argv[]) { int port = -1 ; if (argc != 3 ) { usage(argv[0 ]); exit (-1 ); } port = atoi(argv[2 ]); if (port < 5000 ) { usage(argv[0 ]); exit (-1 ); } int socket_fd = -1 ; if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0 )) < 0 ) { perror ("socket" ); exit (-1 ); } int b_reuse = 1 ; setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof (int )); struct sockaddr_in sin ; bzero (&sin , sizeof (sin )); sin .sin_family = AF_INET; sin .sin_port = htons(port); if (inet_pton(AF_INET, argv[1 ], (void *)&sin .sin_addr) != 1 ) { perror ("inet_pton" ); exit (-1 ); } if (bind(socket_fd, (struct sockaddr *)&sin , sizeof (sin )) < 0 ) { perror("bind" ); exit (-1 ); } if (listen(socket_fd, 5 ) < 0 ) { perror("listen" ); exit (-1 ); } printf ("Server starting....OK!\n" ); int newfd = -1 ; struct sockaddr_in cin ; socklen_t addrlen = sizeof (cin ); char ipv4_addr[16 ]; int ret = -1 ; char buf[BUFSIZ]; char replay[BUFSIZ]; while (1 ) { if ((newfd = accept(socket_fd, (struct sockaddr *)&cin , &addrlen)) < 0 ) { perror("accept" ); exit (-1 ); } if (!inet_ntop(AF_INET, (void *)&cin .sin_addr, ipv4_addr, sizeof (cin ))) { perror ("inet_ntop" ); exit (-1 ); } printf ("Clinet(%s:%d) is connected successfully![newfd=%d]\n" , ipv4_addr, ntohs(cin .sin_port), newfd); bzero(buf, BUFSIZ); bzero(replay, BUFSIZ); do { ret = read(newfd, buf, BUFSIZ - 1 ); } while (ret < 0 && EINTR == errno); if (ret < 0 ) { perror("read" ); exit (-1 ); } if (!ret) break ; printf ("Receive data: %s\n" , buf); strcat (replay, buf); ret = send(newfd, replay, strlen (replay), 0 ); if (ret < 0 ) { perror("send" ); exit (-1 ); } if (!strncasecmp(buf, "quit" , strlen ("quit" ))) { printf ("Client is exiting!\n" ); break ; } close(newfd); } close(socket_fd); return 0 ; } void usage (char *str) { printf ("\n%s serv_ip serv_port" , str); printf ("\n\t serv_ip: server ip address" ); printf ("\n\t serv_port: server port(>5000)\n\n" ); printf ("\n\t Attention: The IP address must be the IP address of the local nic or 0.0.0.0 \n\n" ); }
2.2 client
客户端 点击查看实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> void usage (char *str) ; int main (int argc, char *argv[]) { int port = -1 ; int portClient = -1 ; if (argc != 4 ) { usage(argv[0 ]); exit (-1 ); } port = atoi(argv[2 ]); portClient = atoi(argv[3 ]); if (port < 5000 || portClient < 5000 || (port == portClient)) { usage(argv[0 ]); exit (-1 ); } int socket_fd = -1 ; if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0 )) < 0 ) { perror ("socket" ); exit (-1 ); } int b_reuse = 1 ; setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof (int )); struct sockaddr_in sin ; bzero (&sin , sizeof (sin )); sin .sin_family = AF_INET; sin .sin_port = htons(port); if (inet_pton(AF_INET, argv[1 ], (void *)&sin .sin_addr) != 1 ) { perror ("inet_pton" ); exit (-1 ); } struct sockaddr_in sinClient ; bzero(&sinClient, sizeof (sinClient)); sinClient.sin_family = AF_INET; sinClient.sin_port = htons(portClient); if (inet_pton(AF_INET, argv[1 ], (void *)&sinClient.sin_addr) != 1 ) { perror ("inet_pton" ); exit (-1 ); } if (bind(socket_fd, (struct sockaddr *)&sinClient, sizeof (sinClient)) < 0 ) { perror("bind" ); exit (-1 ); } if (connect(socket_fd, (struct sockaddr *)&sin , sizeof (sin )) < 0 ) { perror("connect" ); exit (-1 ); } printf ("Client staring...OK!\n" ); int ret = -1 ; char buf[BUFSIZ]; char replay[BUFSIZ]; while (1 ) { bzero (buf, BUFSIZ); bzero (replay, BUFSIZ); printf (">" ); if (fgets(buf, BUFSIZ - 1 , stdin ) == NULL ) { continue ; } do { ret = write(socket_fd, buf, strlen (buf)); }while (ret < 0 && EINTR == errno); ret = recv(socket_fd, replay, BUFSIZ, 0 ); if (ret < 0 ) { perror("recv" ); exit (-1 ); } printf ("server replay:%s\n" , replay); if (!strncasecmp(buf, "quit" , strlen ("quit" ))) { printf ("Client is exiting!\n" ); break ; } } close(socket_fd); return 0 ; } void usage (char *str) { printf ("\n%s serv_ip serv_port" , str); printf ("\n\t serv_ip: server ip address" ); printf ("\n\t serv_port: server port(>5000)\n\n" ); printf ("\n\t client_port: client portClient(>5000 && !=port )\n\n" ); }
2.3 Makefile
由于需要生成两个可执行程序,自己输命令有些繁琐,这里使用make
来进行。
点击查看 Makefile 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 CC = gcc DEBUG = -g -O2 -Wall CFLAGS += $(DEBUG) TARGET_LIST = ${patsubst %.c, %, ${wildcard *.c}} all : $(TARGET_LIST) %.o : %.c $(CC) $(CFLAGS) -c $< -o $@ .PHONY : all clean clean_o clean_outclean : clean_o clean_out @rm -vf $(TARGET_LIST) clean_o : @rm -vf *.o clean_out : @rm -vf *.out
2.4 测试结果 我们执行以下命令编译链接程序,生成两个可执行文件:
然后会有如下提示,并生成两个可执行文件吗,分别为服务器端程序server
和客户端程序client
:
1 2 gcc -g -O2 -Wall client.c -o client gcc -g -O2 -Wall server.c -o server
对于服务器端,我们执行以下命令启动服务器进程:
1 ./server 0.0.0.0 5001 # 允许监听所有网卡IP及端口
对于客户端,我们执行以下命令启动客户端进程:
1 ./client 192.168.10.101 5001 5003 # 连接到本地一块网卡的IP,并设置客户端向外发送数据的端口为5003
然后我们就会看到如下现象:
其实这个例子只是为了发现一个问题,就是我们服务器改为循环服务器后,可以持续监听,等待客户端的连接,但是每次却只能接收一个数据,这是因为我们服务器在接收一次数据后吗,就将客户端连接过来的新的socket
描述符关闭了,想要一个客户端也可以持续发送数据我们其实将客户端的connect()
函数放在循环中就可以了,但是这样显得就很麻烦。我们若是再开启一个终端,输入以下命令:
1 ./client 192.168.10.101 5001 5004 # 连接到本地一块网卡的IP,并设置客户端向外发送数据的端口为5004, 不能与上边冲突
然后会发现,有下边两种情况,这里就不放图了,可以自己试一下:
第一种,两个客户端都连接上去,先连接的客户端先发送数据
这种情况下,先连接的客户端只能发送接收一次数据,然后后连接的也是一样,可以正常连接并且发送一次数据。
第一种,两个客户端都连接上去,后连接的客户端先发送数据
这种情况下,后连接的客户端连接时,虽然连接成功了,但是服务器端不会有任何反反应,因为它等待着先连接的客户端发送数据,当后连接的客户端先发送数据时,服务器端也不会有任何反应,当先连接的客户端发送一次数据后,后连接的客户端的数据也在服务器端显示了。
毫无疑问,这样的程序是很不严谨的,因为每一次我们将数据发过去后,都会导致需要重新连接,当然这也可以实现服务器的持续监听,但是不免有很多的问题,所以一般情况不会在TCP
编程中使用这种服务器。
二、TCP
多线程服务器 1. 服务器模型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 socket(...); bind(...); listen(...); while (1 ){ accpet(...); if ((pthread_create(...))!==-1 ) { process(...); close(...); exit (...); } close(...); }
【注意】
(1)编译程序的时候一定要加上-lpthread
。
(2)注意线程结束后,线程的回收问题,可以设置一个线程分离,这样线程结束后就会自动回收。
2. 使用实例 在此示例中,我只修改了服务器,并未修改客户端。需要注意的是,我们在线程执行函数中一定要设置线程分离,以在该线程结束的时候自动回收资源,防止出现僵尸线程。
2.1 server
服务器端 点击查看实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <strings.h> #include <arpa/inet.h> #include <netinet/in.h> #include <unistd.h> #include <string.h> #include <pthread.h> void usage (char *str) ; void *clientDataHandle (void *arg) ;int main (int argc, char *argv[]) { int port = -1 ; if (argc != 3 ) { usage(argv[0 ]); exit (-1 ); } port = atoi(argv[2 ]); if (port < 5000 ) { usage(argv[0 ]); exit (-1 ); } int socket_fd = -1 ; if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0 )) < 0 ) { perror ("socket" ); exit (-1 ); } int b_reuse = 1 ; setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof (int )); struct sockaddr_in sin ; bzero (&sin , sizeof (sin )); sin .sin_family = AF_INET; sin .sin_port = htons(port); if (inet_pton(AF_INET, argv[1 ], (void *)&sin .sin_addr) != 1 ) { perror ("inet_pton" ); exit (-1 ); } if (bind(socket_fd, (struct sockaddr *)&sin , sizeof (sin )) < 0 ) { perror("bind" ); exit (-1 ); } if (listen(socket_fd, 5 ) < 0 ) { perror("listen" ); exit (-1 ); } printf ("Server starting....OK!\n" ); int newfd = -1 ; struct sockaddr_in cin ; socklen_t addrlen = sizeof (cin ); char ipv4_addr[16 ]; pthread_t tid; while (1 ) { if ((newfd = accept(socket_fd, (struct sockaddr *)&cin , &addrlen)) < 0 ) { perror("accept" ); exit (-1 ); } if (!inet_ntop(AF_INET, (void *)&cin .sin_addr, ipv4_addr, sizeof (cin ))) { perror ("inet_ntop" ); exit (-1 ); } printf ("Clinet(%s:%d) is connected successfully![newfd=%d]\n" , ipv4_addr, ntohs(cin .sin_port), newfd); pthread_create (&tid, NULL , clientDataHandle, (void *) &newfd); } close(socket_fd); return 0 ; } void usage (char *str) { printf ("\n%s serv_ip serv_port" , str); printf ("\n\t serv_ip: server ip address" ); printf ("\n\t serv_port: server port(>5000)\n\n" ); printf ("\n\t Attention: The IP address must be the IP address of the local nic or 0.0.0.0 \n\n" ); } void *clientDataHandle (void *arg) { int newfd = *(int *) arg; int ret = -1 ; char buf[BUFSIZ]; char replay[BUFSIZ]; printf ("handler thread: newfd =%d\n" , newfd); pthread_detach(pthread_self()); while (1 ) { bzero(buf, BUFSIZ); bzero(replay, BUFSIZ); do { ret = read(newfd, buf, BUFSIZ-1 ); }while (ret < 0 && EINTR == errno); if (ret < 0 ) { perror ("read" ); exit (-1 ); } if (!ret) break ; printf ("Receive client[%d] data: %s\n" , newfd, buf); strcat (replay, buf); ret = send(newfd, replay, strlen (replay), 0 ); if (ret < 0 ) { perror("send" ); exit (-1 ); } if (!strncasecmp(buf, "quit" , strlen ("quit" ))) { printf ("Client[%d] is exiting!\n" , newfd); break ; } } close(newfd); return (void *)0 ; }
2.2 client
客户端 点击查看实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> void usage (char *str) ; int main (int argc, char *argv[]) { int port = -1 ; int portClient = -1 ; if (argc != 4 ) { usage(argv[0 ]); exit (-1 ); } port = atoi(argv[2 ]); portClient = atoi(argv[3 ]); if (port < 5000 || portClient < 5000 || (port == portClient)) { usage(argv[0 ]); exit (-1 ); } int socket_fd = -1 ; if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0 )) < 0 ) { perror ("socket" ); exit (-1 ); } int b_reuse = 1 ; setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof (int )); struct sockaddr_in sin ; bzero (&sin , sizeof (sin )); sin .sin_family = AF_INET; sin .sin_port = htons(port); if (inet_pton(AF_INET, argv[1 ], (void *)&sin .sin_addr) != 1 ) { perror ("inet_pton" ); exit (-1 ); } struct sockaddr_in sinClient ; bzero(&sinClient, sizeof (sinClient)); sinClient.sin_family = AF_INET; sinClient.sin_port = htons(portClient); if (inet_pton(AF_INET, argv[1 ], (void *)&sinClient.sin_addr) != 1 ) { perror ("inet_pton" ); exit (-1 ); } if (bind(socket_fd, (struct sockaddr *)&sinClient, sizeof (sinClient)) < 0 ) { perror("bind" ); exit (-1 ); } if (connect(socket_fd, (struct sockaddr *)&sin , sizeof (sin )) < 0 ) { perror("connect" ); exit (-1 ); } printf ("Client staring...OK!\n" ); int ret = -1 ; char buf[BUFSIZ]; char replay[BUFSIZ]; while (1 ) { bzero (buf, BUFSIZ); bzero (replay, BUFSIZ); printf (">" ); if (fgets(buf, BUFSIZ - 1 , stdin ) == NULL ) { continue ; } do { ret = write(socket_fd, buf, strlen (buf)); }while (ret < 0 && EINTR == errno); ret = recv(socket_fd, replay, BUFSIZ, 0 ); if (ret < 0 ) { perror("recv" ); exit (-1 ); } printf ("server replay:%s\n" , replay); if (!strncasecmp(buf, "quit" , strlen ("quit" ))) { printf ("Client is exiting!\n" ); break ; } } close(socket_fd); return 0 ; } void usage (char *str) { printf ("\n%s serv_ip serv_port" , str); printf ("\n\t serv_ip: server ip address" ); printf ("\n\t serv_port: server port(>5000)\n\n" ); printf ("\n\t client_port: client portClient(>5000 && !=port )\n\n" ); }
2.3 Makefile
由于需要生成两个可执行程序,自己输命令有些繁琐,这里使用make
来进行。这一次由于需要用到线程库pthread.h
所以我们在链接的时候需要加上-lpthread
参数,由于是一个Makefile
生成两个可执行程序,所以这里我们使用隐含规则推导的话,需要将编译和链接的步骤分开,所以其实下边的程序其实并不是很通用。
【注意】 为了使用ps
命令时可以更清楚的看到现象,所以这里服务器端生成的可执行程序名称我改成了myserver
点击查看 Makefile 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 TARGET1=myserver TARGET2=client CC = gcc DEBUG = -g -O2 -Wall CFLAGS += $(DEBUG) TARGET_LIST = ${patsubst %.c, %, ${wildcard *.c}} OBJ_LIST = ${patsubst %.c, %.o, ${wildcard *.c}} all : $(TARGET1) $(TARGET2) $(TARGET1) : server.o $(CC) $(CFLAGS) $< -o $@ -lpthread $(TARGET2) : client.o $(CC) $(CFLAGS) $< -o $@ -lpthread $(OBJ_LIST) : %.o : %.c $(CC) $(CFLAGS) -c $< -o $@ .PHONY : all clean clean_o clean_outclean : clean_o clean_out @rm -vf $(TARGET_LIST) $(TARGET1) $(TARGET2) clean_o : @rm -vf *.o clean_out : @rm -vf *.out
2.4 测试结果 我们执行以下命令编译链接程序,生成两个可执行文件:
然后会有如下提示,并生成两个可执行文件吗,分别为服务器端程序myserver
和客户端程序client
:
1 2 3 4 gcc -g -O2 -Wall -c server.c -o server.o gcc -g -O2 -Wall server.o -o myserver -lpthread gcc -g -O2 -Wall -c client.c -o client.o gcc -g -O2 -Wall client.o -o client -lpthread
对于服务器端,我们执行以下命令启动服务器进程:
1 ./myserver 0.0.0.0 5001 # 允许监听所有网卡IP及端口
对于客户端,我们分别在三个终端中执行以下命令启动三个客户端进程:
1 2 3 ./client 192.168.10.101 5001 5002 # 连接到本地一块网卡的IP,并设置客户端向外发送数据的端口为5002 ./client 192.168.10.101 5001 5003 # 连接到本地一块网卡的IP,并设置客户端向外发送数据的端口为5003 ./client 192.168.10.101 5001 5004 # 连接到本地一块网卡的IP,并设置客户端向外发送数据的端口为5004
然后我们会看到如下运行结果:
我们通过以下命令查看myserver
服务器端所有线程:
然后便会有以下提示信息:
上图中的(1)
是刚才连接了三个客户端的情况,一共有四个myserver
线程,后边我们在客户端进程输入quit
以退出客户端,每退出一个客户端进行就会减少一个线程。每增加一个客户端,就会增加一个线程。每个客户端都是独立正常工作的,其实就达到了一种并发的现象,使得我们的服务器可以同时处理多个客户端的请求。
三、TCP
多进程服务器 1. 服务器模型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 socket(...); bind(...); listen(...); while (1 ){ accpet(...); if (fork(...) == 0 ) { process(...); close(...); exit (...); } close(...); }
【注意】
(1)进程结束后如果不回收的话,会产生僵尸进程,我们为了实现自动回收,可以使用信号来进行操作。
(2)同一个进程打开不同的文件的时候,文件描述符会递增。
(3)不同的进程打开同一个文件,它们得到的文件描述符可能是一致的。
(4)不同的进程打开不同的文件,它们得到的文件描述符也有可能是一致的。
后边的(2)、(3)和(4)几条结论是后来自己测试出来的,其实也不难理解,就是进程都有自己独自的虚拟内存空间,是互不影响的。
2. 使用实例 在此示例中,我只修改了服务器,并未修改客户端。需要注意的是,我们在服务器端要对结束的子进程进行回收,由于子进程结束的时候会向父进程发出SIGCHLD
信号,所以我们可以使用信号来完成子进程的回收,以避免出现僵尸进程。
2.1 server
服务器端 点击查看实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <strings.h> #include <arpa/inet.h> #include <netinet/in.h> #include <unistd.h> #include <string.h> #include <signal.h> #include <sys/wait.h> void usage (char *str) ; void clientDataHandle (void *arg) ;void sigChildHandle (int signo) ; int main (int argc, char *argv[]) { int port = -1 ; if (argc != 3 ) { usage(argv[0 ]); exit (-1 ); } port = atoi(argv[2 ]); if (port < 5000 ) { usage(argv[0 ]); exit (-1 ); } signal(SIGCHLD, sigChildHandle); int socket_fd = -1 ; if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0 )) < 0 ) { perror ("socket" ); exit (-1 ); } int b_reuse = 1 ; setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof (int )); struct sockaddr_in sin ; bzero (&sin , sizeof (sin )); sin .sin_family = AF_INET; sin .sin_port = htons(port); if (inet_pton(AF_INET, argv[1 ], (void *)&sin .sin_addr) != 1 ) { perror ("inet_pton" ); exit (-1 ); } if (bind(socket_fd, (struct sockaddr *)&sin , sizeof (sin )) < 0 ) { perror("bind" ); exit (-1 ); } if (listen(socket_fd, 5 ) < 0 ) { perror("listen" ); exit (-1 ); } printf ("Server starting....OK!\n" ); int newfd = -1 ; struct sockaddr_in cin ; socklen_t addrlen = sizeof (cin ); char ipv4_addr[16 ]; pid_t pid = -1 ; while (1 ) { if ((newfd = accept(socket_fd, (struct sockaddr *)&cin , &addrlen)) < 0 ) { perror("accept" ); exit (-1 ); } pid = fork(); if (pid < 0 ) { perror("fork" ); break ; } else if (pid > 0 ) { printf ("%d\n" , newfd); close(newfd); } else { close(socket_fd); if (!inet_ntop(AF_INET, (void *)&cin .sin_addr, ipv4_addr, sizeof (cin ))) { perror ("inet_ntop" ); exit (-1 ); } printf ("Clinet(%s:%d) is connected successfully![newfd=%d]\n" , ipv4_addr, ntohs(cin .sin_port), newfd); clientDataHandle(&newfd); exit (0 ); } } close(socket_fd); return 0 ; } void usage (char *str) { printf ("\n%s serv_ip serv_port" , str); printf ("\n\t serv_ip: server ip address" ); printf ("\n\t serv_port: server port(>5000)\n\n" ); printf ("\n\t Attention: The IP address must be the IP address of the local nic or 0.0.0.0 \n\n" ); } void clientDataHandle (void *arg) { int newfd = *(int *) arg; int ret = -1 ; char buf[BUFSIZ]; char replay[BUFSIZ]; printf ("handler process: newfd =%d\n" , newfd); while (1 ) { bzero(buf, BUFSIZ); bzero(replay, BUFSIZ); do { ret = read(newfd, buf, BUFSIZ-1 ); }while (ret < 0 && EINTR == errno); if (ret < 0 ) { perror ("read" ); exit (-1 ); } if (!ret) break ; printf ("Receive client[%d] data: %s\n" , newfd, buf); strcat (replay, buf); ret = send(newfd, replay, strlen (replay), 0 ); if (ret < 0 ) { perror("send" ); exit (-1 ); } if (!strncasecmp(buf, "quit" , strlen ("quit" ))) { printf ("Client[%d] is exiting!\n" , newfd); break ; } } close(newfd); } void sigChildHandle (int signo) { if (signo == SIGCHLD) { waitpid(-1 , NULL , WNOHANG); } }
2.2 client
客户端 点击查看实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> void usage (char *str) ; int main (int argc, char *argv[]) { int port = -1 ; int portClient = -1 ; if (argc != 4 ) { usage(argv[0 ]); exit (-1 ); } port = atoi(argv[2 ]); portClient = atoi(argv[3 ]); if (port < 5000 || portClient < 5000 || (port == portClient)) { usage(argv[0 ]); exit (-1 ); } int socket_fd = -1 ; if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0 )) < 0 ) { perror ("socket" ); exit (-1 ); } int b_reuse = 1 ; setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof (int )); struct sockaddr_in sin ; bzero (&sin , sizeof (sin )); sin .sin_family = AF_INET; sin .sin_port = htons(port); if (inet_pton(AF_INET, argv[1 ], (void *)&sin .sin_addr) != 1 ) { perror ("inet_pton" ); exit (-1 ); } struct sockaddr_in sinClient ; bzero(&sinClient, sizeof (sinClient)); sinClient.sin_family = AF_INET; sinClient.sin_port = htons(portClient); if (inet_pton(AF_INET, argv[1 ], (void *)&sinClient.sin_addr) != 1 ) { perror ("inet_pton" ); exit (-1 ); } if (bind(socket_fd, (struct sockaddr *)&sinClient, sizeof (sinClient)) < 0 ) { perror("bind" ); exit (-1 ); } if (connect(socket_fd, (struct sockaddr *)&sin , sizeof (sin )) < 0 ) { perror("connect" ); exit (-1 ); } printf ("Client staring...OK!\n" ); int ret = -1 ; char buf[BUFSIZ]; char replay[BUFSIZ]; while (1 ) { bzero (buf, BUFSIZ); bzero (replay, BUFSIZ); printf (">" ); if (fgets(buf, BUFSIZ - 1 , stdin ) == NULL ) { continue ; } do { ret = write(socket_fd, buf, strlen (buf)); }while (ret < 0 && EINTR == errno); ret = recv(socket_fd, replay, BUFSIZ, 0 ); if (ret < 0 ) { perror("recv" ); exit (-1 ); } printf ("server replay:%s\n" , replay); if (!strncasecmp(buf, "quit" , strlen ("quit" ))) { printf ("Client is exiting!\n" ); break ; } } close(socket_fd); return 0 ; } void usage (char *str) { printf ("\n%s serv_ip serv_port" , str); printf ("\n\t serv_ip: server ip address" ); printf ("\n\t serv_port: server port(>5000)\n\n" ); printf ("\n\t client_port: client portClient(>5000 && !=port )\n\n" ); }
2.3 Makefile
【注意】 为了使用ps
命令时可以更清楚的看到现象,所以这里服务器端生成的可执行程序名称我改成了myserver
点击查看 Makefile 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 TARGET1=myserver TARGET2=client CC = gcc DEBUG = -g -O2 -Wall CFLAGS += $(DEBUG) TARGET_LIST = ${patsubst %.c, %, ${wildcard *.c}} OBJ_LIST = ${patsubst %.c, %.o, ${wildcard *.c}} all : $(TARGET1) $(TARGET2) $(TARGET1) : server.o $(CC) $(CFLAGS) $< -o $@ -lpthread $(TARGET2) : client.o $(CC) $(CFLAGS) $< -o $@ -lpthread $(OBJ_LIST) : %.o : %.c $(CC) $(CFLAGS) -c $< -o $@ .PHONY : all clean clean_o clean_outclean : clean_o clean_out @rm -vf $(TARGET_LIST) $(TARGET1) $(TARGET2) clean_o : @rm -vf *.o clean_out : @rm -vf *.out
2.4 测试结果 我们执行以下命令编译链接程序,生成两个可执行文件:
然后会有如下提示,并生成两个可执行文件吗,分别为服务器端程序myserver
和客户端程序client
:
1 2 3 4 gcc -g -O2 -Wall -c server.c -o server.o gcc -g -O2 -Wall server.o -o myserver -lpthread gcc -g -O2 -Wall -c client.c -o client.o gcc -g -O2 -Wall client.o -o client -lpthread
对于服务器端,我们执行以下命令启动服务器进程:
1 ./myserver 0.0.0.0 5001 # 允许监听所有网卡IP及端口
对于客户端,我们分别在三个终端中执行以下命令启动三个客户端进程:
1 2 3 ./client 192.168.10.101 5001 5002 # 连接到本地一块网卡的IP,并设置客户端向外发送数据的端口为5002 ./client 192.168.10.101 5001 5003 # 连接到本地一块网卡的IP,并设置客户端向外发送数据的端口为5003 ./client 192.168.10.101 5001 5004 # 连接到本地一块网卡的IP,并设置客户端向外发送数据的端口为5004
然后我们会看到如下运行结果:
【说明】
可以看出,新的客户端连接到服务器的时候产生的新的socket
描述符都是4
,这是因为我们在父进程中将newfd
关闭了,当我们父进程中注释掉关闭newfd
这个语句的时候,就会发现每次打印的newfd
是不一样的了。即便是每次获取到了相同的文件描述符数字,但是其实那都是不同的子进程中的文件描述符,即便一样,也不会影响通信。
就比如我们的标准输入输出,我们开了好几个终端,终端也是进程,他们都用到了0
、1
和2
三个文件描述符,但是不同的终端之间并没有因为文件描述符相同而产生的混乱,这就是因为不同的进程在内存中使用的是虚拟内存空间,是相互独立互不影响的。
我们通过以下命令查看myserver
服务器端所有进程:
然后便会有以下提示信息:
上图中其实与多线程的情况类似,这里就不分析了。
3. 一道题目 【题目】 阅读以下程序,完成下面的题目:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 listenfd = socket(…); bind(listenfd,…); listen(listenfd,…); for ( ; ; ){ connfd = accept(listenfd, …); if (( pid = fork( )) == 0 ) { recv(connfd,…); send(connfd,…); } else close(connfd); }
根据上面的程序,对以下说法判断对错
a. 这是一个并发服务器( )
b. 在任何时候,该服务器只能处理一个客户端的请求( )
c. 随着服务器端接受越来越多的请求,connfd
的值变得越来越大( )
【解析】
a.正确,显然是一个多进程的并发服务器;
b.错误,这是一个多进程的并发服务器,可以同时在不同的进程中处理不同客户端的请求;
c.错误,由于在子进程之外,关闭了accept
得到的用于数据传输的新的socket
文件描述符,再由上边使用实例可知,这里的connfd
是不变的。若是去掉close(connfd);
是这条语句的话,connfd
的值将会随着服务器端接受越来越多的客户端请求而变得越来越大。