• Linux 网络编程


    套接字(Socket):

    通过网络实现跨机通信

    作用:一种文件描述符传输层的文件描述符

    整个编程中,需要着重注意htonl/htons、ntohl/ntohs、inet_addr等

    TCP的C/S实现


    循环服务器模型

    TCP服务器实现过程

    1.创建套接字:

    初始化结构体struct sockaddr_in

    2.给套接字绑定ip地址和端口号:bind函数

    #include

    3.将套接字文件描述符,从主动变为被动文件描述符(做监听准备——listen函数)

    4.accept函数:被动监听客户的连接并响应        【实现三次握手(无客户端连接,则会阻塞)】

    5.服务器调用read(recv)和write(send)函数————SIGPIPE忽略

    recv的返回值等于0的时候,证明客户端已经关闭

    6.注意事项:

            字节序转换:

    发送数据:将主机端序转为网络端序

    接收数据:将网络端序转为主机端序

    7.调用close或者shutdown关闭TCP连接

    close:

    缺点1:一次性将读写都关闭——只想关写(读),打开写(读),就实现不了

    缺点2:如果多个文件猫述符指向了同一个连接时。如果只close关闭了其中某个文件猫述符时只要其它的fd还打开着,那么连接不会被断开。直到所有的描述符都被close后才断开连接
    出现多个描述指向同一个连接的原因可能两个:
    1.通过dup方式复制出其它描述符
    2.子进程维承了这个描述符,所以子进程的描述符也指向了连接

    shutdown:

    可以全关掉

    • shutdown(套接字描述符)会关闭整个套接字的发送和接收功能,不管是否有连接建立。
    • shutdown(一个连接描述符)只会关闭与该连接相关的发送和接收功能,不会影响其他连接或套接字描述符。
    • close(套接字描述符)会完全关闭套接字描述符,释放与之相关的资源,并将描述符标记为无效。
    • close(一个连接描述符)的概念上并不存在,连接描述符通常是由套接字描述符派生出来的,因此关闭连接描述符时实际上是关闭了套接字描述符。

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include //*******//
    7. #include //*******//
    8. #include //*******//
    9. #include
    10. #include
    11. int sockfd;
    12. void my_exit(int sig)
    13. {
    14. shutdown(sockfd, SHUT_RDWR);
    15. close(sockfd);
    16. printf("shutdown socket done\n");
    17. exit(0);
    18. }
    19. void handle(int sig) // SIGPIPE的信号处理函数————以观察是否产生了SIGPIPE信号
    20. {
    21. if (sig == SIGPIPE)
    22. {
    23. printf("SIGPIPE is going\n");
    24. }
    25. }
    26. int main(int argc, char **argv)
    27. {
    28. signal(SIGINT, my_exit);
    29. signal(SIGPIPE, handle);
    30. signal(SIGPIPE, SIG_IGN);
    31. // 1.
    32. sockfd = socket(AF_INET, SOCK_STREAM, 0);
    33. if (sockfd < 0)
    34. {
    35. perror("socked is error");
    36. exit(-1);
    37. }
    38. printf("socket success\n");
    39. // setsockopt函数
    40. int i;
    41. setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
    42. // 2.
    43. struct sockaddr_in sockaddr_in1;
    44. sockaddr_in1.sin_family = AF_INET; // IPV4
    45. // “5555”可以用宏定义
    46. sockaddr_in1.sin_port = htons(4443); // 正确的做法是使用htons函数将主机字节序转换为网络字节序————htons而非htonl因为端口号是16位
    47. sockaddr_in1.sin_addr.s_addr = inet_addr("192.168.106.128"); //*****//
    48. if (bind(sockfd, (struct sockaddr *)&sockaddr_in1, sizeof(sockaddr_in1)) < 0)
    49. {
    50. perror("bind error");
    51. exit(-1);
    52. }
    53. printf("bind success\n");
    54. // 3.
    55. if (listen(sockfd, 20) < 0)
    56. {
    57. perror("listen error");
    58. exit(-1);
    59. }
    60. printf("listen success\n");
    61. // 4.
    62. struct sockaddr_in addr2;
    63. int len_addr2 = sizeof(addr2);
    64. while (1)
    65. {
    66. // 强制类型转换
    67. int sock_fd1 = accept(sockfd, (struct sockaddr *)&addr2, &len_addr2); // 每来一个客户端的连接请求,就会生成一个描述符,只要知道这个描述符,就能通过此通信
    68. if (sock_fd1 < 0)
    69. {
    70. perror("accept error");
    71. exit(-1);
    72. }
    73. printf("client ip = %s ,port = %d\n", inet_ntoa(addr2.sin_addr), ntohs(addr2.sin_port)); // 1.inet_ntoa把ip地址转换为字符————2.把网络的转换为主机的
    74. // 5.
    75. char buffer[1024] = {0};
    76. int recv_t = recv(sock_fd1, buffer, sizeof(buffer) - 1, 0);
    77. printf("recv_t : %d ", recv_t);
    78. if (recv_t < 0)
    79. {
    80. perror("recv error");
    81. exit(-1);
    82. }
    83. else if (recv_t == 0) // recv的返回值为零的时候,证明客户端关闭了!
    84. {
    85. printf("client is closed\n");
    86. }
    87. else
    88. {
    89. printf("recv :%s\n", buffer);
    90. while (1)
    91. {
    92. memset(buffer, 0, sizeof(buffer));
    93. scanf("%s", buffer);
    94. // int w_t = send(sock_fd1, buffer, strlen(buffer), 0);
    95. int w_t = send(sock_fd1, buffer, strlen(buffer), MSG_NOSIGNAL); // MSG_NOSIGNAL:表示此操作不愿被SIGPIPE信号断开;或注册信号处理函数
    96. if (w_t < 0)
    97. {
    98. perror("send data error");
    99. exit(-1);
    100. }
    101. }
    102. }
    103. shutdown(sock_fd1, SHUT_RDWR);
    104. }
    105. return 0;
    106. }
    8.setsockopt函数

    主要用在服务器端:

    【当有客服端连接到服务器的时候,此时服务器端按下ctrl + c,断开连接,此时需要等待2MSL,才能再次用原来的ip和端口号新建客户端,为了去除这种等待2MSL的】

    SO_REUSEADDR允许完全重复的捆绑:当一个IP地址和端口绑定到某个套接口上时,还允许此IP地址和端口捆绑到另一个套接口上

    setsockopt(server_sockfd,SOL_SOCKET,SO_REUSEADDR,&j,sizeof(j));-CSDN博客

    9.在socket()和bind()调用之间,使用下列代码——防止客户端关闭时,要等2MSL的时间

    TCP客服端的实现过程

    1. client.c
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. int main(int argc, char **argv)
    12. {
    13. if (argc != 3)
    14. {
    15. perror("input error");
    16. exit(-1);
    17. }
    18. int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
    19. if (socket_fd < 0)
    20. {
    21. perror("socket error");
    22. exit(-1);
    23. }
    24. struct sockaddr_in addr_in1;
    25. addr_in1.sin_family = AF_INET;
    26. addr_in1.sin_addr.s_addr = inet_addr(argv[1]);
    27. addr_in1.sin_port = htons(atoi(argv[2]));
    28. if (connect(socket_fd, (struct sockaddr *)&addr_in1, sizeof(addr_in1)) == 0)
    29. {
    30. printf("connect ok\n");
    31. }
    32. else
    33. {
    34. printf("connect error\n");
    35. }
    36. while (1)
    37. {
    38. char buffer[1024];
    39. memset(buffer, 0, sizeof(buffer));
    40. scanf("%s", buffer);
    41. write(socket_fd, buffer, strlen(buffer) + 1);
    42. memset(buffer, 0, sizeof(buffer));
    43. read(socket_fd, buffer, sizeof(buffer));
    44. printf("%s", buffer);
    45. }
    46. return 0;
    47. }

    UDP的C/S实现

    UDP协议没有建立连接特性,所以UDP协议没有自动记录对方IP和端口的特点,每次发
    送数据时,必须亲自指定对方的IP和端口,只有这样才能将数据发送给对方。

     

    UDP通信过程

    1.调用socket创建套接字文件

    2.bind绑定固定的ip和端口

    3.调用sendto和recvfrom函数,发送和接收数据

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. int main(int argc, char **argv)
    11. {
    12. // if (argc != 3)
    13. // {
    14. // printf("input error\n");
    15. // exit(-1);
    16. // }
    17. // 1.
    18. int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    19. if (sock_fd < 0)
    20. {
    21. perror("socket error");
    22. exit(-1);
    23. }
    24. // 2.
    25. struct sockaddr_in addr;
    26. addr.sin_family = AF_INET;
    27. addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    28. addr.sin_port = htons(5554);
    29. if (bind(sock_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    30. {
    31. perror("bind error");
    32. exit(-1);
    33. }
    34. // 3.
    35. struct sockaddr_in addr2;
    36. int addr2_len = sizeof(addr2);
    37. addr2.sin_family = AF_INET;
    38. addr2.sin_addr.s_addr = inet_addr(argv[1]);
    39. addr2.sin_port = htons(atoi(argv[2]));
    40. while (1)
    41. {
    42. char buffer[1024];
    43. scanf("%s", buffer);
    44. int ret = sendto(sock_fd, buffer, strlen(buffer) + 1,
    45. 0, (struct sockaddr *)&addr2, addr2_len);
    46. if (ret < 0)
    47. {
    48. perror("sendto error");
    49. exit(-1);
    50. }
    51. }
    52. return 0;
    53. }
    54. #include
    55. #include
    56. #include
    57. #include
    58. #include
    59. #include
    60. #include
    61. #include
    62. #include
    63. int main(int argc, char **argv)
    64. {
    65. // 1.
    66. int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    67. if (sock_fd < 0)
    68. {
    69. perror("socket error");
    70. exit(-1);
    71. }
    72. // 2.
    73. struct sockaddr_in addr;
    74. addr.sin_family = AF_INET;
    75. addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    76. addr.sin_port = htons(5555);
    77. if (bind(sock_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    78. {
    79. perror("bind error");
    80. exit(-1);
    81. }
    82. // 3.
    83. struct sockaddr_in addr2;
    84. memset(&addr2, 0, sizeof(addr2));
    85. int addr2_len = sizeof(addr2);
    86. char buffer[1024] = {0};
    87. while (1)
    88. {
    89. int ret = recvfrom(sock_fd, buffer, sizeof(buffer),
    90. 0, (struct sockaddr *)&addr2, &addr2_len);
    91. if (ret < 0)
    92. {
    93. perror("recvfrom error");
    94. exit(-1);
    95. }
    96. printf("from ip = %s ,from port = %d \n", inet_ntoa(addr2.sin_addr), ntohs(addr2.sin_port));
    97. printf("recv message = %s \n", buffer);
    98. }
    99. return 0;
    100. }

    运行结果:

    广播


    一个人发,然后其它所有人都接收,这就是广播。


    广播只能在局域网内部有效,广播数据是无法越过路由器的,也就是说路由器就是广播数据的边界。 广播只能在局域网内部有效,广播数据是无法越过路由器的,也就是说路由器就是广播数据的边界。


    实现方法:

    1.广播的发数据,不需要绑定自己的IP地址

    2.ip地址写成广播地址;例如:192.168.1.255

    3.接收端的ip地址,不能设置为固定ip,要指定为htons(INADDR_ANY)

    4.接收方需要setsockopt函数,设置套接字文件可以重复绑定

       addr1.sin_addr.s_addr = htons(INADDR_ANY);//为什么是htons和htonl!!!!
    1. 广播发送:无需bind
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. int main(int argc, char **argv)
    11. {
    12. int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    13. if (sock_fd < 0)
    14. {
    15. perror("socket error");
    16. exit(-1);
    17. }
    18. //*****************************************************************************//
    19. int j = 1;
    20. setsockopt(sock_fd, SOL_SOCKET, SO_BROADCAST, (void *)&j, sizeof(j));
    21. //*****************************************************************************//
    22. struct sockaddr_in addr1;
    23. addr1.sin_family = AF_INET;
    24. addr1.sin_addr.s_addr = inet_addr("127.0.0.255");
    25. addr1.sin_port = htons(5555);
    26. while (1)
    27. {
    28. char buffer[1024] = {0};
    29. scanf("%s", buffer);
    30. sendto(sock_fd, buffer, sizeof(buffer),
    31. 0, (struct sockaddr *)&addr1, sizeof(addr1));
    32. }
    33. return 0;
    34. }
    35. 接受广播:
    36. #include
    37. #include
    38. #include
    39. #include
    40. #include
    41. #include
    42. #include
    43. #include
    44. int main(int argc, char **argv)
    45. {
    46. int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    47. if (sock_fd < 0)
    48. {
    49. perror("socket error");
    50. exit(-1);
    51. }
    52. int j = 1;
    53. setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &j, sizeof(j));
    54. struct sockaddr_in addr1;
    55. addr1.sin_family = AF_INET;
    56. //*****************************************************************************//
    57. addr1.sin_addr.s_addr = htons(INADDR_ANY);//为什么是htons和htonl!!!!
    58. //*****************************************************************************//
    59. addr1.sin_port = htons(5555);
    60. int ret = bind(sock_fd, (struct sockaddr *)&addr1, sizeof(addr1));
    61. if (ret < 0)
    62. {
    63. perror("bind error");
    64. exit(-1);
    65. }
    66. struct sockaddr_in addr2;
    67. int len = sizeof(addr2);
    68. memset(&addr2, 0, sizeof(addr2));
    69. char buffer[1024];
    70. while (1)
    71. {
    72. memset(buffer, 0, sizeof(buffer));
    73. recvfrom(sock_fd, buffer, sizeof(buffer),
    74. 0, (struct sockaddr *)&addr2, &len);
    75. printf("recv %s\n", buffer);
    76. }
    77. return 0;
    78. }

    组播

    把一些ip设置为一个组,给这些组,发消息(后续在QT里涉及到

  • 相关阅读:
    nacos
    ROS系列:第六章 机器人建模
    ORACLE Linux(OEL) - Primavera P6EPPM 安装及分享
    机器学习 | 模型评估和选择 各种评估指标总结——错误率精度-查准率查全率-真正例率假正例率 PR曲线ROC曲线
    从瀑布模式到水母模式:ChatGPT如何赋能软件研发全流程
    JSP校园导游查询系统myeclipse开发sql数据库bs框架java编程web网页结构
    【测试】使用卷积神经网络(CNN)中的 卷积核 对 图像进行特征提取
    Linux内核调试工具——devmem
    以业务为核心,泛微协助生产制造企业推动销售到生产一体化管理
    在python里如何实现switch函数的功能
  • 原文地址:https://blog.csdn.net/qq_58170320/article/details/133178842