Golang在服务器没有可用的后备队列时,TCP连接变慢。

huangapple go评论116阅读模式
英文:

Golang TCP Connection Slow when Server Doesn't Have Backlog Available

问题

更新

通过添加了pthreaded C客户端,重新创建了问题,表明长连接时间是TCP协议的一部分,而不是特定实现的一部分。修改这些协议似乎不容易。

初始问题

我相信我的问题主要是:当使用Golang的net包尝试通过TCP连接到服务器时,以下情况会发生:

  1. 服务器没有可用的连接,即使在后台也是如此。
  2. 连接没有被拒绝/失败。

连接到服务器的连接似乎存在大量开销,服务器响应时间从5 ms增加到几秒钟。这在生产环境和下面的最小示例中都看到了。正确的解决方案是使用连接池连接到服务器,这将被实现。这主要是我的好奇心。

复现步骤:

  1. 使用backlog = 1运行服务器,运行client.go。
    • 所有50个goroutine同时触发,总完成时间接近2分钟。
  2. 使用backlog = 100运行服务器,运行client.go。
    • 所有50个goroutine同时触发,连接到服务器排队,并在~260 ms内完成。
  3. 运行三个使用50 us重试时间的C客户端,平均能够在12 ms内完成连接,因此没有看到这个问题。

backlog = 1的示例输出(第一个时间是拨号时间,第二个时间是完成时间):

  1. user@computer ~/tcp-tests $ go run client.go 127.0.0.1:46999
  2. Long Elapsed Time: 216.579μs, 315.196μs
  3. Long Elapsed Time: 274.169μs, 5.970873ms
  4. Long Elapsed Time: 74.4μs, 10.753871ms
  5. Long Elapsed Time: 590.965μs, 205.851066ms
  6. Long Elapsed Time: 1.029287689s, 1.029574065s
  7. Long Elapsed Time: 1.02945649s, 1.035098229s
  8. ...
  9. Long Elapsed Time: 3.045881865s, 6.378597166s
  10. Long Elapsed Time: 3.045314838s, 6.383783688s
  11. Time taken stats: 2.85 +/- 1.59 s // 平均值 +/- STDEV
  12. Main Taken: 6.384677948s

backlog = 100的示例输出:

  1. ...
  2. Long Elapsed Time: 330.098μs, 251.004077ms
  3. Long Elapsed Time: 298.146μs, 256.187795ms
  4. Long Elapsed Time: 315.832μs, 261.523685ms
  5. Time taken stats: 0.13 +/- 0.08 s
  6. Main Taken: 263.186955ms

那么,在net.DialTCP的内部发生了什么(我们还使用了其他类型的拨号,没有明显的区别),导致拨号时间增加?

  • 在尝试建立连接之间的轮询时间?
  • RFC 5681全局拥塞控制(可能包括互斥锁?)变量,在所有初始失败的连接尝试上递增?
  • 其他原因?

我倾向于前两者,因为1s,3s,5s的值似乎是魔数。它们在我的普通本地机器和大规模生产环境中都出现过。

