TCP/IP协议的工作原理

image.png

Socket

socket的主要类型有两种,数据流套接字(Stream Sockets)和数据报套接字(Datagram Sockets)两种。
数据流套接字 TCP 可靠传高
数据报套接字 UDP 可靠性低,传输经济

image.png

TCP

服务器端

端口说明及端口复用

端口号加协议标识一个“唯一端口”,TCP/IP协议的3000端口和HTTP协议的3000端口,并不会冲突,但是如果同时打开相同协议相同端口的“端口监听”时,就会端口冲突了

1.一个应用程序只能绑定一个端口(一个套接字只能绑定一个端口)
2.UDP端口和TCP端口 虽然端口号相同,但是是不同的端口
3.病毒端口复用,是先断开原来的端口,然后自己连接上去,然后是自己的数据就自己处理不是自己的就分发给其他程序
4.可以多个线程同时receive(接收)你绑定的套接字,共享你绑定的套接字
5.设置端口复用
SO_REUSEADDR可以用在以下4种情况下
5.1 当有一个socket1处于TIME_WAIT状态时,而你启动的程序socket2需要用到该地址和端口,你的程序可以用到此项。
5.2 SO_REUSEADDR允许同一个port上启动同一服务的多个实例(多个进程)。但每个实例绑定的ip地址不能相同。有多块网卡或用IP Alias技术的机器可以测试这种情况。
5.3 SO_REUSEADDR允许单个进程绑定相同的端口到多个socket上,但每个socket绑定的ip地址不同,这和5.2很相似,区别请看UNPv1
5.4 SO_REUSEADDR允许完全相同的地址和端口绑定,但这只用于UDP的多播,不能用于TCP。
注意:设置端口复用函数要在绑定之前调用,而且只要绑定到同一个端口的所有套接字都得设置复用。

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <errno.h>
  5. #include <string.h>
  6. #include <sys/types.h>
  7. #include <netinet/in.h>
  8. #include <sys/socket.h>
  9. #include <arpa/inet.h>
  10. /**服务器监听端口号*/
  11. #define SERV_PORT 3000
  12. /**请求队列长度*/
  13. #define LENGTH 10
  14. /**缓冲区长度*/
  15. #define SIZE 128
  16. int main(int argc, char **argv)
  17. {
  18. int res;
  19. int sockfd;
  20. int clientfd;
  21. struct sockaddr_in hostAddr;
  22. struct sockaddr_in clientAddr;
  23. unsigned int addrLen;
  24. char buf[SIZE];
  25. int cnt;
  26. sockfd = socket(AF_INET, SOCK_STREAM, 0);
  27. if (sockfd == -1) {
  28. perror("套接字创建失败");
  29. exit(1);
  30. }
  31. /**套接字与IP端口绑定*/
  32. hostAddr.sin_family = AF_INET; /**TCP/IP协议*/
  33. hostAddr.sin_port = SERV_PORT; // htons(SERV_PORT); /**让系统使用一个未被占用的端口号*/
  34. hostAddr.sin_addr.s_addr = INADDR_ANY; /**本机IP地址*/
  35. bzero(&(hostAddr.sin_zero), 8); /**清零*/
  36. res = bind(sockfd, (struct sockaddr *)&hostAddr, sizeof(struct sockaddr));
  37. if (res == -1) {
  38. perror("套接字绑定失败");
  39. exit(1);
  40. }
  41. /**将套接字设为监听模式,以等待连接请求*/
  42. res = listen(sockfd, LENGTH);
  43. if (res == -1) {
  44. perror("设置监听模式失败");
  45. exit(1);
  46. }
  47. printf("服务器IP:%s\n", inet_ntoa(hostAddr.sin_addr));
  48. printf("服务器端口号:%d\n", hostAddr.sin_port);
  49. printf("等待客户端请求连接。\n");
  50. // 请求到来后,接受连接请求,并接收数据
  51. while(1) {
  52. addrLen = sizeof(struct sockaddr_in);
  53. clientfd = accept(sockfd, (struct sockaddr *) &clientAddr, &addrLen);
  54. if (clientfd == -1) {
  55. perror("接受连接请求错误");
  56. continue;
  57. }
  58. printf("客户端IP:%s\n", inet_ntoa(clientAddr.sin_addr));
  59. cnt = recv(clientfd, buf, SIZE, 0);
  60. if (cnt == -1) {
  61. perror("数据接收失败");
  62. exit(1);
  63. }
  64. printf("收到数据:%s\n", buf);
  65. close(clientfd);
  66. }
  67. return 0;
  68. }

