2、多线程并发服务器的步骤
1 #include <stdio.h>
2 #include <sys/socket.h>//socket
3 #include <netinet/in.h>//struct sockaddr_in
4 #include <arpa/inet.h>//inet_pton inet_addr
5 #include <string.h>//bzero
6 #include <stdlib.h>//_exit
7 #include <pthread.h>//线程相关函数
8 #include <unistd.h>
9 typedef struct {
10 int cfd;//存放已连接套接字
11 struct sockaddr_in addr;//存放客户端的信息
12 }CLIENT_MSG;
13 void *deal_client_fun(void *arg)
14 {
15 CLIENT_MSG *p = (CLIENT_MSG *)arg;
16
17 //打印客户端的信息
18 char ip[16]="";
19 unsigned short port = 0;
20 inet_ntop(AF_INET, &p‐>addr.sin_addr.s_addr, ip, 16);
21 port = ntohs(p‐>addr.sin_port);
22 printf("%s %hu connected\n", ip, port);
23
24 //while获取客户端的请求 并回应
25 while(1)
26 {
27 unsigned char buf[1500]="";
28 int len = recv(p‐>cfd, buf, sizeof(buf), 0);
29 if(len <= 0)
30 {
31 printf("%s %hu 退出了\n", ip, port);
32 close(p‐>cfd);
33 break;
34 }
35 else
36 {
37 printf("%s %d:%s\n", ip, port, buf);
38 send(p‐>cfd, buf, len, 0);
39 }
40 }
41
42 //释放堆区空间
43 if(p != NULL)
44 {
45 free(p);
46 p=NULL;
47 }
48
49 //线程结束
50 pthread_exit(NULL);
51
52 return NULL;
53 }
54 int main(int argc, char const *argv[])
55 {
56 if(argc != 2)
57 {
58 printf("./a.out 8000\n");
59 _exit(‐1);
60 }
61
62 //1、创建tcp监听套接字
63 int lfd = socket(AF_INET, SOCK_STREAM, 0);
64 if(lfd < 0)
65 {
66 perror("socket");
67 _exit(‐1);
68 }
69
70 //设置端口复用
71 int opt = 1;
72 setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
73
74 //2、bind给服务器的监听套接字 绑定固定的IP、port
75 struct sockaddr_in my_addr;
76 bzero(&my_addr, sizeof(my_addr));
77 my_addr.sin_family = AF_INET;
78 my_addr.sin_port = htons(atoi(argv[1]));
79 my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
80 int ret = bind(lfd, (struct sockaddr *)&my_addr, sizeof(my_addr));
81 if(ret < 0)
82 {
83 perror("bind");
84 _exit(‐1);
85 }
86
87 //3、listen将服务器的套接字主动变被动 创建连接队列 进行监听
88 ret = listen(lfd, 128);
89 if(ret < 0)
90 {
91 perror("listen");
92 _exit(‐1);
93 }
94
95 //4、while‐‐>accept提取客户端
96 while(1)
97 {
98 struct sockaddr_in cli_addr;
99 socklen_t cli_len = sizeof(cli_addr);
100 int cfd = accept(lfd, (struct sockaddr *)&cli_addr, &cli_len);
101
102 CLIENT_MSG *p = (CLIENT_MSG *)calloc(1,sizeof(CLIENT_MSG));
103 p‐>cfd = cfd;
104 p‐>addr = cli_addr;
105
106 //5、一个客户端 创建一个线程
107 pthread_t tid;
108 pthread_create(&tid, NULL, deal_client_fun, (void *)p);
109 //线程分离
110 pthread_detach(tid);
111 }
112
113 //关闭监听套接字
114 close(lfd);
115 return 0;
116 }
其中
70 //设置端口复用
71 int opt = 1;
72 setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
为什么要设置端口复用,设置了有什么作用,详细讲解
在这段代码中,通过设置端口复用,实际上是在告诉操作系统允许重新使用被占用的端口。在网络编程中,端口复用是一个常见的技术手段,其作用和优势如下:
1. 允许快速重启服务器:当服务器进程异常退出或者被关闭后,如果不设置端口复用,操作系统会在一段时间内保持该端口处于TIME_WAIT状态,这样会导致在该端口上无法立即启动新的服务器进程。设置了端口复用后,新的服务器进程可以立即重用之前被占用的端口,快速恢复服务。
2. 提高服务器的健壮性和可用性:在高并发环境下,服务器可能会频繁启动和关闭,如果不设置端口复用,会导致端口资源被长时间占用,从而影响服务器的健壮性和可用性。通过设置端口复用,可以避免端口资源被长时间占用,提高服务器的稳定性。
3. 解决TIME_WAIT状态资源耗尽问题:如果不设置端口复用,在高并发情况下,大量的TIME_WAIT状态连接会消耗系统资源,甚至导致资源耗尽。设置端口复用可以有效减少TIME_WAIT状态连接的数量,避免资源耗尽问题的发生。
总之,设置端口复用可以提高服务器的稳定性、健壮性和可用性,是网络编程中常用的一项技术手段。
端口复用是什么意思,可以详细讲解吗
当一个套接字(Socket)在关闭之后,操作系统会保持一段时间的状态,称为TIME_WAIT状态。在这段时间内,该套接字的端口会被标记为不可用,以防止新的连接使用相同的端口。这个状态持续的时间通常是几分钟,具体取决于操作系统的设置。
端口复用就是一种机制,允许一个端口在短时间内被多个套接字共享或者重新使用。通常情况下,一个套接字关闭后,操作系统会将其端口保留一段时间以处理可能存在的延迟数据包或者断开连接后的数据包。但在某些情况下,如果需要快速重新启动服务器或者处理大量的连接请求,这种行为就会成为一个问题,因为端口会被占用一段时间,导致无法立即重新使用。
通过设置端口复用选项,可以告诉操作系统允许其他套接字在当前套接字关闭后立即绑定到同一端口上,而不必等待TIME_WAIT状态结束。这样可以有效地提高服务器的可用性和响应速度,特别是在高并发的网络环境中。
在实际编程中,可以通过调用`setsockopt`函数来设置端口复用选项,具体是通过设置`SO_REUSEADDR`选项来实现的。当这个选项被设置为1时,表示允许端口复用,操作系统将允许重新绑定到同一端口上。