您的位置:首页 > 理论基础 > 计算机网络

嵌入式Linux网络编程,网络基础,TCP编程,socket(),bind(),listen(),accept(),connect(),send()/recv(),close()/shutdown()

2018-11-15 17:43 651 查看

文章目录

  • 3,把主动套接字变成被动套接字listen()
  • 4,阻塞等待客户端连接请求accept()
  • 5,客户端的连接函数connect()
  • 6,发送数据send()、write()
  • 7,接受数据 recv()、read()
  • 8,套接字的关闭 close()、shutdown()
  • 9,示例
  • 1,创建socket文件描述符socket()

    int socket (int domain, int type, int protocol);

    1. domain 是地址族(域名)
    domain 含义
    PF_NS // Xerox NS协议
    PF_IMPLINK // Interface Message协议
    AF_INET IPv4 Internet protocols ip(7) // internet 协议
    AF_INET6 IPv6 Internet protocols ipv6(7)
    AF_UNIX,AF_LOCAL Local communication unix(7)// unix internal协议
    AF_NETLINK Kernel user interface device netlink(7)
    AF_PACKET Low level packet interface packet(7)
    1. type // 套接字类型
      ·SOCK_STREAM // 流式套接字,唯一对应于TCP
      ·SOCK_DGRAM // 数据报套接字,唯一对应着UDP
      ·SOCK_RAW // 原始套接字
    2. protocol 参数通常置为0,原始套接字编程时需填充
    3. 返回值
      ·On success, a file descriptor for the new socket is returned.
      ·On error, -1 is returned, and errno is set appropriately.
      ·成功时返回文件描述符,出错时返回为-1

    2, 绑定bind()

    int bind (int sockfd, struct sockaddr* addr, int addrLen);

    1. sockfd 由socket() 调用返回

    2. addr 是指向 sockaddr_in 结构的指针,包含本机IP 地址和端口号
      struct sockaddr_in

      u_short sin_family // protocol family
      u_short sin_port     // port number
      struct in_addr  sin_addr  //IP address (32-bits)
    3. addrLen(地址长度) : sizeof (struct sockaddr_in)

    4. 返回值
      ·On success, zero is returned.
      ·On error, -1 is returned, and errno is set appropriately.

    2.1, 地址相关的数据结构struct sockaddr、struct sockaddr_in、struct in_addr

    1. 通用地址结构
    struct sockaddr
    {
    u_short  sa_family;    //2字节, 地址族, AF_xxx
    char  sa_data[14];     // 14字节协议地址
    };
    1. Internet协议地址结构
    struct sockaddr_in
    {
    u_short sin_family;      // 地址族, AF_INET,2 bytes
    u_short sin_port;      // 端口,2 bytes
    struct in_addr sin_addr;  // IPV4地址,4 bytes
    char sin_zero[8];        // 8 bytes unused,作为填充必须清零
    };
    1. IPv4地址结构
    // internet address
    struct in_addr
    {
    in_addr_t  s_addr;            // u32 network address
    };

    3,把主动套接字变成被动套接字listen()

    int listen (int sockfd, int backlog);

    1. sockfd:监听连接的套接字,通过socket()函数拿到的fd
    2. backlog
      ·指定了正在等待连接的最大队列长度,它的作用在于处理可能同时出现的几个连接请求。
      ·同时允许几路客户端和服务器进行正在连接的过程(正在三次握手)一般填5, 测试得知,ARM最大为8
      ·DoS(拒绝服务)攻击即利用了这个原理,非法的连接占用了全部的连接数,造成正常的连接请求被拒绝。
    • 内核中服务器的套接字fd会维护2个链表:
      1. 正在三次握手的的客户端链表(数量=2*backlog+1)
      2. 已经建立好连接的客户端链表(已经完成3次握手分配好了newfd)
    1. 返回值: 成功返回0 或 失败返回-1

    完成listen()调用后,socket变成了监听socket(listening socket).

    4,阻塞等待客户端连接请求accept()

    #include <sys/types.h>
    #include <sys/socket.h>
    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) ;

    1. sockfd : 监听套接字 ,经过前面socket()创建并通过bind(),listen()设置过的fd
    2. addr : 对方地址1(内核自动取到连接过来的客户端的信息)
    3. addrlen:地址长度
    4. 返回值:
      ·On success, these system calls return a nonnegative integer that is a descriptor for the accepted socket.
      ·On error, -1 is returned, and errno is set appropriately.
      ·成功时返回已经建立好连接的新的newfd

    listen()和accept()是TCP服务器端使用的函数

    5,客户端的连接函数connect()

    #include <sys/types.h>
    #include <sys/socket.h>
    int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

    使用方法和服务器的bind()函数类似

    1. sockfd : socket返回的文件描述符
    2. serv_addr : 服务器端的地址信息
    3. addrlen : serv_addr的长度
    4. 返回值:0 或 -1

    connect()是客户端使用的系统调用。

    6,发送数据send()、write()

    send() write()
    ssize_t send(int socket, const void *buffer, size_t length, int flags); ssize_t write(int fd, const void *buf, size_t count);

    #include <sys/socket.h>
    ssize_t send(int socket, const void *buffer, size_t length, int flags);

    1. buffer : 发送缓冲区首地址
    2. length : 发送的字节数
    3. flags : 发送方式(通常为0,填0的时候和write()一样)
    flags 含义
    MSG_DONTWAIT Enables nonblocking operation;非阻塞发送
    MSG_OOB 用以发送TCP类型的带外数据(out-of-band)
    1. 返回值:
      ·成功:实际发送的字节数
      ·失败:-1, 并设置errno

    7,接受数据 recv()、read()

    recv() read()
    ssize_t recv(int socket, const void *buffer, size_t length, int flags); ssize_t read(int fd, void *buf, size_t count);

    #include <sys/socket.h>
    ssize_t recv(int socket, const void *buffer, size_t length, int flags);

    1. buffer : 发送缓冲区首地址
    2. length : 发送的字节数
    3. flags : 接收方式(通常为0,填0的时候和read()一样)
    flags 含义
    MSG_DONTWAIT Enables nonblocking operation;非阻塞发送
    MSG_OOB 用以发送TCP类型的带外数据(out-of-band)
    MSG_PEEK This flag causes the receive operation to return data from thebeginning of the receive queue without removing that data from
    the queue. Thus, a subsequent receive call will return the same data.内核会从网络接受数据,并填充缓冲区,read()函数读掉
    的数据在缓冲区中就没了;recv()函数用了此参数,读完之后,数据任然存在
    1. 返回值:
      ·成功:实际接收的字节数
      ·失败:-1, 并设置errno

    8,套接字的关闭 close()、shutdown()

    8.1,关闭双向通讯 close()

    int close(int sockfd);

    8.2,选择关闭 shutdown()

    int shutdown(int sockfd, int howto);

    1. TCP连接是双向的(是可读写的),当我们使用close时,会把读写通道都关闭,有时侯我们希望只关闭一个方向,这个时候我们可以使用shutdown。
    2. howto
    howto 含义
    SHUT_RD further receptions will be disallowed关闭读通道,但是可以继续往套接字写数据
    SHUT_WR further transmissions will be disallowed关闭写通道。只能从套接字读取数据
    SHUT_RDWR further recep‐tions and transmissions will be disallowed,和close()效果一样
    1. RETURN VALUE
      ·On success, zero is returned.
      ·On error, -1 is returned, and errno is set appropriately.

    9,示例

    9.1,头文件<net.h>

    #ifndef __NET_H__
    #define __NET_H__
    
    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <errno.h>
    #include <stdlib.h>
    #include <strings.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    
    #define SERV_PORT 5001
    #define SERV_IP_ADDR "192.168.199.200"
    #define BACKLOG 5
    #define QUIT_STR "quit"
    
    #endif

    9.2,服务器端代码<service.c>

    #include "net.h"
    
    int main(void)
    {
    int fd = -1;
    struct sockaddr_in sin;	//如果是IPV6的编程,要使用struct sockddr_in6结构体(详细情况请参考man 7 ipv6),通常更通用的方法可以通过struct sockaddr_storage来编程
    
    /* 1 创建socket fd */
    if((fd = socket(AF_INET,SOCK_STREAM,0)) < 0){	//AF_INET  IPV4编程
    perror("sockket");
    exit(1);
    }
    /* 2 绑定 */
    /* 2.1 填充struct sockaddr_in结构体变量 */
    bzero(&sin,sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_port = htons(SERV_PORT);//网络字节序的端口号
    /* 优化1  让服务器可以绑定在任意的IP上 */
    #if 1
    sin.sin_addr.s_addr = htonl(INADDR_ANY);// 优化1  让服务器可以绑定在任意的IP上
    #else
    if(inet_pton(AF_INET,SERV_IP_ADDR,(void *)sin.sin_addr.s_addr) < 0){	//sin.sin_addr.s_addr等价于sin.sin_addr
    //AF_INET  IPV4编程
    perror("inet_pton");
    exit(1);
    }
    #endif
    /* 2.2 绑定 */
    if(bind(fd,(struct sockaddr *)&sin,sizeof(sin)) < 0){
    perror("bind");
    exit(1);
    }
    /* 3 调用listen() 把主动套接字变成被动套接字 */
    if(listen(fd,BACKLOG) <0){
    perror("listen");
    exit(1);
    }
    int newfd = -1;
    /* 4 阻塞等待客户端连接请求 */
    #if 0
    if((newfd = accept(fd,NULL,NULL)) < 0)//不关心客户端信息,来了就为其服务
    {
    perror("accept");
    exit(1);
    }
    /* 优化2 通过程序获取刚建立连接的socket的客户端的IP地址和端口号 */
    #else
    struct sockaddr_in cin;
    socklen_t addrlen  = sizeof(cin);
    if((newfd = accept(fd,(struct sockaddr *)&cin,&addrlen)) < 0)
    {
    perror("accept");
    exit(1);
    }
    char ipv4_addr[16];
    if(! inet_ntop(AF_INET,(void *)&cin.sin_addr.s_addr,ipv4_addr,sizeof(cin)))//将网络字节序形式的IP地址转化为本地点分形式的字符串IP地址
    {
    perror("inet_ntop");
    exit(1);
    }
    
    printf("Client (:%s is connected port:%d\n",ipv4_addr,ntohs(cin.sin_port));
    #endif
    /* 5 读写 */
    int ret = -1;//read()是个阻塞函数,要做读写错误的工程处理
    char buf[BUFSIZ];//BUFSIZE是系统提供的
    
    while(1)
    {
    bzero(buf,BUFSIZ);//首先将buf清零
    do{
    ret = read(newfd,buf,BUFSIZ-1);//防止数组下标越界BUFSIZE-1
    }while(ret < 0 && EINTR == errno);
    if(ret < 0)
    {
    perror("read");
    exit(1);
    }
    if(!ret){	//对方已经关闭
    break;
    }
    printf("receive data: %s",buf);
    if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR))){	//用户输入了quit字符
    printf("Client is existing!\n");
    break;
    }
    }
    close(newfd);
    close(fd);
    return 0;
    }

    9.3,客户端代码<client.c>

    #include "net.h"
    
    int main(void)
    {
    int fd = -1;
    struct sockaddr_in sin;	//如果是IPV6的编程,要使用struct sockddr_in6结构体(详细情况请参考man 7 ipv6),通常更通用的方法可以通过struct sockaddr_storage来编程
    
    /* 1 创建socket fd */
    if((fd = socket(AF_INET,SOCK_STREAM,0)) < 0){	//AF_INET  IPV4编程
    perror("sockket");
    exit(1);
    }
    /* 2 连接服务器 */
    /* 2.1 填充struct sockaddr_in结构体变量 */
    bzero(&sin,sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_port = htons(SERV_PORT);//网络字节序的端口号
    #if 1
    if((sin.sin_addr.s_addr = inet_addr(SERV_IP_ADDR)) < 0){
    perror("inet_addr");
    exit(1);
    }
    #else
    if(inet_pton(AF_INET,SERV_IP_ADDR,(void *)sin.sin_addr.s_addr) < 0){	//sin.sin_addr.s_addr等价于sin.sin_addr
    //AF_INET  IPV4编程
    perror("inet_pton");
    exit(1);
    }
    #endif
    /* 2.2 连接服务器 */
    if(connect(fd,(struct sockaddr *)&sin,sizeof(sin)) < 0){
    perror("connect");
    exit(1);
    }
    
    /* 3 读写数据 */
    char buf[BUFSIZ];//BUFSIZE是系统提供的
    
    while(1)
    {
    bzero(buf,BUFSIZ);//首先将buf清零
    if(fgets(buf,BUFSIZ-1,stdin) == NULL)//放置数组下标越界BUFSIZE-1
    {
    continue;
    }
    write(fd,buf,strlen(buf));
    if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR))){	//用户输入了quit字符
    printf("Client is existing!\n");
    break;
    }
    }
    /* 4 关闭套接字 */
    close(fd);
    return 0;
    }

    9.4,运行客户端

    linux@linux:~/test/network$ ./client
    asda
    axsa
    as
    quite
    Client is existing!

    9.5,运行服务器端

    linux@linux:~/test/network$ ./service
    Client (:192.168.199.200 is connected port:44644
    receive data: asda
    receive data: axsa
    receive data: as
    receive data: quite
    Client is existing!
    阅读更多
    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: 
    相关文章推荐