服务器端并发—多开子线程处理

注意:上面的代码以及下面的代码,使用的都是堵塞方式的套接字,使用堵塞方式的套接字,进程的效率比较低,尤其是在读写操作时,我们可以使用fcntl()或ioctl()函数来改变套接字的属性,将其设置为无堵塞方式。

在无堵塞方式下,进程调用recv,read,recvfrom函数从套接字缓冲区中读取内容时,如果缓冲区没有数据,函数立即返回,并设置相应错误码。同样写入时,send,write,sendto,如果缓冲区没有空闲空间,函数立即返回,并设置相应错误码。 将套接字设置为无阻塞模式,cpu可以通过轮询的方式同时处理多个套接字,但是这种方式会浪费大量CPU时间,从而降低系统的性能,Linux 提供了select()函数可以,有效解决这个问题。该函数用来将进程挂起,使系统内核去监听多个文件描述的状态变化,任何一个文件描述符上有时间发送时,进程都会被唤起,进而节省了CPU时间。

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <errno.h>
  5. #include <string.h>
  6. #include <sys/types.h>
  7. #include <netinet/in.h>
  8. #include <sys/socket.h>
  9. #include <arpa/inet.h>
  10. /**服务器监听端口号*/
  11. #define SERV_PORT 3000
  12. /**请求队列长度*/
  13. #define LENGTH 10
  14. /**缓冲区长度*/
  15. #define SIZE 128
  16. int main(int argc, char **argv)
  17. {
  18. int res;
  19. int sockfd;
  20. int clientfd;
  21. int pth;
  22. struct sockaddr_in hostAddr;
  23. struct sockaddr_in clientAddr;
  24. unsigned int addrLen;
  25. char buf[SIZE];
  26. int cnt;
  27. sockfd = socket(AF_INET, SOCK_STREAM, 0);
  28. if (sockfd == -1)
  29. {
  30. perror("套接字创建失败");
  31. exit(1);
  32. }
  33. /**套接字与IP端口绑定*/
  34. hostAddr.sin_family = AF_INET; /**TCP/IP协议*/
  35. hostAddr.sin_port = SERV_PORT; // htons(SERV_PORT); /**让系统使用一个未被占用的端口号*/
  36. hostAddr.sin_addr.s_addr = INADDR_ANY; /**本机IP地址*/
  37. bzero(&(hostAddr.sin_zero), 8); /**清零*/
  38. res = bind(sockfd, (struct sockaddr *)&hostAddr, sizeof(struct sockaddr));
  39. if (res == -1)
  40. {
  41. perror("套接字绑定失败");
  42. exit(1);
  43. }
  44. /**将套接字设为监听模式,以等待连接请求*/
  45. res = listen(sockfd, LENGTH);
  46. if (res == -1)
  47. {
  48. perror("设置监听模式失败");
  49. exit(1);
  50. }
  51. printf("服务器IP:%s\n", inet_ntoa(hostAddr.sin_addr));
  52. printf("服务器端口号:%d\n", hostAddr.sin_port);
  53. printf("等待客户端请求连接。\n");
  54. // 请求到来后,接受连接请求,并接收数据
  55. while (1)
  56. {
  57. addrLen = sizeof(struct sockaddr_in);
  58. clientfd = accept(sockfd, (struct sockaddr *)&clientAddr, &addrLen);
  59. if (clientfd == -1)
  60. {
  61. perror("接受连接请求错误");
  62. continue;
  63. }
  64. pth = fork();
  65. if (pth == -1) {
  66. perror("进程创建失败");
  67. exit(1);
  68. }
  69. if (pth == 0) {
  70. close(sockfd); // 关闭父进程套接字
  71. printf("客户端IP:%s\n", inet_ntoa(clientAddr.sin_addr));
  72. cnt = recv(clientfd, buf, SIZE, 0);
  73. if (cnt == -1)
  74. {
  75. perror("数据接收失败");
  76. exit(1);
  77. }
  78. printf("收到数据:%s\n", buf);
  79. sleep(5);
  80. close(clientfd);
  81. exit(0);
  82. }
  83. /**父亲进程关闭子进程套接字*/
  84. close(clientfd);
  85. }
  86. return 0;
  87. }

