• linux网络编程-socket-函数及TCP通信实现


    套接字概念

    在通信过程中,套接字一定是成对出现的即服务器一个,客户端一个。Socket本身有“插座”的意思,在Linux环境下,用于表示进程间网络通信的特殊文件类型。本质为内核借助缓冲区形成的伪文件。
    既然是文件,那么理所当然的,我们可以使用文件描述符引用套接字

    在这里插入图片描述
    虽然仅有一个套接字,但这个套接字有两个缓冲区,一端的发送缓冲区对应对端的接收缓冲区,那么不看细节,那就是管道从一端流到另一端。
    一个服务端和一个客户端连接有三个socket,两个在服务端,一个在客户端,服务端使用socket函数创建完socket并bind地址结构在connect设置监听上限 然后交给accept,在有连接建立后,accept将新建一个socket,旧socket将继续去监听。

    预备知识

    网络字节序和基本转换函数

    TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。
    如果本地存储使用小端字节序,需要进行网络字节序和主机字节序的转换。

    #include <arpa/inet.h>
    uint32_t htonl(uint32_t hostlong); //不经常使用,使用inet_pton
    uint16_t htons(uint16_t hostshort);//客户端和服务端指定端口时使用
    uint32_t ntohl(uint32_t netlong);  //不经常使用,使用inet_ntop
    uint16_t ntohs(uint16_t netshort);//服务端获取客户端端口时使用
    
    • 1
    • 2
    • 3
    • 4
    • 5

    htonl 本地ip转网络ip
    htons 本地端口转网络端口
    ntohl 网络ip转本地ip
    ntohs 网络端口转本地端口
    n代表网络,h代表本地,l代表ip,s代表端口,ip不是点分十进制

    上述在转换ip时还需要将点分十进制string类型转换成int类型,比较麻烦
    并且只能处理ipv4的地址,现在在转换ip时使用下面的函数

    	#include <arpa/inet.h>
    	int inet_pton(int af, const char *src, void *dst);
    	const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
    
    • 1
    • 2
    • 3

    inet_pton,这里的p表示点分十进制的ipv4或者ipv6,n表示网络字节序,因此这个命令表示将点分十进制的ip转换成网络字节序。
    inet_ntop 顾名思义将网络字节序转换成点分十进制的形式。
    具体看下面的ip地址转换函数。

    上述转换函数在sockaddr_in中使用

    IP地址转换函数

    在linxu使用man inet_pton查看函数原型

    //本地字节序(string)转换成网络字节序
    int inet_pton(int af, const char *src, void *dst);
    
    • 1
    • 2

    af表示使用的ip类型,有 AF_INET和 AF_INET6
    src:(传入)表示ip地址(点分十进制)
    dst:(传出)转换后的网络字节序的ip地址
    返回值:1:成功 ; 0:表示src不是一个有效的ip地址;-1:失败

    在linux使用man inet_ntop查看函数原型

    //网络字节序(二进制)转换成本地字节序(string)
    #include <arpa/inet.h>
    const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);
    
    • 1
    • 2
    • 3

    af表示使用的ip类型,有 AF_INET和 AF_INET6
    src:(传入)网络字节序ip地址
    dst:(传出)本地字节序(string)
    size: dst的大小
    返回值:成功返回一个dst,失败返回空

    sockaddr数据结构

    在这里插入图片描述

    //一般代码
    struct sockaddr_in addr;
    bind(fd,(struct sockaddr*)&addr,);//第三个参数是size,这里先不说
    //强制将sockaddr_in转换成sockaddr类型
    
    • 1
    • 2
    • 3
    • 4

    man有9卷,查看ip看第7卷,即man 7 ip

    在这里插入图片描述
    定义的时候,由于addr.sin_addr是一个结构体,应该给结构体里的数据赋值而不是给结构体,这里注意。

    struct sockaddr_in addr;
    addr.sin_family= AF_INET;//或者是 AF_INET6
    addr.sin_port=htons(9527);//这里随便指定了一个端口  本地端口转网络端口h本地 n网络 s端口
    addr.sin_addr.s_addr=inet_pton(AF_INET,"192.168.1.101",dst);
    
    • 1
    • 2
    • 3
    • 4

    上述的addr.sin_addr.s_addr可以那样写,也可以

    int dst;
    inet_pton(AF_INET,"192.168.1.101",(void *)&dst);
    addr.sin_addr.s_addr=dst;
    
    • 1
    • 2
    • 3

    但一般都是使用宏
    在这里插入图片描述

    INADDR_ANY;//取出系统中有效的任意ip地址,取出的是二进制类型
    //因此还要转换成网络字节序
    addr.sin_addr.s_addr=(htonl)INADDR_ANY;
    
    • 1
    • 2
    • 3

    网络套接字函数

    在这里插入图片描述
    服务端
    socket()产生一个套接字,由文件描述符关联
    bind()将套接字和ip、端口绑定
    listen()设置同时监听上限(说白了就是参数设置,并不是监听)
    accept()阻塞监听客户端连接
    套接字给了accept作为参数,当与客户端建立连接后会返回一个新的socket,那个旧的socket会被解放出来继续监听,这个旧socket就像酒店迎宾小姐,引着人进入酒店后会重新去监听。
    read()读数据
    write()回写数据
    close()关闭连接

    客户端
    socket()创建socket
    connect()socket要连接的ip和端口
    write()
    read()
    close()

    socket()

    man socket

      #include <sys/types.h>          /* See NOTES */
      #include <sys/socket.h>
      int socket(int domain, int type, int protocol);
    
    • 1
    • 2
    • 3

    功能:socket 创建一个套接字
    domain:常见下面的三种
    在这里插入图片描述
    type:指定传输的协议 SOCK_STREAM / SOCK_DGRAM
    在这里插入图片描述
    protocol: 选用的协议中代表协议 默认传0,传0便是根据type指定的选协议
    SOCK_STREAM 便是TCP,SOCK_DGRAM便是UDP
    返回值:成功返回一个新套接字对应的文件描述符 ,失败是返回-1
    在这里插入图片描述

    fd= socket(AF_INET, SOCK_STREAM , 0);
    
    • 1

    bind()

    man 2 bind

     #include <sys/types.h>          /* See NOTES */
     #include <sys/socket.h>
    
     int bind(int sockfd, const struct sockaddr *addr,
                    socklen_t addrlen);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    功能:给socket绑定一个ip+端口(地址结构)
    sockfd:socket()的返回值
    addr:先初始化sockaddr_in,再强转成sockaddr ,下面是个例子

    下面初始化sockaddr_in

    struct sockaddr_in addr;
    addr.sin_family= AF_INET;//或者是 AF_INET6
    addr.sin_port=htons(9527);//这里随便指定了一个端口  本地端口转网络端口h本地 n网络 s端口
    addr.sin_addr.s_addr=(htonl)INADDR_ANY;
    
    • 1
    • 2
    • 3
    • 4

    强转时

    (struct sockaddr *)addr
    
    • 1

    addrlen:地址结构的大小
    返回值:成功返回0 失败返回-1 error

    listen()

    man listen

     #include <sys/types.h>          /* See NOTES */
     #include <sys/socket.h>
     int listen(int sockfd, int backlog);
    
    • 1
    • 2
    • 3

    功能:listen设置同时与服务器建立连接的上限数(同时进行三次握手的客户端数量)
    sockfd:socket
    backlog :上限值,上限值不能超过128
    返回值:成功返回0 失败返回-1 error

    accept()

    man 2 accept

     #include <sys/types.h>          /* See NOTES */
     #include <sys/socket.h>
     int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    
    • 1
    • 2
    • 3

    功能:阻塞等待客户端建立连接,成功返回一个与客户端成功连接的新socket文件描述符

    sockfd:给个socket,这里注意,新产生的socket要依托于旧socket,主要是用旧socket的ip和端口
    addr:传出参数,传出成功与服务器建立连接的那个客户端的地址结构(ip+端口号)
    addrlen:传入传出参数。进去时是addr的大小,出是客户端addr实际大小

    socklen_t clit_addr_len =sizeof(addr);
    //第三个参数如下面这样写
    &clit_addr_len 
    
    • 1
    • 2
    • 3

    返回值:成功时 返回能够与服务器进行数据通信的socket对应的文件描述符 失败:-1 error

    connect()

    man 2 connect

      #include <sys/types.h>          /* See NOTES */
      #include <sys/socket.h>
      int connect(int sockfd, const struct sockaddr *addr,
                       socklen_t addrlen);
    
    • 1
    • 2
    • 3
    • 4

    使用现有的socket与服务器建立连接
    sockfd:socket
    addr:传入参数,是服务器的地址结构(ip+端口)
    addrlen:服务器地址结构的大小

    客户端不需要绑定自己的ip和端口,虽然可以使用bind绑定
    如果没有绑定,那么会采用“隐式绑定”(系统来干的)

    实例

    在这里插入图片描述
    注意上图强转时是传的地址,上图有个错误,在accept函数里面,第二个参数应该是用来保存客户端信息的。具体代码看后面。
    在这里插入图片描述
    编译

    gcc tcpserver.c -o server -Wall -g
    
    • 1

    运行server,这里暂时使用脑残命令测试
    在这里插入图片描述
    可以看到运行成功
    下面是代码,下面代码加了显示客户端ip地址和端口的功能

    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<unistd.h>
    #include<errno.h>
    #include<pthread.h>
    #include<ctype.h>
    #include<sys/socket.h>
    #include<arpa/inet.h>
    
    
    void sys_err(const char *str)
    {
     perror(str);
     exit(1);
    }
    
    
    int main(int argc,char *argv[])
    {
      int lfd=0;
      lfd=socket(AF_INET,SOCK_STREAM,0);
      if(lfd==-1)
      {
         sys_err("socket error");
      }
      
      //bind函数绑定ip和端口
      struct sockaddr_in server_addr;
      server_addr.sin_family=AF_INET;
      server_addr.sin_port=htons(22222);
      server_addr.sin_addr.s_addr=(htonl)INADDR_ANY;
      bind(lfd,(struct sockaddr *)&server_addr,sizeof(server_addr));
      
      //设置同时监听个数
      int lisRetrun=listen(lfd,128);
      if(lisRetrun==-1)
      {
          sys_err("listen error");
      }
      
      //accept
      struct sockaddr_in client_addr;
      socklen_t cliAddrLen=sizeof(client_addr);
      int cfd=accept(lfd,(struct sockaddr *)&client_addr,&cliAddrLen);
      if(cfd==-1)
      {
          sys_err("accept error");
      }
      
      char client_ip[1024];
      printf("\n client ip:%s  port:%d\n",
                    inet_ntop(AF_INET,&client_addr.sin_addr,client_ip,sizeof(client_ip)),
                    ntohs(client_addr.sin_port)
                        );
      
      char buf[BUFSIZ];
      while(1)
      {
          
         int ret= read(cfd,buf,sizeof(buf));
          
          int i;
         for(i=0;i<ret;i++)
          {
           printf("%c",buf[i]);
           buf[i]=toupper(buf[i]);
          }
      
          write(cfd,buf,ret);
      
      }
      close(lfd);
      close(cfd);
      
      return 0;
      
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80

    客户端代码

    在这里插入图片描述

    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<unistd.h>
    #include<errno.h>
    #include<pthread.h>
    #include<ctype.h>
    #include<sys/socket.h>
    #include<arpa/inet.h>
    
    
    void sys_err(const char *str)
    {
     perror(str);
     exit(1);
    }
    
    
    int main(int argc,char *argv[])
    {
      
      
      int cfd=socket(AF_INET,SOCK_STREAM,0);
      if(cfd==-1)
      {
          sys_err("create socket error");
      }
      
      struct sockaddr_in serv_addr;//服务器地址结构
      serv_addr.sin_family=AF_INET;
      serv_addr.sin_port=htons(22222);//设置端口
      int dst;
      inet_pton(AF_INET, "127.0.0.1", (void *)&dst);
      serv_addr.sin_addr.s_addr=dst;
      
      int conResult=connect(cfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
      if(conResult)
      {
          sys_err("connect error");
      }
      
      
      int count=10;
      char buf[1024];
      while(--count)
      {
         write(cfd,"hello",5); 
        int ret= read(cfd,buf,sizeof(buf));
         int j;
         for(j=0;j<ret;j++)
         {
             printf("%c",buf[j]);
         }
         sleep(1);
         printf("\n");
      }
      
      close(cfd);
      return 0;
      
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
  • 相关阅读:
    day062:平衡二叉树——左旋、右旋
    JS如何反转字符串以及如何将数字转换为数字数组
    修改键盘映射、交换按键
    SaleSmartly新增AI意图识别触发器!让客户享受更精准的自动化服务
    2023年下半年架构案例真题及答案
    docker、docker-compose部署oracle,plsql连接远程oracle
    APP瀑布流分页问题——领取,使用等操作即消失问题
    阿里云负载均衡(SLB)简介
    Windows11 wsl2编译Android14 使用ASfP Debug windows上启动的模拟器
    什么?Coolbpf 不仅可以远程编译,还可以发现网络抖动!
  • 原文地址:https://blog.csdn.net/baidu_41553551/article/details/125601652