• UDP网络套接字编程


    先来说说数据在网络上的传输过程吧,我们知道系统其实终究是根据冯诺依曼来构成的,而网络数据是怎么发的呢?

    其实很简单,网络有五层。如下:

    如上图,我们知道的是,每层对应的操作系统中的那些地方,有些可能说是网络有七层,其实和这个五层一样的。下面我们说说数据是怎么运输的在网络中,如下图:

    如上图,其实数据在网络中是自顶向下,然后在通过以太网的网线传输到另一个主机上,在自底向上,就可以收到了,前提是在同一个局域网中,如果不在一个局域网,肯定会经过路由器的,这里就不详细说了,主要说说我们的udp协议。

    我们知道了网络的五层,那么每层其实都与对应的协议等。udp协议对应在传输层(运输层)。那么我们来看看如何用udp协议实现套接字编程吧。先来看看代码:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. using namespace std;
    13. #define NUM 1024
    14. int main(int argc, char *argv[])
    15. {
    16. unordered_map<uint16_t,sockaddr_in> usdate;
    17. // 创建套接字
    18. int sock = socket(AF_INET, SOCK_DGRAM, 0);
    19. if (sock < 0)
    20. {
    21. cout << "sock enrro" << endl;
    22. exit(1);
    23. }
    24. // 绑定
    25. sockaddr_in se;
    26. memset(&se, 0, sizeof(se));
    27. se.sin_family = AF_INET;
    28. se.sin_port = htons(atoi(argv[2]));
    29. se.sin_addr.s_addr = inet_addr(argv[1]);
    30. int ret = bind(sock, (sockaddr *)&se, sizeof(se));
    31. if (ret < 0)
    32. {
    33. cout << "bind enrro" << endl;
    34. exit(2);
    35. }
    36. // 服务端 1.0版本
    37. // 可以开始读取
    38. // sockaddr_in reader;
    39. // socklen_t size = sizeof(reader);
    40. // char buffer[NUM];
    41. // while (true)
    42. // {
    43. // ssize_t r = recvfrom(sock, buffer, sizeof(buffer) - 1, 0, (sockaddr *)&reader, &size);
    44. // buffer[r] = '\0';
    45. // if (r > 0)
    46. // {
    47. // cout << buffer << endl;
    48. // }
    49. // else
    50. // break;
    51. // sendto(sock, buffer, sizeof(buffer), 0, (sockaddr *)&reader, size);
    52. // memset(buffer, 0, sizeof(buffer));
    53. // }
    54. // close(sock);
    55. // 服务器 2.0版本 实现群聊
    56. char buffer[NUM];
    57. memset(buffer, 0, NUM);
    58. sockaddr_in reader;
    59. socklen_t size = sizeof(reader);
    60. while (true)
    61. {
    62. ssize_t s = recvfrom(sock, buffer, sizeof(buffer) - 1, 0, (sockaddr *)&reader, &size);
    63. usdate.insert(make_pair(reader.sin_port, reader));
    64. cout << "插入成功" << endl;
    65. cout << ntohs(reader.sin_port) <<" "<< inet_ntoa(reader.sin_addr)<< '#' << " "
    66. << ":" << buffer << endl;
    67. if (s > 0)
    68. {
    69. for (const auto &e : usdate)
    70. sendto(sock, buffer, sizeof(buffer), 0, (sockaddr *)&(e.second), sizeof(e.second));
    71. memset(buffer, 0, NUM);
    72. }
    73. else
    74. break;
    75. }
    76. close(sock);
    77. return 0;
    78. }
    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. using namespace std;
    11. #define NUM 1024
    12. void *reads(void *args)
    13. {
    14. // 线程分离
    15. pthread_detach(pthread_self());
    16. char buffer[NUM];
    17. // 清空buffer
    18. memset(buffer, 0, NUM);
    19. int *sc = static_cast<int *>(args);
    20. sockaddr_in reader;
    21. socklen_t len = sizeof(reader);
    22. while (true)
    23. {
    24. ssize_t s = recvfrom(*sc, buffer, sizeof(buffer) - 1, 0, (sockaddr *)&reader, &len);
    25. if (s > 0)
    26. {
    27. cout << buffer << endl;
    28. memset(buffer, 0, NUM);
    29. }
    30. else
    31. break;
    32. }
    33. return nullptr;
    34. }
    35. int main(int argc, char *argv[])
    36. {
    37. int sock = socket(AF_INET, SOCK_DGRAM, 0);
    38. if (sock < 0)
    39. {
    40. cout << "sock enrro" << endl;
    41. exit(1);
    42. }
    43. char buffer[NUM];
    44. memset(buffer, 0, NUM);
    45. pthread_t tid;
    46. pthread_create(&tid, nullptr, reads, &sock);
    47. sockaddr_in clinet;
    48. memset(&clinet, 0, sizeof(clinet));
    49. clinet.sin_family = AF_INET;
    50. clinet.sin_addr.s_addr = inet_addr(argv[1]);
    51. clinet.sin_port = htons(atoi(argv[2]));
    52. while (true)
    53. {
    54. cout << "你要输入" << endl;
    55. cin >> buffer;
    56. ssize_t s = sendto(sock, buffer, sizeof(buffer), 0, (sockaddr *)&(clinet), sizeof(clinet));
    57. if (s > 0)
    58. memset(buffer, 0, NUM);
    59. else
    60. break;
    61. }
    62. close(sock);
    63. return 0;
    64. }

    这是我的服务端和客户端的代码,分了单人聊天和多人聊天。下面就讲解一下吧。

    什么是端口号:主机中能表示一个唯一的进程的编号

    什么是ip地址:其实ip地址是网络层对应的主机地址。

    什么是mac地址:这个网卡的地址,一般出厂的时候就会确定,且不能修改

    什么是套接字:IP+端口号

    我们首先可以根据套接字找到网络中唯一的一个主机上进程,此处不考虑ip地址重复问题。假设ip地址不重复。所以我们要进行udp套接字编程,首先要创建套接字。也就是我上图代码中的socket这个函数,然后绑定地址和端口,这个就可以用我们main函数中的参数了。创建完套接字和绑定完成以后,我们就可以通信了,我们用的这些函数其实就是系统调用,是udp的一些函数暴露给用户层的系统调用。

    大概知道了怎么用udp编程,那么此时有些伙伴可能有些疑问了。我们在系统层面上pid也可以表示进程的唯一性,为啥不用PID表示端口号呢?其实也很简单,原因就是网络层是这么表示的,就好比你的名字一样,在外面大家都叫你名字,回到家家里面的人都叫你小名,一样的道理。

    然后就是有些人可能没有理解数据是怎么从下往上,从上往下的。其实很好理解,因我们在写代码的时候,我们是属于用户层的。而我们传输层的系统调用接口,那么说明他这个数据肯定是要进内核的,而我们的另一个主机接收到信息后,我们用的打印函数,又是用户层的,所以就类似于一个轮回。所以这样就可以很好的理解了。

    然后就是一些编码的注意事项了,在客户端我用了多线程来实现了读数据和写数据的解耦,这个其实在单人聊天中没什么影响,再多人聊天中就不可以了。假设单人聊天就要先发在读。因为再多人聊天中,我们预期是一个人发,多个人收,如果还是用这个代码的话,那么 如果开启服务端的时候,多个人你同时建立连接,那么此时如果有其中一个人发了信息,并且假设其他人都没有发信息,那么此时就会导致其他人卡在写的界面,因为他们没有写,所以没办法读信息。所以此时我们这里必须要实现成多线程,一个写,一个读,读写解耦。两个互不影响。建议使用线程,不用进程,且不说多进程可不可以实现这个功能,就算是实现了,那么此时它的消耗是很大的。(多进程也可以实现这个功能),如果用多进程,那么此时我们要考虑的是,如何回收这个子进程,肯定不可以阻塞等待,如果用waitpid且不是阻塞等待的话,那么此时我们要写成循环,要不断去检测子进程是否完成任务。或是可以在子进程中在frok,让孙子进程执行任务,子进程退出,此时就会形成孤儿进程,会被1号进程领养,所以不用担心资源泄露,但是这样很明显很麻烦,还不如用线程,且消耗还比进程小。

    以上就是这篇文章的内容,希望大家支持,如果对你有用,希望支持一下!!!!

  • 相关阅读:
    论文总结5 基于Kmeans聚类的XGBoost集成算法研究
    [附源码]java毕业设计保险业务管理系统
    ALEVEL经济学:通货膨胀的原因是什么?
    定制开发一款家政小程序,应知应会
    基于时延估计的扰动卡尔曼滤波器外力估计
    每条你收藏的资讯背后,都离不开TA
    第一章:网络协议的奥秘
    k8s 入门到实战--部署应用到 k8s
    为什么数据分析能力总是不见长?只会用基础图表当然不行!
    Spring Boot 基础使用
  • 原文地址:https://blog.csdn.net/huichaochao/article/details/134520327