客户端

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <errno.h>
  5. #include <string.h>
  6. #include <sys/types.h>
  7. #include <netinet/in.h>
  8. #include <sys/socket.h>
  9. /**服务器监听端口号*/
  10. #define SERV_PORT 3000
  11. /**请求队列长度*/
  12. #define LENGTH 10
  13. /**缓冲区长度*/
  14. #define SIZE 128
  15. int main(int argc, char **argv)
  16. {
  17. int res;
  18. int sockfd;
  19. struct sockaddr_in hostAddr;
  20. char buf[SIZE];
  21. int cnt;
  22. if (argc != 2) {
  23. perror("参数错误");
  24. exit(1);
  25. }
  26. sockfd = socket(AF_INET, SOCK_STREAM, 0);
  27. if (sockfd == -1) {
  28. perror("套接字创建失败");
  29. exit(1);
  30. }
  31. /**套接字与IP端口绑定*/
  32. hostAddr.sin_family = AF_INET; /**TCP/IP协议*/
  33. hostAddr.sin_port = SERV_PORT; // htons(SERV_PORT); /**让系统使用一个未被占用的端口号*/
  34. hostAddr.sin_addr.s_addr = INADDR_ANY; /**本机IP地址*/
  35. bzero(&(hostAddr.sin_zero), 8); /**清零*/
  36. res = connect(sockfd, (struct sockaddr *)&hostAddr, sizeof(struct sockaddr));
  37. if (res == -1)
  38. {
  39. perror("连接失败");
  40. exit(1);
  41. }
  42. strcpy(buf, argv[1]);
  43. cnt = send(sockfd, buf, SIZE, 0);
  44. if (cnt == -1) {
  45. perror("发送数据失败");
  46. exit(1);
  47. }
  48. printf("发送数据:%s\n", buf);
  49. close(sockfd);
  50. return 0;
  51. }

UDP

服务器端

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <errno.h>
  5. #include <string.h>
  6. #include <sys/types.h>
  7. #include <netinet/in.h>
  8. #include <sys/socket.h>
  9. #include <arpa/inet.h>
  10. /**服务器监听端口号*/
  11. #define SERV_PORT 3000
  12. /**请求队列长度*/
  13. #define LENGTH 10
  14. /**缓冲区长度*/
  15. #define SIZE 128
  16. int main(int argc, char **argv)
  17. {
  18. int res;
  19. int sockfd;
  20. int clientfd;
  21. struct sockaddr_in hostAddr;
  22. struct sockaddr_in clientAddr;
  23. unsigned int addrLen;
  24. char buf[SIZE];
  25. int cnt;
  26. sockfd = socket(AF_INET, SOCK_DGRAM, 0);
  27. if (sockfd == -1) {
  28. perror("套接字创建失败");
  29. exit(1);
  30. }
  31. /**套接字与IP端口绑定*/
  32. hostAddr.sin_family = AF_INET; /**TCP/IP协议*/
  33. hostAddr.sin_port = SERV_PORT; // htons(SERV_PORT); /**让系统使用一个未被占用的端口号*/
  34. hostAddr.sin_addr.s_addr = INADDR_ANY; /**本机IP地址*/
  35. bzero(&(hostAddr.sin_zero), 8); /**清零*/
  36. res = bind(sockfd, (struct sockaddr *)&hostAddr, sizeof(struct sockaddr));
  37. if (res == -1) {
  38. perror("套接字绑定失败");
  39. exit(1);
  40. }
  41. printf("服务器IP:%s\n", inet_ntoa(hostAddr.sin_addr));
  42. printf("服务器端口号:%d\n", hostAddr.sin_port);
  43. printf("等待客户端请求连接。\n");
  44. // 请求到来后,接受连接请求,并接收数据
  45. while(1) {
  46. addrLen = sizeof(struct sockaddr_in);
  47. cnt = recvfrom(sockfd, buf, SIZE, 0,
  48. (struct sockaddr *)&clientAddr, &addrLen
  49. );
  50. if (cnt == -1) {
  51. perror("数据接收失败");
  52. exit(1);
  53. }
  54. printf("客户端IP:%s\n", inet_ntoa(clientAddr.sin_addr));
  55. printf("收到数据:%s\n", buf);
  56. }
  57. close(clientfd);
  58. return 0;
  59. }

