• [Linux系统编程]_网络编程(五)


    嵌入式之路,贵在日常点滴

                                                                    ---阿杰在线送代码

    目录

    一、解惑

    为什么需要学习网络编程? 

    如何准确地定位网络上一台或多台主机以及定位主机上的特定的应用? 

    端口号作用 

    Socket(套接字)

    Socket(套接字)主要用到的协议

    TCP与UDP对比

    字节序

    二、常用API  

    三、Sockt服务器和客户端的开发步骤 

    四、代码实现

    socket服务端代码实现

    socket客户端代码实现 

    实现服务端和客户端聊天 

    多方消息收发

    补充常识

    关于include内容搜索 


    一、解惑

    为什么需要学习网络编程? 

    前面对于进程间通信我们讲了:管道、消息队列、共享内存、信号、信号量

    这5种通信方式都是依靠Liunx内核。这也造成了他们的一个缺点就是,这些通信方式只能在单机运行

    那么如何实现多机通信呢?这就需要引入网络编程。 

    那么网络有哪些呢? 

    就从嵌入式出发,有Linux与Android、IOS、各种单片机,或者是Android对Android等等。 

    如何准确地定位网络上一台或多台主机以及定位主机上的特定的应用? 

    答:通过通信双方地址。地址由IP地址和端口号组成。
    IP地址:负责定位主机。
    端口号:负责定位主机上的特定应用。 

    端口号作用 

    一台拥有IP地址的主机可以提供许多服务,比如Web服务、 FTP服务、SMTP服务等。

    这些服务完全可以通过1个IP地址来实现。那么,主机是怎样区分不同的网络服务呢?显然不能只靠IP地址,因为IP地址与网络服务的关系是一对多的关系。

    实际上是通过"IP地址+端口号"来区分不同的服务的。

    端口提供了一种访问通道,
    服务器一般都是通过知名端口号来识别的。例如,对于每个TCP/IP实现来说,FTP服务器的TCP端口号都是21.每个Telnet服务器的TCP端口号都是23,每个TFTP(简单文件 传送协议)服务器的UDP端口号都是69

    Socket(套接字)

    Socket本身有“插座”的意思,在Linux环境下,用于表示进程间网络通信的特殊文件类型。
    本质为内核借助缓冲区形成的伪文件。既然是文件,那么理所当然的,我们可以使用文件描述符引用套接字。与管道类似的,Linux系统将其封装成文件的目的是为了统一接口,使得读写套接字和读写文件的操作一致。区别是管道主要应用于本地进程间通信,而套接字多应用于网络进程间数据的传递。

    在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一标识网络通讯中的一个进程。“IP地址+端口号”就对应一个socket。欲建立连接的两个进程各自有一个socket来标识,那么这两个socket组成的socket pair就唯一标识一个连接。因此可以用Socket来描述网络连接的一对一关系。

    Socket(套接字)主要用到的协议

    1、TCP(面向连接)双方必须连接成功才能发数据,类似打电话,主要用于对传输数据精确的情况,如传输指令。
    2、UDP(面向报文)双方无须连接成功就能发数据,类似发短信,主要用于传输大数据量的情况,如视频。

    TCP与UDP对比

    1、TCP面向连接(如打电话要先拨号建立连接) ;UDP是无连接的,即发送数据之前不需要建立连接
    2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付
    3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
    4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多, 多对一和多对多的交互通信
    5、TCP首部开销20字节;UDP的首部开销小,只有8个字节
    6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道

    字节序

    字节序是指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序。

    大端字节序:将高序字节存储在起始地址,如:TCP/IP协议字节序

    小端字节序:将低序字节存储在起始地址,如:X86系列CPU字节序

    TCP/IP协议规定,网络数据流应采用大端字节序

    为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换:

    1. #include
    2. uint32_t htonl(uint32_t hostlong);
    3. uint16_t htons(uint16_t hostshort);
    4. uint32_t ntohl(uint32_t netlong);
    5. uint16_t ntohs(uint16_t netshort);
    6. //h表示host,n表示network,l表示32位长整数,s表示16位短整数。
    7. //如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回,如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。

    、常用API  

    1、连接协议:(约法三章,确定协议)
    原型:int socket(int domain, int type, int protocol);
    (1)domain:通常是AF_INET,指IPV4因特网域
    (2)type:指定socket类型,SOCK_STREAM它使用TCP协议,SOCK_DGRAM使用UDP协议
    (3)protocol:0默认选择type对应的协议
    创建成功返回涛姐号,创建失败返回-1.

    2、连接地址
    bind()函数,约定IP地址和端口号
    原型:int bind(int sockfd, const struct sockaddr *maddr,socklen_t addrlen);
    (1)sockfd:网络标识符
    (2)struct sockaddr_in{
         sa_family_t sin_family;  //指定AF_***,表示使用什么协议族的ip格式
         in_port_in      sin_port;   //设置端口号
         struct in_addr  sin_addr;   //设置ip
         unsigned char sin_zero[8];
    };
    (3)结构体大小

     地址转换API

    int inet_aton(const char* straddr,struct in_addr *addrp);

    把字符串形式的“192.168.1.123”转为网络能识别的格式

    char* inet_ntoa(struct in_addr inaddr);

    把网络格式的ip地址转为字符串形式

    3、监听

    原型:int listen(int sockfd,int backlog)

    (1)sockfd:网络连接号

    (2)backlog:指定请求队列中同时最大请求数 

    4、连接

    原型:int accept(int sockfd,struct sockaddr *addr,socklen_t addrlen)

    (1)网络连接号

    (2)客户端地址,NULL

    (3)客户端地址长度,NULL(指针) 返回值是新建立的通道。 

    5、客户端用connect连接主机

    connect(int sockfd,const struct sockaddr *addr,socklen_t addrlen)

    (1)目的服务器端口号

    (2)服务器的IP地址和端口号的结构体

    (3)地址长度 

    6、数据收发

    、Sockt服务器和客户端的开发步骤 

    socket编程步骤:
    (1)创建套接字//类似内存空间
    (2)为套接字添加信息(IP地址和端口号)
    (3)监听网络连接
    (4)监听到有客户端接入,接受一个连接
    (5)数据交互
    (6)关闭套接字,断开连接 

    四、代码实现

    socket服务端代码实现

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. int main()
    9. {
    10. int s_fd;
    11. char readbuf[128];
    12. char* msg = "I Get Your message";
    13. struct sockaddr_in s_addr;
    14. struct sockaddr_in c_addr;
    15. memset(&s_addr,0,sizeof(struct sockaddr_in));
    16. memset(&c_addr,0,sizeof(struct sockaddr_in));
    17. //1.socket
    18. s_fd = socket(AF_INET,SOCK_STREAM,0);
    19. if(s_fd == -1)
    20. {
    21. perror("sccket");
    22. exit(-1);
    23. }
    24. //2.bind
    25. s_addr.sin_family = AF_INET;
    26. s_addr.sin_port = htons(8989);
    27. inet_aton("192.168.22.123",&s_addr.sin_addr);
    28. bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
    29. //3.listen
    30. listen(s_fd,10);
    31. //4.accept
    32. int clen;
    33. clen = sizeof(struct sockaddr_in);
    34. int c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
    35. //如果没有连接就会堵塞在这里
    36. if(c_fd == -1)
    37. {
    38. perror("accept");
    39. }
    40. printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));
    41. //5.read
    42. int n_read = read(c_fd,readbuf,128);
    43. if(n_read == -1){
    44. perror("read");
    45. }else{
    46. printf("read msg:%d %s\n",n_read,readbuf);
    47. }
    48. //6.write
    49. write(c_fd,msg,strlen(msg));
    50. return 0;
    51. }

    使用telnet编译调试服务端代码是否成功编写

    运行结果

    创建服务器,可同时连接10个,连接完成后要从这个地址读数据,无数据则堵塞,需要从命令行写数据,然后才读,读完以后,给个消息。 

    socket客户端代码实现 

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. int main()
    9. {
    10. int c_fd;
    11. char readbuf[128];
    12. char* msg = "msg from client";
    13. struct sockaddr_in c_addr;
    14. memset(&c_addr,0,sizeof(struct sockaddr_in));
    15. //1.socket
    16. c_fd = socket(AF_INET,SOCK_STREAM,0);
    17. if(c_fd == -1)
    18. {
    19. perror("sccket");
    20. exit(-1);
    21. }
    22. //2.connect
    23. c_addr.sin_family = AF_INET;
    24. c_addr.sin_port = htons(8989);
    25. inet_aton("192.168.22.123",&c_addr.sin_addr);
    26. //连接时会一直阻塞在这里,知道连接成功
    27. if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr)) == -1)
    28. {
    29. perror("connect");
    30. exit(-1);
    31. }
    32. //3.send
    33. write(c_fd,msg,strlen(msg));
    34. //4.read
    35. int n_read = read(c_fd,readbuf,128);
    36. //4.read
    37. int n_read = read(c_fd,readbuf,128);
    38. if(n_read == -1){
    39. perror("read");
    40. }else{
    41. printf("read messasge from sever:%d %s\n",n_read,readbuf);
    42. }
    43. return 0;
    44. }

    将服务端和客户端结合起来:

    运行结果:

    实现服务端和客户端聊天 

    服务端

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. int main(int argc,char **argv)
    9. {
    10. int s_fd;
    11. int c_fd;
    12. char readbuf[128] = {0};
    13. char msg[128] = {0};
    14. struct sockaddr_in s_addr;
    15. struct sockaddr_in c_addr;
    16. memset(&s_addr,0,sizeof(struct sockaddr_in));
    17. memset(&c_addr,0,sizeof(struct sockaddr_in));
    18. if(argc != 3){//参数判断
    19. printf("param is not good\n");
    20. exit(-1);
    21. }
    22. //1.socket
    23. s_fd = socket(AF_INET,SOCK_STREAM,0);
    24. if(s_fd == -1)
    25. {
    26. perror("sccket");
    27. exit(-1);
    28. }
    29. //2.bind
    30. s_addr.sin_family = AF_INET;
    31. s_addr.sin_port = htons(atoi(argv[2]));
    32. inet_aton(argv[1],&s_addr.sin_addr);
    33. bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
    34. //3.listen
    35. listen(s_fd,10);
    36. //4.accept
    37. int clen;
    38. clen = sizeof(struct sockaddr_in);
    39. //为实现双方聊天的关键点,死循环,一直执行在这里,不让程序退出,调用进程函数fork来实现聊天功能
    40. while(1){
    41. c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
    42. printf("sever c_fd:%d\n",c_fd);
    43. if(c_fd == -1)
    44. {
    45. perror("accept");
    46. }
    47. printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));
    48. if(fork() == 0)
    49. {//fork() == 0子进程用来发
    50. if(fork()==0){
    51. while(1)
    52. {
    53. memset(msg,0,sizeof(msg));
    54. printf("please input:\n");
    55. gets(msg);
    56. write(c_fd,msg,strlen(msg));
    57. }
    58. }
    59. }
    60. while(1)//父进程while(1)用来读
    61. {
    62. memset(readbuf,0,sizeof(readbuf));
    63. int n_read = read(c_fd,readbuf,128);
    64. if(n_read == -1)
    65. {
    66. perror("read");
    67. exit(-1);
    68. }
    69. else
    70. {
    71. printf("read msg:%d %s\n",n_read,readbuf);
    72. }
    73. }
    74. }
    75. return 0;
    76. }

    客户端

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. int main(int argc,char **argv)
    9. {
    10. int c_fd;
    11. char readbuf[128] = {0};
    12. char msg[128] = {0};
    13. struct sockaddr_in c_addr;
    14. memset(&c_addr,0,sizeof(struct sockaddr_in));
    15. if(argc != 3){
    16. printf("param is not good\n");
    17. exit(-1);
    18. }
    19. //1.socket
    20. c_fd = socket(AF_INET,SOCK_STREAM,0);
    21. if(c_fd == -1)
    22. {
    23. perror("sccket");
    24. exit(-1);
    25. }
    26. //2.connect
    27. c_addr.sin_family = AF_INET;
    28. c_addr.sin_port = htons(atoi(argv[2]));
    29. inet_aton(argv[1],&c_addr.sin_addr);
    30. if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in)) == -1)
    31. {
    32. perror("connect");
    33. exit(-1);
    34. }
    35. while(1)
    36. {
    37. if(fork() == 0)
    38. {//fork() == 0子进程用来发
    39. while(1)
    40. {
    41. memset(msg,0,sizeof(msg));
    42. printf("please input your msg:\n");
    43. gets(msg);
    44. write(c_fd,msg,strlen(msg));
    45. }
    46. }
    47. while(1)//父进程while(1)用来读
    48. {
    49. memset(readbuf,0,sizeof(readbuf));
    50. int n_read = read(c_fd,readbuf,128);
    51. if(n_read != -1)
    52. {
    53. printf("read messasge from sever:%d %s\n",n_read,readbuf);
    54. }
    55. }
    56. }
    57. return 0;
    58. }

    运行结果:

    多方消息收发

    (光用目前学过的父子进程来做 非常麻烦 不好实现 等后期进一步学习完再来补充这一块知识)

    补充常识

    ping用于确定本地主机是否能与另一台主机成功交换(发送与接收)数据包,再根据返回的信息,就可以推断TCP/IP参数是否设置正确,以及运行是否正常、网络是否通畅等。ping用于确定本地主机是否能与另一台主机成功交换(发送与接收)数据包,再根据返回的信息,就可以推断TCP/IP参数是否设置正确,以及运行是否正常、网络是否通畅等。

    如:常识用windows与Linux

    Telnet协议是TCP/IP协议族中的一员,是Internet远程登录服务的标准协议和主要方式。它为用户提供了在本地计算机上完成远程主机工作的能力。在终端使用者的电脑上使用telnet程序,用它连接到服务器。终端使用者可以在telnet程序中输入命令,这些命令会在服务器上运行,就像直接在服务器的控制台上输入一样。可以在本地就能控制服务器。要开始一个telnet会话,必须输入用户名和密码来登录服务器。Telnet是常用的远程控制Web服务器的方法。 

    windows底下telnet遇到如下问题解决方法:

     'telnet' 不是内部或外部命令,也不是可运行的程序 或批处理文件。

    关于include内容搜索 

    在include地下搜索struct sockaddr_in结构体

    进入头文件的目录:cd /usr/include/

    寻找包含该内容的头文件和行号

    grep “struct sockaddr_in” * -nir(r是递归的意思,n是找出行号,i是不区分大小写) 

    可以搜索这个结构体定义的地方 vi linux/in.h +184找到结构的体定义初始位置。

  • 相关阅读:
    一文教你Linux 磁盘管理
    机器学习第十一课--K-Means聚类
    【Qt】鼠标拖拽修改控件尺寸---八个方位修改
    R语言遍历文件夹求取其中所有栅格文件的平均值
    Mycat 安装配置
    VUE+ts项目配置--alias别名配置
    基于SpringBoot+MyBatis实现的个人博客系统(一)
    支持双层PDF:IronOCR for .NET 2022.11 支持 -127+国家语言
    办理广播电视节目制作许可证? 你需要知道这些
    导航基础知识
  • 原文地址:https://blog.csdn.net/weixin_50546241/article/details/126239642