这是用C编写的最小服务器。感兴趣的配置值是listenbacklog参数。

  1. /*
  2. Adapted from
  3. https://www.geeksforgeeks.org/tcp-server-client-implementation-in-c/
  4. Compile and run with:
  5. gcc server.c -o server; ./server
  6. */
  7. #include <stdio.h>
  8. #include <string.h>
  9. #include <sys/socket.h>
  10. #include <arpa/inet.h>
  11. #include <unistd.h>
  12. #include <sys/time.h>
  13. int main(void)
  14. {
  15. int socket_desc, client_sock, client_size;
  16. struct sockaddr_in server_addr, client_addr;
  17. char server_message[2000], client_message[2000];
  18. // Clean buffers:
  19. memset(server_message, '
    /*
  20. Adapted from
  21. https://www.geeksforgeeks.org/tcp-server-client-implementation-in-c/
  22. Compile and run with:
  23. gcc server.c -o server; ./server
  24. */
  25. #include <stdio.h>
  26. #include <string.h>
  27. #include <sys/socket.h>
  28. #include <arpa/inet.h>
  29. #include <unistd.h>
  30. #include <sys/time.h>
  31. int main(void)
  32. {
  33. int socket_desc, client_sock, client_size;
  34. struct sockaddr_in server_addr, client_addr;
  35. char server_message[2000], client_message[2000];
  36. // Clean buffers:
  37. memset(server_message, '\0', sizeof(server_message));
  38. memset(client_message, '\0', sizeof(client_message));
  39. // Create socket:
  40. socket_desc = socket(AF_INET, SOCK_STREAM, 0);
  41. if(socket_desc < 0){
  42. printf("Error while creating socket\n");
  43. return -1;
  44. }
  45. printf("Socket created successfully\n");
  46. // Set port and IP:
  47. server_addr.sin_family = AF_INET;
  48. server_addr.sin_port = htons(46999);
  49. server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
  50. // Bind to the set port and IP:
  51. if(bind(socket_desc, (struct sockaddr*)&server_addr, sizeof(server_addr))<0){
  52. printf("Couldn't bind to the port\n");
  53. return -1;
  54. }
  55. printf("Done with binding\n");
  56. // Listen for clients:
  57. // Increasing the backlog allows the Go client to connect and wait
  58. // rather than poll/retry.
  59. if(listen(socket_desc, 100) < 0){
  60. printf("Error while listening\n");
  61. return -1;
  62. }
  63. printf("\nListening for incoming connections.....\n");
  64. // Accept an incoming connection:
  65. client_size = sizeof(client_addr);
  66. int server_run = 1;
  67. do
  68. {
  69. struct timeval start, end;
  70. double cpu_time_used;
  71. gettimeofday(&start, NULL);
  72. client_sock = accept(socket_desc, (struct sockaddr*)&client_addr, &client_size);
  73. if (client_sock < 0){
  74. printf("Can't accept\n");
  75. return -1;
  76. }
  77. // Receive client's message:
  78. if (recv(client_sock, client_message, sizeof(client_message), 0) < 0){
  79. printf("Couldn't receive\n");
  80. return -1;
  81. }
  82. if (strcmp(client_message, "stop") == 0)
  83. {
  84. server_run = 0;
  85. printf("Received stop message.\n");
  86. }
  87. // Respond to client:
  88. strcpy(server_message, "This is the server's message.");
  89. if (send(client_sock, server_message, strlen(server_message), 0) < 0){
  90. printf("Can't send\n");
  91. return -1;
  92. }
  93. // sleep for 5 ms
  94. usleep(5000);
  95. // Closing the socket:
  96. close(client_sock);
  97. gettimeofday(&end, NULL);
  98. cpu_time_used = (end.tv_usec - start.tv_usec) / 1000.0;
  99. if (cpu_time_used > 0.0) // overflow in tv_usec if negative
  100. printf("Server Time: %.4f ms\n", cpu_time_used);
  101. } while(server_run);
  102. close(socket_desc);
  103. return 0;
  104. }
  105. ', sizeof(server_message));
  106. memset(client_message, '
    /*
  107. Adapted from
  108. https://www.geeksforgeeks.org/tcp-server-client-implementation-in-c/
  109. Compile and run with:
  110. gcc server.c -o server; ./server
  111. */
  112. #include <stdio.h>
  113. #include <string.h>
  114. #include <sys/socket.h>
  115. #include <arpa/inet.h>
  116. #include <unistd.h>
  117. #include <sys/time.h>
  118. int main(void)
  119. {
  120. int socket_desc, client_sock, client_size;
  121. struct sockaddr_in server_addr, client_addr;
  122. char server_message[2000], client_message[2000];
  123. // Clean buffers:
  124. memset(server_message, '\0', sizeof(server_message));
  125. memset(client_message, '\0', sizeof(client_message));
  126. // Create socket:
  127. socket_desc = socket(AF_INET, SOCK_STREAM, 0);
  128. if(socket_desc < 0){
  129. printf("Error while creating socket\n");
  130. return -1;
  131. }
  132. printf("Socket created successfully\n");
  133. // Set port and IP:
  134. server_addr.sin_family = AF_INET;
  135. server_addr.sin_port = htons(46999);
  136. server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
  137. // Bind to the set port and IP:
  138. if(bind(socket_desc, (struct sockaddr*)&server_addr, sizeof(server_addr))<0){
  139. printf("Couldn't bind to the port\n");
  140. return -1;
  141. }
  142. printf("Done with binding\n");
  143. // Listen for clients:
  144. // Increasing the backlog allows the Go client to connect and wait
  145. // rather than poll/retry.
  146. if(listen(socket_desc, 100) < 0){
  147. printf("Error while listening\n");
  148. return -1;
  149. }
  150. printf("\nListening for incoming connections.....\n");
  151. // Accept an incoming connection:
  152. client_size = sizeof(client_addr);
  153. int server_run = 1;
  154. do
  155. {
  156. struct timeval start, end;
  157. double cpu_time_used;
  158. gettimeofday(&start, NULL);
  159. client_sock = accept(socket_desc, (struct sockaddr*)&client_addr, &client_size);
  160. if (client_sock < 0){
  161. printf("Can't accept\n");
  162. return -1;
  163. }
  164. // Receive client's message:
  165. if (recv(client_sock, client_message, sizeof(client_message), 0) < 0){
  166. printf("Couldn't receive\n");
  167. return -1;
  168. }
  169. if (strcmp(client_message, "stop") == 0)
  170. {
  171. server_run = 0;
  172. printf("Received stop message.\n");
  173. }
  174. // Respond to client:
  175. strcpy(server_message, "This is the server's message.");
  176. if (send(client_sock, server_message, strlen(server_message), 0) < 0){
  177. printf("Can't send\n");
  178. return -1;
  179. }
  180. // sleep for 5 ms
  181. usleep(5000);
  182. // Closing the socket:
  183. close(client_sock);
  184. gettimeofday(&end, NULL);
  185. cpu_time_used = (end.tv_usec - start.tv_usec) / 1000.0;
  186. if (cpu_time_used > 0.0) // overflow in tv_usec if negative
  187. printf("Server Time: %.4f ms\n", cpu_time_used);
  188. } while(server_run);
  189. close(socket_desc);
  190. return 0;
  191. }
  192. ', sizeof(client_message));
  193. // Create socket:
  194. socket_desc = socket(AF_INET, SOCK_STREAM, 0);
  195. if(socket_desc < 0){
  196. printf("Error while creating socket\n");
  197. return -1;
  198. }
  199. printf("Socket created successfully\n");
  200. // Set port and IP:
  201. server_addr.sin_family = AF_INET;
  202. server_addr.sin_port = htons(46999);
  203. server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
  204. // Bind to the set port and IP:
  205. if(bind(socket_desc, (struct sockaddr*)&server_addr, sizeof(server_addr))<0){
  206. printf("Couldn't bind to the port\n");
  207. return -1;
  208. }
  209. printf("Done with binding\n");
  210. // Listen for clients:
  211. // Increasing the backlog allows the Go client to connect and wait
  212. // rather than poll/retry.
  213. if(listen(socket_desc, 100) < 0){
  214. printf("Error while listening\n");
  215. return -1;
  216. }
  217. printf("\nListening for incoming connections.....\n");
  218. // Accept an incoming connection:
  219. client_size = sizeof(client_addr);
  220. int server_run = 1;
  221. do
  222. {
  223. struct timeval start, end;
  224. double cpu_time_used;
  225. gettimeofday(&start, NULL);
  226. client_sock = accept(socket_desc, (struct sockaddr*)&client_addr, &client_size);
  227. if (client_sock < 0){
  228. printf("Can't accept\n");
  229. return -1;
  230. }
  231. // Receive client's message:
  232. if (recv(client_sock, client_message, sizeof(client_message), 0) < 0){
  233. printf("Couldn't receive\n");
  234. return -1;
  235. }
  236. if (strcmp(client_message, "stop") == 0)
  237. {
  238. server_run = 0;
  239. printf("Received stop message.\n");
  240. }
  241. // Respond to client:
  242. strcpy(server_message, "This is the server's message.");
  243. if (send(client_sock, server_message, strlen(server_message), 0) < 0){
  244. printf("Can't send\n");
  245. return -1;
  246. }
  247. // sleep for 5 ms
  248. usleep(5000);
  249. // Closing the socket:
  250. close(client_sock);
  251. gettimeofday(&end, NULL);
  252. cpu_time_used = (end.tv_usec - start.tv_usec) / 1000.0;
  253. if (cpu_time_used > 0.0) // overflow in tv_usec if negative
  254. printf("Server Time: %.4f ms\n", cpu_time_used);
  255. } while(server_run);
  256. close(socket_desc);
  257. return 0;
  258. }

这是测试用的Go客户端

  1. /*
  2. Adapted from
  3. https://www.linode.com/docs/guides/developing-udp-and-tcp-clients-and-servers-in-go/
  4. Run once the server.c is compiled and running with:
  5. go run client.go 127.0.0.1:46999
  6. */
  7. package main
  8. import (
  9. "fmt"
  10. "net"
  11. "os"
  12. "time"
  13. "github.com/montanaflynn/stats"
  14. "sync"
  15. )
  16. func do_message(wg *sync.WaitGroup, connect string, time_taken *float64) {
  17. defer wg.Done()
  18. message := make([]byte, 128)
  19. start_time := time.Now()
  20. pAddr, err := net.ResolveTCPAddr("tcp", connect)
  21. if err != nil {
  22. return
  23. }
  24. c, err := net.DialTCP("tcp", nil, pAddr)
  25. if err != nil {
  26. fmt.Println(err)
  27. return
  28. }
  29. c.SetLinger(0)
  30. dialed_time := time.Since(start_time)
  31. defer func() {
  32. c.Close()
  33. elapsed_time := time.Since(start_time)
  34. if elapsed_time.Microseconds() > 60 { // microseconds
  35. fmt.Println("Long Elapsed Time: " + dialed_time.String() + ", " + elapsed_time.String())
  36. }
  37. *time_taken = float64(elapsed_time.Microseconds())
  38. }()
  39. text := "{\"service\": \"magic_service_str\"}"
  40. c.Write([]byte(text))
  41. code, _ := c.Read(message) // Does not actually wait for response.
  42. code = code
  43. }
  44. func main() {
  45. main_start := time.Now()
  46. arguments := os.Args
  47. if len(arguments) == 1 {
  48. fmt.Println("Please provide host:port.")
  49. return
  50. }
  51. n_messages := 50
  52. wg := new(sync.WaitGroup)
  53. wg.Add(n_messages)
  54. times := make([]float64, n_messages)
  55. for i := 0; i < n_messages; i++ {
  56. // Used to turn the goroutines into serial implementation
  57. // time.Sleep(5500 * time.Microsecond)
  58. go do_message(wg, arguments[1], &times[i])
  59. }
  60. wg.Wait()
  61. avg, _ := stats.Mean(times)
  62. std, _ := stats.StandardDeviation(times)
  63. fmt.Println("Time taken stats: " + fmt.Sprintf("%.2f", avg / 1000000.0) + " +/- " + fmt.Sprintf("%.2f", std / 1000000.0) + " s")
  64. main_taken := time.Since(main_start)
  65. fmt.Println("Main Taken: " + main_taken.String())
  66. }

在C中更新了pthreaded客户端,并确认问题不是Golang实现:

  1. // gcc client_p.c -o pclient -lpthread
  2. #include <stdio.h>
  3. #include <string.h>
  4. #include <sys/socket.h>
  5. #include <arpa/inet.h>
  6. #include <unistd.h>
  7. #include <stdlib.h>
  8. #include<sys/time.h>
  9. #include <pthread.h>
  10. #include <errno.h>
  11. #ifndef THREAD_LOOP_COUNT
  12. #define THREAD_LOOP_COUNT 1
  13. #endif
  14. /* Subtract the ‘struct timeval’ values X and Y,
  15. storing the result in RESULT.
  16. Return 1 if the difference is negative, otherwise 0.
  17. https://www.gnu.org/software/libc/manual/html_node/Calculating-Elapsed-Time.html
  18. */
  19. int
  20. timeval_subtract (struct timeval *result, struct timeval *x, struct timeval *y)
  21. {
  22. /* Perform the carry for the later subtraction by updating y. */
  23. if (x->tv_usec < y->tv_usec) {
  24. int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1;
  25. y->tv_usec -= 1000000 * nsec;
  26. y->tv_sec += nsec;
  27. }
  28. if (x->tv_usec - y->tv_usec > 1000000) {
  29. int nsec = (x->tv_usec - y->tv_usec) / 1000000;
  30. y->tv_usec += 1000000 * nsec;
  31. y->tv_sec -= nsec;
  32. }
  33. /* Compute the time remaining to wait.
  34. tv_usec is certainly positive. */
  35. result->tv_sec = x->tv_sec - y->tv_sec;
  36. result->tv_usec = x->tv_usec - y->tv_usec;
  37. /* Return 1 if result is negative. */
  38. return x->tv_sec < y->tv_sec;
  39. }
  40. static void* workerThreadFunc(void* arg)
  41. {
  42. int socket_desc;
  43. struct sockaddr_in server_addr;
  44. char server_message[2000], client_message[2000];
  45. // Clean buffers:
  46. memset(server_message,'
    // gcc client_p.c -o pclient -lpthread
  47. #include <stdio.h>
  48. #include <string.h>
  49. #include <sys/socket.h>
  50. #include <arpa/inet.h>
  51. #include <unistd.h>
  52. #include <stdlib.h>
  53. #include<sys/time.h>
  54. #include <pthread.h>
  55. #include <errno.h>
  56. #ifndef THREAD_LOOP_COUNT
  57. #define THREAD_LOOP_COUNT 1
  58. #endif
  59. /* Subtract the ‘struct timeval’ values X and Y,
  60. storing the result in RESULT.
  61. Return 1 if the difference is negative, otherwise 0.
  62. https://www.gnu.org/software/libc/manual/html_node/Calculating-Elapsed-Time.html
  63. */
  64. int
  65. timeval_subtract (struct timeval *result, struct timeval *x, struct timeval *y)
  66. {
  67. /* Perform the carry for the later subtraction by updating y. */
  68. if (x->tv_usec < y->tv_usec) {
  69. int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1;
  70. y->tv_usec -= 1000000 * nsec;
  71. y->tv_sec += nsec;
  72. }
  73. if (x->tv_usec - y->tv_usec > 1000000) {
  74. int nsec = (x->tv_usec - y->tv_usec) / 1000000;
  75. y->tv_usec += 1000000 * nsec;
  76. y->tv_sec -= nsec;
  77. }
  78. /* Compute the time remaining to wait.
  79. tv_usec is certainly positive. */
  80. result->tv_sec = x->tv_sec - y->tv_sec;
  81. result->tv_usec = x->tv_usec - y->tv_usec;
  82. /* Return 1 if result is negative. */
  83. return x->tv_sec < y->tv_sec;
  84. }
  85. static void* workerThreadFunc(void* arg)
  86. {
  87. int socket_desc;
  88. struct sockaddr_in server_addr;
  89. char server_message[2000], client_message[2000];
  90. // Clean buffers:
  91. memset(server_message,'\0',sizeof(server_message));
  92. memset(client_message,'\0',sizeof(client_message));
  93. // Set port and IP the same as server-side:
  94. server_addr.sin_family = AF_INET;
  95. server_addr.sin_port = htons(46999);
  96. server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
  97. int retries = 0;
  98. struct timeval start, end, difference;
  99. double cpu_time_used;
  100. for(int i = 0; i < THREAD_LOOP_COUNT; i++)
  101. {
  102. gettimeofday(&start, NULL);
  103. // Create socket:
  104. socket_desc = socket(AF_INET, SOCK_STREAM, 0);
  105. if(socket_desc < 0){
  106. printf("Unable to create socket\n");
  107. return;
  108. }
  109. // Send connection request to server:
  110. while(connect(socket_desc, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0){
  111. retries++;
  112. if (retries > 10)
  113. {
  114. printf("Unable to connect\n");
  115. retries = 0;
  116. }
  117. usleep(5);
  118. }
  119. int retries = 0;
  120. // Send the message to server:
  121. if(send(socket_desc, client_message, strlen("client message."), 0) < 0){
  122. printf("Unable to send message\n");
  123. close(socket_desc);
  124. return;
  125. }
  126. // Receive the server's response:
  127. if(recv(socket_desc, server_message, sizeof(server_message), 0) < 0){
  128. printf("Error while receiving server's msg\n");
  129. close(socket_desc);
  130. return;
  131. }
  132. // Close the socket:
  133. close(socket_desc);
  134. gettimeofday(&end, NULL);
  135. timeval_subtract (&difference, &end, &start);
  136. double cpu_time_used = (double)difference.tv_sec + (double)difference.tv_usec / 1000000.0;
  137. printf("Client Time: %.4e s\n", cpu_time_used);
  138. }
  139. }
  140. int main(int argc, char **argv)
  141. {
  142. int n_threads = 50;  // default value
  143. if (argc > 1)
  144. n_threads = atoi(argv[1]);
  145. pthread_t *threads = (pthread_t*)malloc(n_threads * sizeof(pthread_t));
  146. struct timeval start, end, difference;
  147. gettimeofday(&start, NULL);
  148. for(int i = 0; i < n_threads; i++)
  149. {
  150. int createRet = pthread_create(&threads[i], NULL, workerThreadFunc, NULL);
  151. if (createRet != 0)
  152. {
  153. printf("failed to create thread\n");
  154. }
  155. }
  156. for(int i = 0; i < n_threads; i++)
  157. pthread_join(threads[i], NULL);
  158. gettimeofday(&end, NULL);
  159. timeval_subtract (&difference, &end, &start);
  160. double cpu_time_used = (double)difference.tv_sec + (double)difference.tv_usec / 1000000.0;
  161. printf("Total Client Time: %.4e s\n", cpu_time_used);
  162. free(threads);
  163. return 0;
  164. }
  165. ',sizeof(server_message));
  166. memset(client_message,'
    // gcc client_p.c -o pclient -lpthread
  167. #include <stdio.h>
  168. #include <string.h>
  169. #include <sys/socket.h>
  170. #include <arpa/inet.h>
  171. #include <unistd.h>
  172. #include <stdlib.h>
  173. #include<sys/time.h>
  174. #include <pthread.h>
  175. #include <errno.h>
  176. #ifndef THREAD_LOOP_COUNT
  177. #define THREAD_LOOP_COUNT 1
  178. #endif
  179. /* Subtract the ‘struct timeval’ values X and Y,
  180. storing the result in RESULT.
  181. Return 1 if the difference is negative, otherwise 0.
  182. https://www.gnu.org/software/libc/manual/html_node/Calculating-Elapsed-Time.html
  183. */
  184. int
  185. timeval_subtract (struct timeval *result, struct timeval *x, struct timeval *y)
  186. {
  187. /* Perform the carry for the later subtraction by updating y. */
  188. if (x->tv_usec < y->tv_usec) {
  189. int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1;
  190. y->tv_usec -= 1000000 * nsec;
  191. y->tv_sec += nsec;
  192. }
  193. if (x->tv_usec - y->tv_usec > 1000000) {
  194. int nsec = (x->tv_usec - y->tv_usec) / 1000000;
  195. y->tv_usec += 1000000 * nsec;
  196. y->tv_sec -= nsec;
  197. }
  198. /* Compute the time remaining to wait.
  199. tv_usec is certainly positive. */
  200. result->tv_sec = x->tv_sec - y->tv_sec;
  201. result->tv_usec = x->tv_usec - y->tv_usec;
  202. /* Return 1 if result is negative. */
  203. return x->tv_sec < y->tv_sec;
  204. }
  205. static void* workerThreadFunc(void* arg)
  206. {
  207. int socket_desc;
  208. struct sockaddr_in server_addr;
  209. char server_message[2000], client_message[2000];
  210. // Clean buffers:
  211. memset(server_message,'\0',sizeof(server_message));
  212. memset(client_message,'\0',sizeof(client_message));
  213. // Set port and IP the same as server-side:
  214. server_addr.sin_family = AF_INET;
  215. server_addr.sin_port = htons(46999);
  216. server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
  217. int retries = 0;
  218. struct timeval start, end, difference;
  219. double cpu_time_used;
  220. for(int i = 0; i < THREAD_LOOP_COUNT; i++)
  221. {
  222. gettimeofday(&start, NULL);
  223. // Create socket:
  224. socket_desc = socket(AF_INET, SOCK_STREAM, 0);
  225. if(socket_desc < 0){
  226. printf("Unable to create socket\n");
  227. return;
  228. }
  229. // Send connection request to server:
  230. while(connect(socket_desc, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0){
  231. retries++;
  232. if (retries > 10)
  233. {
  234. printf("Unable to connect\n");
  235. retries = 0;
  236. }
  237. usleep(5);
  238. }
  239. int retries = 0;
  240. // Send the message to server:
  241. if(send(socket_desc, client_message, strlen("client message."), 0) < 0){
  242. printf("Unable to send message\n");
  243. close(socket_desc);
  244. return;
  245. }
  246. // Receive the server's response:
  247. if(recv(socket_desc, server_message, sizeof(server_message), 0) < 0){
  248. printf("Error while receiving server's msg\n");
  249. close(socket_desc);
  250. return;
  251. }
  252. // Close the socket:
  253. close(socket_desc);
  254. gettimeofday(&end, NULL);
  255. timeval_subtract (&difference, &end, &start);
  256. double cpu_time_used = (double)difference.tv_sec + (double)difference.tv_usec / 1000000.0;
  257. printf("Client Time: %.4e s\n", cpu_time_used);
  258. }
  259. }
  260. int main(int argc, char **argv)
  261. {
  262. int n_threads = 50;  // default value
  263. if (argc > 1)
  264. n_threads = atoi(argv[1]);
  265. pthread_t *threads = (pthread_t*)malloc(n_threads * sizeof(pthread_t));
  266. struct timeval start, end, difference;
  267. gettimeofday(&start, NULL);
  268. for(int i = 0; i < n_threads; i++)
  269. {
  270. int createRet = pthread_create(&threads[i], NULL, workerThreadFunc, NULL);
  271. if (createRet != 0)
  272. {
  273. printf("failed to create thread\n");
  274. }
  275. }
  276. for(int i = 0; i < n_threads; i++)
  277. pthread_join(threads[i], NULL);
  278. gettimeofday(&end, NULL);
  279. timeval_subtract (&difference, &end, &start);
  280. double cpu_time_used = (double)difference.tv_sec + (double)difference.tv_usec / 1000000.0;
  281. printf("Total Client Time: %.4e s\n", cpu_time_used);
  282. free(threads);
  283. return 0;
  284. }
  285. ',sizeof(client_message));
  286. // Set port and IP the same as server-side:
  287. server_addr.sin_family = AF_INET;
  288. server_addr.sin_port = htons(46999);
  289. server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
  290. int retries = 0;
  291. struct timeval start, end, difference;
  292. double cpu_time_used;
  293. for(int i = 0; i < THREAD_LOOP_COUNT; i++)
  294. {
  295. gettimeofday(&start, NULL);
  296. // Create socket:
  297. socket_desc = socket(AF_INET, SOCK_STREAM, 0);
  298. if(socket_desc < 0){
  299. printf("Unable to create socket\n");
  300. return;
  301. }
  302. // Send connection request to server:
  303. while(connect(socket_desc, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0){
  304. retries++;
  305. if (retries > 10)
  306. {
  307. printf("Unable to connect\n");
  308. retries = 0;
  309. }
  310. usleep(5);
  311. }
  312. int retries = 0;
  313. // Send the message to server:
  314. if(send(socket_desc, client_message, strlen("client message."), 0) < 0){
  315. printf("Unable to send message\n");
  316. close(socket_desc);
  317. return;
  318. }
  319. // Receive the server's response:
  320. if(recv(socket_desc, server_message, sizeof(server_message), 0) < 0){
  321. printf("Error while receiving server's msg\n");
  322. close(socket_desc);
  323. return;
  324. }
  325. // Close the socket:
  326. close(socket_desc);
  327. gettimeofday(&end, NULL);
  328. timeval_subtract (&difference, &end, &start);
  329. double cpu_time_used = (double)difference.tv_sec + (double)difference.tv_usec / 1000000.0;
  330. printf("Client Time: %.4e s\n", cpu_time_used);
  331. }
  332. }
  333. int main(int argc, char **argv)
  334. {
  335. int n_threads = 50; // default value
  336. if (argc > 1)
  337. n_threads = atoi(argv[1]);
  338. pthread_t *threads = (pthread_t*)malloc(n_threads * sizeof(pthread_t));
  339. struct timeval start, end, difference;
  340. gettimeofday(&start, NULL);
  341. for(int i = 0; i < n_threads; i++)
  342. {
  343. int createRet = pthread_create(&threads[i], NULL, workerThreadFunc, NULL);
  344. if (createRet != 0)
  345. {
  346. printf("failed to create thread\n");
  347. }
  348. }
  349. for(int i = 0; i < n_threads; i++)
  350. pthread_join(threads[i], NULL);
  351. gettimeofday(&end, NULL);
  352. timeval_subtract (&difference, &end, &start);
  353. double cpu_time_used = (double)difference.tv_sec + (double)difference.tv_usec / 1000000.0;
  354. printf("Total Client Time: %.4e s\n", cpu_time_used);
  355. free(threads);
  356. return 0;
  357. }
英文:

Update

With the added pthreaded C client, the problem is recreated, indicating the long connection times are part of the TCP protocol, rather than specific implementations. Altering those protocols doesn't seem easily available.

Initial Question

I believe my question is largely: What does the Golang net package do when attempting to connect to a server over TCP and:

  1. The server has no connections available, even in backlog.
  2. The connection is not refused/failed.

There seems to be a large amount of overhead in that connection with server response times ramping up from 5 ms to several seconds. This was seen both in a production environment and in the minimal example below. The proper solution is to use connection pools to the server, which will be implemented. This is largely my curiosity.

To reproduce:

  1. Run server with backlog = 1, run client.go.
    • All 50 goroutines fire at once, with a total completion time of almost 2 minutes.
  2. Run server with backlog = 100, run client.go.
    • All 50 goroutines fire at once, queue up connected to the server, and complete in ~260 ms.
  3. Running three C clients utilizing 50 us retry times was able to complete connections within 12 ms on average, so didn't see this issue.

Example output for backlog = 1 (first time is time to dial, second is time to completion):

  1. user@computer ~/tcp-tests $ go run client.go 127.0.0.1:46999
  2. Long Elapsed Time: 216.579&#181;s, 315.196&#181;s
  3. Long Elapsed Time: 274.169&#181;s, 5.970873ms
  4. Long Elapsed Time: 74.4&#181;s, 10.753871ms
  5. Long Elapsed Time: 590.965&#181;s, 205.851066ms
  6. Long Elapsed Time: 1.029287689s, 1.029574065s
  7. Long Elapsed Time: 1.02945649s, 1.035098229s
  8. ...
  9. Long Elapsed Time: 3.045881865s, 6.378597166s
  10. Long Elapsed Time: 3.045314838s, 6.383783688s
  11. Time taken stats: 2.85 +/- 1.59 s // average +/- STDEV
  12. Main Taken: 6.384677948s

Example output for backlog = 100:

  1. ...
  2. Long Elapsed Time: 330.098&#181;s, 251.004077ms
  3. Long Elapsed Time: 298.146&#181;s, 256.187795ms
  4. Long Elapsed Time: 315.832&#181;s, 261.523685ms
  5. Time taken stats: 0.13 +/- 0.08 s
  6. Main Taken: 263.186955ms

So what's going on under the hood of net.DialTCP (we used other flavors of dial as well, with no discernible difference) that causes the dial time to grow?

  • Polling time between attempts to make a connection?
  • An RFC 5681 Global Congestion Control (likely including mutex lock?) variable that gets incremented on all the initial failed connection attempts?
  • Something else?

I'm leaning towards the first two, as the 1s, 3s, 5s values seem to be magic numbers. They show up both on my modest local machine, and a large scale production environment.

Here is the minimal server written in C. The configuration value of interest is the backlog argument to listen.

  1. /*
  2. Adapted from
  3. https://www.geeksforgeeks.org/tcp-server-client-implementation-in-c/
  4. Compile and run with:
  5. gcc server.c -o server; ./server
  6. */
  7. #include &lt;stdio.h&gt;
  8. #include &lt;string.h&gt;
  9. #include &lt;sys/socket.h&gt;
  10. #include &lt;arpa/inet.h&gt;
  11. #include &lt;unistd.h&gt;
  12. #include &lt;sys/time.h&gt;
  13. int main(void)
  14. {
  15. int socket_desc, client_sock, client_size;
  16. struct sockaddr_in server_addr, client_addr;
  17. char server_message[2000], client_message[2000];
  18. // Clean buffers:
  19. memset(server_message, &#39;
    /*
  20. Adapted from
  21. https://www.geeksforgeeks.org/tcp-server-client-implementation-in-c/
  22. Compile and run with:
  23. gcc server.c -o server; ./server
  24. */
  25. #include &lt;stdio.h&gt;
  26. #include &lt;string.h&gt;
  27. #include &lt;sys/socket.h&gt;
  28. #include &lt;arpa/inet.h&gt;
  29. #include &lt;unistd.h&gt;
  30. #include &lt;sys/time.h&gt;
  31. int main(void)
  32. {
  33. int socket_desc, client_sock, client_size;
  34. struct sockaddr_in server_addr, client_addr;
  35. char server_message[2000], client_message[2000];
  36. // Clean buffers:
  37. memset(server_message, &#39;\0&#39;, sizeof(server_message));
  38. memset(client_message, &#39;\0&#39;, sizeof(client_message));
  39. // Create socket:
  40. socket_desc = socket(AF_INET, SOCK_STREAM, 0);
  41. if(socket_desc &lt; 0){
  42. printf(&quot;Error while creating socket\n&quot;);
  43. return -1;
  44. }
  45. printf(&quot;Socket created successfully\n&quot;);
  46. // Set port and IP:
  47. server_addr.sin_family = AF_INET;
  48. server_addr.sin_port = htons(46999);
  49. server_addr.sin_addr.s_addr = inet_addr(&quot;127.0.0.1&quot;);
  50. // Bind to the set port and IP:
  51. if(bind(socket_desc, (struct sockaddr*)&amp;server_addr, sizeof(server_addr))&lt;0){
  52. printf(&quot;Couldn&#39;t bind to the port\n&quot;);
  53. return -1;
  54. }
  55. printf(&quot;Done with binding\n&quot;);
  56. // Listen for clients:
  57. // Increasing the backlog allows the Go client to connect and wait
  58. // rather than poll/retry.
  59. if(listen(socket_desc, 100) &lt; 0){
  60. printf(&quot;Error while listening\n&quot;);
  61. return -1;
  62. }
  63. printf(&quot;\nListening for incoming connections.....\n&quot;);
  64. // Accept an incoming connection:
  65. client_size = sizeof(client_addr);
  66. int server_run = 1;
  67. do
  68. {
  69. struct timeval start, end;
  70. double cpu_time_used;
  71. gettimeofday(&amp;start, NULL);
  72. client_sock = accept(socket_desc, (struct sockaddr*)&amp;client_addr, &amp;client_size);
  73. if (client_sock &lt; 0){
  74. printf(&quot;Can&#39;t accept\n&quot;);
  75. return -1;
  76. }
  77. // Receive client&#39;s message:
  78. if (recv(client_sock, client_message, sizeof(client_message), 0) &lt; 0){
  79. printf(&quot;Couldn&#39;t receive\n&quot;);
  80. return -1;
  81. }
  82. if (strcmp(client_message, &quot;stop&quot;) == 0)
  83. {
  84. server_run = 0;
  85. printf(&quot;Received stop message.\n&quot;);
  86. }
  87. // Respond to client:
  88. strcpy(server_message, &quot;This is the server&#39;s message.&quot;);
  89. if (send(client_sock, server_message, strlen(server_message), 0) &lt; 0){
  90. printf(&quot;Can&#39;t send\n&quot;);
  91. return -1;
  92. }
  93. // sleep for 5 ms
  94. usleep(5000);
  95. // Closing the socket:
  96. close(client_sock);
  97. gettimeofday(&amp;end, NULL);
  98. cpu_time_used = (end.tv_usec - start.tv_usec) / 1000.0;
  99. if (cpu_time_used &gt; 0.0) // overflow in tv_usec if negative
  100. printf(&quot;Server Time: %.4f ms\n&quot;, cpu_time_used);
  101. } while(server_run);
  102. close(socket_desc);
  103. return 0;
  104. }
  105. &#39;, sizeof(server_message));
  106. memset(client_message, &#39;
    /*
  107. Adapted from
  108. https://www.geeksforgeeks.org/tcp-server-client-implementation-in-c/
  109. Compile and run with:
  110. gcc server.c -o server; ./server
  111. */
  112. #include &lt;stdio.h&gt;
  113. #include &lt;string.h&gt;
  114. #include &lt;sys/socket.h&gt;
  115. #include &lt;arpa/inet.h&gt;
  116. #include &lt;unistd.h&gt;
  117. #include &lt;sys/time.h&gt;
  118. int main(void)
  119. {
  120. int socket_desc, client_sock, client_size;
  121. struct sockaddr_in server_addr, client_addr;
  122. char server_message[2000], client_message[2000];
  123. // Clean buffers:
  124. memset(server_message, &#39;\0&#39;, sizeof(server_message));
  125. memset(client_message, &#39;\0&#39;, sizeof(client_message));
  126. // Create socket:
  127. socket_desc = socket(AF_INET, SOCK_STREAM, 0);
  128. if(socket_desc &lt; 0){
  129. printf(&quot;Error while creating socket\n&quot;);
  130. return -1;
  131. }
  132. printf(&quot;Socket created successfully\n&quot;);
  133. // Set port and IP:
  134. server_addr.sin_family = AF_INET;
  135. server_addr.sin_port = htons(46999);
  136. server_addr.sin_addr.s_addr = inet_addr(&quot;127.0.0.1&quot;);
  137. // Bind to the set port and IP:
  138. if(bind(socket_desc, (struct sockaddr*)&amp;server_addr, sizeof(server_addr))&lt;0){
  139. printf(&quot;Couldn&#39;t bind to the port\n&quot;);
  140. return -1;
  141. }
  142. printf(&quot;Done with binding\n&quot;);
  143. // Listen for clients:
  144. // Increasing the backlog allows the Go client to connect and wait
  145. // rather than poll/retry.
  146. if(listen(socket_desc, 100) &lt; 0){
  147. printf(&quot;Error while listening\n&quot;);
  148. return -1;
  149. }
  150. printf(&quot;\nListening for incoming connections.....\n&quot;);
  151. // Accept an incoming connection:
  152. client_size = sizeof(client_addr);
  153. int server_run = 1;
  154. do
  155. {
  156. struct timeval start, end;
  157. double cpu_time_used;
  158. gettimeofday(&amp;start, NULL);
  159. client_sock = accept(socket_desc, (struct sockaddr*)&amp;client_addr, &amp;client_size);
  160. if (client_sock &lt; 0){
  161. printf(&quot;Can&#39;t accept\n&quot;);
  162. return -1;
  163. }
  164. // Receive client&#39;s message:
  165. if (recv(client_sock, client_message, sizeof(client_message), 0) &lt; 0){
  166. printf(&quot;Couldn&#39;t receive\n&quot;);
  167. return -1;
  168. }
  169. if (strcmp(client_message, &quot;stop&quot;) == 0)
  170. {
  171. server_run = 0;
  172. printf(&quot;Received stop message.\n&quot;);
  173. }
  174. // Respond to client:
  175. strcpy(server_message, &quot;This is the server&#39;s message.&quot;);
  176. if (send(client_sock, server_message, strlen(server_message), 0) &lt; 0){
  177. printf(&quot;Can&#39;t send\n&quot;);
  178. return -1;
  179. }
  180. // sleep for 5 ms
  181. usleep(5000);
  182. // Closing the socket:
  183. close(client_sock);
  184. gettimeofday(&amp;end, NULL);
  185. cpu_time_used = (end.tv_usec - start.tv_usec) / 1000.0;
  186. if (cpu_time_used &gt; 0.0) // overflow in tv_usec if negative
  187. printf(&quot;Server Time: %.4f ms\n&quot;, cpu_time_used);
  188. } while(server_run);
  189. close(socket_desc);
  190. return 0;
  191. }
  192. &#39;, sizeof(client_message));
  193. // Create socket:
  194. socket_desc = socket(AF_INET, SOCK_STREAM, 0);
  195. if(socket_desc &lt; 0){
  196. printf(&quot;Error while creating socket\n&quot;);
  197. return -1;
  198. }
  199. printf(&quot;Socket created successfully\n&quot;);
  200. // Set port and IP:
  201. server_addr.sin_family = AF_INET;
  202. server_addr.sin_port = htons(46999);
  203. server_addr.sin_addr.s_addr = inet_addr(&quot;127.0.0.1&quot;);
  204. // Bind to the set port and IP:
  205. if(bind(socket_desc, (struct sockaddr*)&amp;server_addr, sizeof(server_addr))&lt;0){
  206. printf(&quot;Couldn&#39;t bind to the port\n&quot;);
  207. return -1;
  208. }
  209. printf(&quot;Done with binding\n&quot;);
  210. // Listen for clients:
  211. // Increasing the backlog allows the Go client to connect and wait
  212. // rather than poll/retry.
  213. if(listen(socket_desc, 100) &lt; 0){
  214. printf(&quot;Error while listening\n&quot;);
  215. return -1;
  216. }
  217. printf(&quot;\nListening for incoming connections.....\n&quot;);
  218. // Accept an incoming connection:
  219. client_size = sizeof(client_addr);
  220. int server_run = 1;
  221. do
  222. {
  223. struct timeval start, end;
  224. double cpu_time_used;
  225. gettimeofday(&amp;start, NULL);
  226. client_sock = accept(socket_desc, (struct sockaddr*)&amp;client_addr, &amp;client_size);
  227. if (client_sock &lt; 0){
  228. printf(&quot;Can&#39;t accept\n&quot;);
  229. return -1;
  230. }
  231. // Receive client&#39;s message:
  232. if (recv(client_sock, client_message, sizeof(client_message), 0) &lt; 0){
  233. printf(&quot;Couldn&#39;t receive\n&quot;);
  234. return -1;
  235. }
  236. if (strcmp(client_message, &quot;stop&quot;) == 0)
  237. {
  238. server_run = 0;
  239. printf(&quot;Received stop message.\n&quot;);
  240. }
  241. // Respond to client:
  242. strcpy(server_message, &quot;This is the server&#39;s message.&quot;);
  243. if (send(client_sock, server_message, strlen(server_message), 0) &lt; 0){
  244. printf(&quot;Can&#39;t send\n&quot;);
  245. return -1;
  246. }
  247. // sleep for 5 ms
  248. usleep(5000);
  249. // Closing the socket:
  250. close(client_sock);
  251. gettimeofday(&amp;end, NULL);
  252. cpu_time_used = (end.tv_usec - start.tv_usec) / 1000.0;
  253. if (cpu_time_used &gt; 0.0) // overflow in tv_usec if negative
  254. printf(&quot;Server Time: %.4f ms\n&quot;, cpu_time_used);
  255. } while(server_run);
  256. close(socket_desc);
  257. return 0;
  258. }

Here is the testing Go client

  1. /*
  2. Adapted from
  3. https://www.linode.com/docs/guides/developing-udp-and-tcp-clients-and-servers-in-go/
  4. Run once the server.c is compiled and running with:
  5. go run client.go 127.0.0.1:46999
  6. */
  7. package main
  8. import (
  9. &quot;fmt&quot;
  10. &quot;net&quot;
  11. &quot;os&quot;
  12. &quot;time&quot;
  13. &quot;github.com/montanaflynn/stats&quot;
  14. &quot;sync&quot;
  15. )
  16. func do_message(wg *sync.WaitGroup, connect string, time_taken *float64) {
  17. defer wg.Done()
  18. message := make([]byte, 128)
  19. start_time := time.Now()
  20. pAddr, err := net.ResolveTCPAddr(&quot;tcp&quot;, connect)
  21. if err != nil {
  22. return
  23. }
  24. c, err := net.DialTCP(&quot;tcp&quot;, nil, pAddr)
  25. if err != nil {
  26. fmt.Println(err)
  27. return
  28. }
  29. c.SetLinger(0)
  30. dialed_time := time.Since(start_time)
  31. defer func() {
  32. c.Close()
  33. elapsed_time := time.Since(start_time)
  34. if elapsed_time.Microseconds() &gt; 60 { // microseconds
  35. fmt.Println(&quot;Long Elapsed Time: &quot; + dialed_time.String() + &quot;, &quot; + elapsed_time.String())
  36. }
  37. *time_taken = float64(elapsed_time.Microseconds())
  38. }()
  39. text := &quot;{\&quot;service\&quot;: \&quot;magic_service_str\&quot;}&quot;
  40. c.Write([]byte(text))
  41. code, _ := c.Read(message) // Does not actually wait for response.
  42. code = code
  43. }
  44. func main() {
  45. main_start := time.Now()
  46. arguments := os.Args
  47. if len(arguments) == 1 {
  48. fmt.Println(&quot;Please provide host:port.&quot;)
  49. return
  50. }
  51. n_messages := 50
  52. wg := new(sync.WaitGroup)
  53. wg.Add(n_messages)
  54. times := make([]float64, n_messages)
  55. for i := 0; i &lt; n_messages; i++ {
  56. // Used to turn the goroutines into serial implementation
  57. // time.Sleep(5500 * time.Microsecond)
  58. go do_message(wg, arguments[1], &amp;times[i])
  59. }
  60. wg.Wait()
  61. avg, _ := stats.Mean(times)
  62. std, _ := stats.StandardDeviation(times)
  63. fmt.Println(&quot;Time taken stats: &quot; + fmt.Sprintf(&quot;%.2f&quot;, avg / 1000000.0) + &quot; +/- &quot; + fmt.Sprintf(&quot;%.2f&quot;, std / 1000000.0) + &quot; s&quot;)
  64. main_taken := time.Since(main_start)
  65. fmt.Println(&quot;Main Taken: &quot; + main_taken.String())
  66. }

Updated pthreaded client in C and confirmed the issue is not the Golang implementation:

  1. // gcc client_p.c -o pclient -lpthread
  2. #include &lt;stdio.h&gt;
  3. #include &lt;string.h&gt;
  4. #include &lt;sys/socket.h&gt;
  5. #include &lt;arpa/inet.h&gt;
  6. #include &lt;unistd.h&gt;
  7. #include &lt;stdlib.h&gt;
  8. #include&lt;sys/time.h&gt;
  9. #include &lt;pthread.h&gt;
  10. #include &lt;errno.h&gt;
  11. #ifndef THREAD_LOOP_COUNT
  12. #define THREAD_LOOP_COUNT 1
  13. #endif
  14. /* Subtract the ‘struct timeval’ values X and Y,
  15. storing the result in RESULT.
  16. Return 1 if the difference is negative, otherwise 0.
  17. https://www.gnu.org/software/libc/manual/html_node/Calculating-Elapsed-Time.html
  18. */
  19. int
  20. timeval_subtract (struct timeval *result, struct timeval *x, struct timeval *y)
  21. {
  22. /* Perform the carry for the later subtraction by updating y. */
  23. if (x-&gt;tv_usec &lt; y-&gt;tv_usec) {
  24. int nsec = (y-&gt;tv_usec - x-&gt;tv_usec) / 1000000 + 1;
  25. y-&gt;tv_usec -= 1000000 * nsec;
  26. y-&gt;tv_sec += nsec;
  27. }
  28. if (x-&gt;tv_usec - y-&gt;tv_usec &gt; 1000000) {
  29. int nsec = (x-&gt;tv_usec - y-&gt;tv_usec) / 1000000;
  30. y-&gt;tv_usec += 1000000 * nsec;
  31. y-&gt;tv_sec -= nsec;
  32. }
  33. /* Compute the time remaining to wait.
  34. tv_usec is certainly positive. */
  35. result-&gt;tv_sec = x-&gt;tv_sec - y-&gt;tv_sec;
  36. result-&gt;tv_usec = x-&gt;tv_usec - y-&gt;tv_usec;
  37. /* Return 1 if result is negative. */
  38. return x-&gt;tv_sec &lt; y-&gt;tv_sec;
  39. }
  40. static void* workerThreadFunc(void* arg)
  41. {
  42. int socket_desc;
  43. struct sockaddr_in server_addr;
  44. char server_message[2000], client_message[2000];
  45. // Clean buffers:
  46. memset(server_message,&#39;
    // gcc client_p.c -o pclient -lpthread
  47. #include &lt;stdio.h&gt;
  48. #include &lt;string.h&gt;
  49. #include &lt;sys/socket.h&gt;
  50. #include &lt;arpa/inet.h&gt;
  51. #include &lt;unistd.h&gt;
  52. #include &lt;stdlib.h&gt;
  53. #include&lt;sys/time.h&gt;
  54. #include &lt;pthread.h&gt;
  55. #include &lt;errno.h&gt;
  56. #ifndef THREAD_LOOP_COUNT
  57. #define THREAD_LOOP_COUNT 1
  58. #endif
  59. /* Subtract the ‘struct timeval’ values X and Y,
  60. storing the result in RESULT.
  61. Return 1 if the difference is negative, otherwise 0.
  62. https://www.gnu.org/software/libc/manual/html_node/Calculating-Elapsed-Time.html
  63. */
  64. int
  65. timeval_subtract (struct timeval *result, struct timeval *x, struct timeval *y)
  66. {
  67. /* Perform the carry for the later subtraction by updating y. */
  68. if (x-&gt;tv_usec &lt; y-&gt;tv_usec) {
  69. int nsec = (y-&gt;tv_usec - x-&gt;tv_usec) / 1000000 + 1;
  70. y-&gt;tv_usec -= 1000000 * nsec;
  71. y-&gt;tv_sec += nsec;
  72. }
  73. if (x-&gt;tv_usec - y-&gt;tv_usec &gt; 1000000) {
  74. int nsec = (x-&gt;tv_usec - y-&gt;tv_usec) / 1000000;
  75. y-&gt;tv_usec += 1000000 * nsec;
  76. y-&gt;tv_sec -= nsec;
  77. }
  78. /* Compute the time remaining to wait.
  79. tv_usec is certainly positive. */
  80. result-&gt;tv_sec = x-&gt;tv_sec - y-&gt;tv_sec;
  81. result-&gt;tv_usec = x-&gt;tv_usec - y-&gt;tv_usec;
  82. /* Return 1 if result is negative. */
  83. return x-&gt;tv_sec &lt; y-&gt;tv_sec;
  84. }
  85. static void* workerThreadFunc(void* arg)
  86. {
  87. int socket_desc;
  88. struct sockaddr_in server_addr;
  89. char server_message[2000], client_message[2000];
  90. // Clean buffers:
  91. memset(server_message,&#39;\0&#39;,sizeof(server_message));
  92. memset(client_message,&#39;\0&#39;,sizeof(client_message));
  93. // Set port and IP the same as server-side:
  94. server_addr.sin_family = AF_INET;
  95. server_addr.sin_port = htons(46999);
  96. server_addr.sin_addr.s_addr = inet_addr(&quot;127.0.0.1&quot;);
  97. int retries = 0;
  98. struct timeval start, end, difference;
  99. double cpu_time_used;
  100. for(int i = 0; i &lt; THREAD_LOOP_COUNT; i++)
  101. {
  102. gettimeofday(&amp;start, NULL);
  103. // Create socket:
  104. socket_desc = socket(AF_INET, SOCK_STREAM, 0);
  105. if(socket_desc &lt; 0){
  106. printf(&quot;Unable to create socket\n&quot;);
  107. return;
  108. }
  109. // Send connection request to server:
  110. while(connect(socket_desc, (struct sockaddr*)&amp;server_addr, sizeof(server_addr)) &lt; 0){
  111. retries++;
  112. if (retries &gt; 10)
  113. {
  114. printf(&quot;Unable to connect\n&quot;);
  115. retries = 0;
  116. }
  117. usleep(5);
  118. }
  119. int retries = 0;
  120. // Send the message to server:
  121. if(send(socket_desc, client_message, strlen(&quot;client message.&quot;), 0) &lt; 0){
  122. printf(&quot;Unable to send message\n&quot;);
  123. close(socket_desc);
  124. return;
  125. }
  126. // Receive the server&#39;s response:
  127. if(recv(socket_desc, server_message, sizeof(server_message), 0) &lt; 0){
  128. printf(&quot;Error while receiving server&#39;s msg\n&quot;);
  129. close(socket_desc);
  130. return;
  131. }
  132. // Close the socket:
  133. close(socket_desc);
  134. gettimeofday(&amp;end, NULL);
  135. timeval_subtract (&amp;difference, &amp;end, &amp;start);
  136. double cpu_time_used = (double)difference.tv_sec + (double)difference.tv_usec / 1000000.0;
  137. printf(&quot;Client Time: %.4e s\n&quot;, cpu_time_used);
  138. }
  139. }
  140. int main(int argc, char **argv)
  141. {
  142. int n_threads = 50;  // default value
  143. if (argc &gt; 1)
  144. n_threads = atoi(argv[1]);
  145. pthread_t *threads = (pthread_t*)malloc(n_threads * sizeof(pthread_t));
  146. struct timeval start, end, difference;
  147. gettimeofday(&amp;start, NULL);
  148. for(int i = 0; i &lt; n_threads; i++)
  149. {
  150. int createRet = pthread_create(&amp;threads[i], NULL, workerThreadFunc, NULL);
  151. if (createRet != 0)
  152. {
  153. printf(&quot;failed to create thread\n&quot;);
  154. }
  155. }
  156. for(int i = 0; i &lt; n_threads; i++)
  157. pthread_join(threads[i], NULL);
  158. gettimeofday(&amp;end, NULL);
  159. timeval_subtract (&amp;difference, &amp;end, &amp;start);
  160. double cpu_time_used = (double)difference.tv_sec + (double)difference.tv_usec / 1000000.0;
  161. printf(&quot;Total Client Time: %.4e s\n&quot;, cpu_time_used);
  162. free(threads);
  163. return 0;
  164. }
  165. &#39;,sizeof(server_message));
  166. memset(client_message,&#39;
    // gcc client_p.c -o pclient -lpthread
  167. #include &lt;stdio.h&gt;
  168. #include &lt;string.h&gt;
  169. #include &lt;sys/socket.h&gt;
  170. #include &lt;arpa/inet.h&gt;
  171. #include &lt;unistd.h&gt;
  172. #include &lt;stdlib.h&gt;
  173. #include&lt;sys/time.h&gt;
  174. #include &lt;pthread.h&gt;
  175. #include &lt;errno.h&gt;
  176. #ifndef THREAD_LOOP_COUNT
  177. #define THREAD_LOOP_COUNT 1
  178. #endif
  179. /* Subtract the ‘struct timeval’ values X and Y,
  180. storing the result in RESULT.
  181. Return 1 if the difference is negative, otherwise 0.
  182. https://www.gnu.org/software/libc/manual/html_node/Calculating-Elapsed-Time.html
  183. */
  184. int
  185. timeval_subtract (struct timeval *result, struct timeval *x, struct timeval *y)
  186. {
  187. /* Perform the carry for the later subtraction by updating y. */
  188. if (x-&gt;tv_usec &lt; y-&gt;tv_usec) {
  189. int nsec = (y-&gt;tv_usec - x-&gt;tv_usec) / 1000000 + 1;
  190. y-&gt;tv_usec -= 1000000 * nsec;
  191. y-&gt;tv_sec += nsec;
  192. }
  193. if (x-&gt;tv_usec - y-&gt;tv_usec &gt; 1000000) {
  194. int nsec = (x-&gt;tv_usec - y-&gt;tv_usec) / 1000000;
  195. y-&gt;tv_usec += 1000000 * nsec;
  196. y-&gt;tv_sec -= nsec;
  197. }
  198. /* Compute the time remaining to wait.
  199. tv_usec is certainly positive. */
  200. result-&gt;tv_sec = x-&gt;tv_sec - y-&gt;tv_sec;
  201. result-&gt;tv_usec = x-&gt;tv_usec - y-&gt;tv_usec;
  202. /* Return 1 if result is negative. */
  203. return x-&gt;tv_sec &lt; y-&gt;tv_sec;
  204. }
  205. static void* workerThreadFunc(void* arg)
  206. {
  207. int socket_desc;
  208. struct sockaddr_in server_addr;
  209. char server_message[2000], client_message[2000];
  210. // Clean buffers:
  211. memset(server_message,&#39;\0&#39;,sizeof(server_message));
  212. memset(client_message,&#39;\0&#39;,sizeof(client_message));
  213. // Set port and IP the same as server-side:
  214. server_addr.sin_family = AF_INET;
  215. server_addr.sin_port = htons(46999);
  216. server_addr.sin_addr.s_addr = inet_addr(&quot;127.0.0.1&quot;);
  217. int retries = 0;
  218. struct timeval start, end, difference;
  219. double cpu_time_used;
  220. for(int i = 0; i &lt; THREAD_LOOP_COUNT; i++)
  221. {
  222. gettimeofday(&amp;start, NULL);
  223. // Create socket:
  224. socket_desc = socket(AF_INET, SOCK_STREAM, 0);
  225. if(socket_desc &lt; 0){
  226. printf(&quot;Unable to create socket\n&quot;);
  227. return;
  228. }
  229. // Send connection request to server:
  230. while(connect(socket_desc, (struct sockaddr*)&amp;server_addr, sizeof(server_addr)) &lt; 0){
  231. retries++;
  232. if (retries &gt; 10)
  233. {
  234. printf(&quot;Unable to connect\n&quot;);
  235. retries = 0;
  236. }
  237. usleep(5);
  238. }
  239. int retries = 0;
  240. // Send the message to server:
  241. if(send(socket_desc, client_message, strlen(&quot;client message.&quot;), 0) &lt; 0){
  242. printf(&quot;Unable to send message\n&quot;);
  243. close(socket_desc);
  244. return;
  245. }
  246. // Receive the server&#39;s response:
  247. if(recv(socket_desc, server_message, sizeof(server_message), 0) &lt; 0){
  248. printf(&quot;Error while receiving server&#39;s msg\n&quot;);
  249. close(socket_desc);
  250. return;
  251. }
  252. // Close the socket:
  253. close(socket_desc);
  254. gettimeofday(&amp;end, NULL);
  255. timeval_subtract (&amp;difference, &amp;end, &amp;start);
  256. double cpu_time_used = (double)difference.tv_sec + (double)difference.tv_usec / 1000000.0;
  257. printf(&quot;Client Time: %.4e s\n&quot;, cpu_time_used);
  258. }
  259. }
  260. int main(int argc, char **argv)
  261. {
  262. int n_threads = 50;  // default value
  263. if (argc &gt; 1)
  264. n_threads = atoi(argv[1]);
  265. pthread_t *threads = (pthread_t*)malloc(n_threads * sizeof(pthread_t));
  266. struct timeval start, end, difference;
  267. gettimeofday(&amp;start, NULL);
  268. for(int i = 0; i &lt; n_threads; i++)
  269. {
  270. int createRet = pthread_create(&amp;threads[i], NULL, workerThreadFunc, NULL);
  271. if (createRet != 0)
  272. {
  273. printf(&quot;failed to create thread\n&quot;);
  274. }
  275. }
  276. for(int i = 0; i &lt; n_threads; i++)
  277. pthread_join(threads[i], NULL);
  278. gettimeofday(&amp;end, NULL);
  279. timeval_subtract (&amp;difference, &amp;end, &amp;start);
  280. double cpu_time_used = (double)difference.tv_sec + (double)difference.tv_usec / 1000000.0;
  281. printf(&quot;Total Client Time: %.4e s\n&quot;, cpu_time_used);
  282. free(threads);
  283. return 0;
  284. }
  285. &#39;,sizeof(client_message));
  286. // Set port and IP the same as server-side:
  287. server_addr.sin_family = AF_INET;
  288. server_addr.sin_port = htons(46999);
  289. server_addr.sin_addr.s_addr = inet_addr(&quot;127.0.0.1&quot;);
  290. int retries = 0;
  291. struct timeval start, end, difference;
  292. double cpu_time_used;
  293. for(int i = 0; i &lt; THREAD_LOOP_COUNT; i++)
  294. {
  295. gettimeofday(&amp;start, NULL);
  296. // Create socket:
  297. socket_desc = socket(AF_INET, SOCK_STREAM, 0);
  298. if(socket_desc &lt; 0){
  299. printf(&quot;Unable to create socket\n&quot;);
  300. return;
  301. }
  302. // Send connection request to server:
  303. while(connect(socket_desc, (struct sockaddr*)&amp;server_addr, sizeof(server_addr)) &lt; 0){
  304. retries++;
  305. if (retries &gt; 10)
  306. {
  307. printf(&quot;Unable to connect\n&quot;);
  308. retries = 0;
  309. }
  310. usleep(5);
  311. }
  312. int retries = 0;
  313. // Send the message to server:
  314. if(send(socket_desc, client_message, strlen(&quot;client message.&quot;), 0) &lt; 0){
  315. printf(&quot;Unable to send message\n&quot;);
  316. close(socket_desc);
  317. return;
  318. }
  319. // Receive the server&#39;s response:
  320. if(recv(socket_desc, server_message, sizeof(server_message), 0) &lt; 0){
  321. printf(&quot;Error while receiving server&#39;s msg\n&quot;);
  322. close(socket_desc);
  323. return;
  324. }
  325. // Close the socket:
  326. close(socket_desc);
  327. gettimeofday(&amp;end, NULL);
  328. timeval_subtract (&amp;difference, &amp;end, &amp;start);
  329. double cpu_time_used = (double)difference.tv_sec + (double)difference.tv_usec / 1000000.0;
  330. printf(&quot;Client Time: %.4e s\n&quot;, cpu_time_used);
  331. }
  332. }
  333. int main(int argc, char **argv)
  334. {
  335. int n_threads = 50; // default value
  336. if (argc &gt; 1)
  337. n_threads = atoi(argv[1]);
  338. pthread_t *threads = (pthread_t*)malloc(n_threads * sizeof(pthread_t));
  339. struct timeval start, end, difference;
  340. gettimeofday(&amp;start, NULL);
  341. for(int i = 0; i &lt; n_threads; i++)
  342. {
  343. int createRet = pthread_create(&amp;threads[i], NULL, workerThreadFunc, NULL);
  344. if (createRet != 0)
  345. {
  346. printf(&quot;failed to create thread\n&quot;);
  347. }
  348. }
  349. for(int i = 0; i &lt; n_threads; i++)
  350. pthread_join(threads[i], NULL);
  351. gettimeofday(&amp;end, NULL);
  352. timeval_subtract (&amp;difference, &amp;end, &amp;start);
  353. double cpu_time_used = (double)difference.tv_sec + (double)difference.tv_usec / 1000000.0;
  354. printf(&quot;Total Client Time: %.4e s\n&quot;, cpu_time_used);
  355. free(threads);
  356. return 0;
  357. }

答案1

得分: 0

根据@user207421的说法,问题出在TCP实现上,其中包括对重试的指数退避。无论是Golang还是C,似乎都没有一种简单的方法来改变这种行为。

答案是:如果你需要高吞吐量,不要频繁打开和关闭TCP连接,而是使用连接池。

有一些工作正在研究如何去除指数退避,下面是相关链接,但对于特定情况可能有更好的解决方案。对我来说确实有更好的解决方案。

ACM SIGCOMM计算机通信评论,“从TCP中去除指数退避”,第38卷,第5期,2008年10月。

英文:

As indicated by @user207421, the issue lies in the TCP implementation, which includes an exponential backoff on retries. Neither Golang nor C appear to have an easy way to alter this behavior.

The answer is: Don't open and close connections of TCP if you high throughput--use a connection pool.

There was some work looking at removing the exponential backoff, linked below, but there is likely a better solution for specific cases. There was for me.

ACM SIGCOMM Computer Communication Review, "Removing Exponential Backoff from TCP", Volume 38, Number 5, October 2008.

huangapple
  • 本文由 发表于 2022年4月12日 19:23:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/71841771.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定