客户端

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <errno.h>
  5. #include <string.h>
  6. #include <sys/types.h>
  7. #include <netinet/in.h>
  8. #include <sys/socket.h>
  9. #include <arpa/inet.h>
  10. /**服务器监听端口号*/
  11. #define SERV_PORT 3000
  12. /**请求队列长度*/
  13. #define LENGTH 10
  14. /**缓冲区长度*/
  15. #define SIZE 128
  16. int main(int argc, char **argv)
  17. {
  18. int res;
  19. int sockfd;
  20. struct sockaddr_in hostAddr;
  21. unsigned int addrLen;
  22. char buf[SIZE];
  23. int cnt;
  24. if (argc != 3) {
  25. perror("参数错误");
  26. exit(1);
  27. }
  28. sockfd = socket(AF_INET, SOCK_DGRAM, 0);
  29. if (sockfd == -1) {
  30. perror("套接字创建失败");
  31. exit(1);
  32. }
  33. /**套接字与IP端口绑定*/
  34. hostAddr.sin_family = AF_INET; /**TCP/IP协议*/
  35. hostAddr.sin_port = SERV_PORT; // htons(SERV_PORT); /**让系统使用一个未被占用的端口号*/
  36. hostAddr.sin_addr.s_addr = inet_addr(argv[1]); // INADDR_ANY; /**本机IP地址*/
  37. bzero(&(hostAddr.sin_zero), 8); /**清零*/
  38. addrLen = sizeof(struct sockaddr_in);
  39. strcpy(buf, argv[2]);
  40. cnt = sendto(sockfd, buf, SIZE, 0,
  41. (struct sockaddr *)&hostAddr, addrLen
  42. );
  43. if (cnt == -1) {
  44. perror("发送数据失败");
  45. exit(1);
  46. }
  47. printf("发送数据:%s\n", buf);
  48. close(sockfd);
  49. return 0;
  50. }

域名系统

通过域名获取主机信息
通过地址获取主机信息
获取本地主机信息

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <sys/types.h>
  5. #include <netinet/in.h>
  6. #include <sys/socket.h>
  7. #include <arpa/inet.h>
  8. #include <netdb.h>
  9. /**uname函数*/
  10. #include <sys/utsname.h>
  11. int main(int argc, char **argv)
  12. {
  13. struct sockaddr_in addr;
  14. struct hostent *host;
  15. struct hostent *webHost;
  16. if (argc != 2) {
  17. printf("参数错误");
  18. exit(1);
  19. }
  20. char domainName[] = "www.baidu.com";
  21. webHost = gethostbyname((char *) &domainName);
  22. printf("webHost.h_name=%s\n", webHost->h_name);
  23. printf("webHost.h_aliases=%s\n", *webHost->h_aliases);
  24. /**将ip地址由字符串转换为二进制形式*/
  25. inet_aton(argv[1], &addr.sin_addr);
  26. /**查找IP对应的主机信息*/
  27. host = gethostbyaddr((char *)&addr.sin_addr, 4, AF_INET);
  28. printf("host=%s\n", host->h_name);
  29. char name[128];
  30. /**获取本地主机信息,gethostname函数调用成功返回0,否则返回-1*/
  31. gethostname((char *)&name, sizeof(name));
  32. printf("name=%s\n", name);
  33. struct utsname utsName;
  34. uname((struct utsname *)&utsName);
  35. printf("sysname=%s\n", utsName.sysname);
  36. printf("nodename=%s\n", utsName.nodename);
  37. printf("release=%s\n", utsName.release);
  38. printf("version=%s\n", utsName.version);
  39. printf("machine=%s\n", utsName.machine);
  40. return 0;
  41. }