• 以C语言为例完成简单的网络聊天程序以及关于socket在Linux下系统调用的分析


    套接字是网络编程中的一种通信机制,是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。

    端口号:

    端口号(port)是传输层协议的内容。

        端口号是一个2字节16位的整数;

        端口号用来标识一个进程,告诉操作系统,当前这个数据交给哪一个程序进行解析;

        IP地址 + 端口号能标识网络上的某一台主机的某一个进程;

        一个端口号只能被一个进程占用。

    端口号 & 进程:

       概念

        进程有唯一的pid标识,端口号也能标识进程;

        一个进程可以绑定多个端口号,一个端口号不能被多个进程绑定。

        源端口号 & 目的端口号

        传输层协议(TCP/IP)的数据段中有两个端口号,分别叫做源端口号和目的端口号,就是在描述“数据是谁的?发给谁?”

    TCP:

    (TCP)传输控制协议,面向连接。是一种提供可靠数据传输的通用协议。

        传输层协议

        有连接

        可靠传输

        面向字节流

     

    socket API:

    1.创建socket文件描述符  (TCP/UDP,客户端+服务器)

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

        参数1(domain): 选择创建的套接字所用的协议族;

        AF_INET : IPv4协议;

        AF_INET6: IPv6协议;

        AF_LOCAL: Unix域协议;

        AF_ROUTE:路由套接口;

        AF_KEY :密钥套接口。

        参数2(type):指定套接口类型,所选类型有:

        SOCK_STREAM:字节流套接字;

        SOCK_DGRAM : 数据报套接字;

        SOCK_RAW : 原始套接口。

        procotol: 使用的特定协议,一般使用默认协议(NULL)。

    2.绑定端口号  (TCP/IP,服务器) 

    int bind(int socket, const struct sockaddr *address, socklen_t address_len);

        参数1(socket) : 是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。

        参数2(address):指向特定协议的地址指针。

        参数3(address_len):上面地址结构的长度。

        返回值:没有错误,bind()返回0,否则SOCKET_ERROR。

    3.开始监听socket  (TCP,服务器) 

    int listen(int socket, int backlog);

        参数1(sockfd):是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。

        参数2(backlog):所监听的端口队列大小。 

    4.接受请求  (TCP,服务器)

    int accept(int socket, struct sockaddr* address, socklen_t* address_len);

        参数1(socket) : 是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。

        参数2(address):指向特定协议的地址指针。

        参数3(addrlen):上面地址结构的长度。

        返回值:没有错误,bind()返回0,否则SOCKET_ERROR。 

    5.建立连接  (TCP,客户端) 

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

    6.关闭套接字

    int close(int fd);

    参数(fd):是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。

     

    服务器端程序:

    1 、加载套接字库

    2 、创建套接字(socket )。

    3 、将套接字绑定到一个本地地址和端口上(bind )。

    4 、将套接字设为监听模式,准备接收客户请求(listen )。

    5 、等待客户请求到来;当请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept )。

    6 、用返回的套接字和客户端进行通信(send/recv )。

    7 、返回,等待另一客户请求。

    8 、关闭套接字。

    #include <stdio.h>
    
    #include <string.h>
    
    #include <sys/types.h>
    
    #include <sys/socket.h>
    
    #include <netinet/in.h>
    
    #include <arpa/inet.h>
    
    #include <unistd.h>
    
     
    
    int main(int argc, char *argv[])
    
    {
    
           int server_sockfd;//服务器端套接字
    
           int client_sockfd;//客户端套接字
    
           int len;
    
           struct sockaddr_in my_addr;   //服务器网络地址结构体
    
           struct sockaddr_in remote_addr; //客户端网络地址结构体
    
           int sin_size;
    
           char buf[BUFSIZ];  //数据传送的缓冲区
    
           memset(&my_addr,0,sizeof(my_addr)); //数据初始化--清零
    
           my_addr.sin_family=AF_INET; //设置为IP通信
    
           my_addr.sin_addr.s_addr=INADDR_ANY;//服务器IP地址--允许连接到所有本地地址上
    
           my_addr.sin_port=htons(8000); //服务器端口号
    
          
    
           /*创建服务器端套接字--IPv4协议,面向连接通信,TCP协议*/
    
           if((server_sockfd=socket(PF_INET,SOCK_STREAM,0))<0)
    
           { 
    
                  perror("socket");
    
                  return 1;
    
           }
    
     
    
            /*将套接字绑定到服务器的网络地址上*/
    
           if (bind(server_sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0)
    
           {
    
                  perror("bind");
    
                  return 1;
    
           }
    
          
    
           /*监听连接请求--监听队列长度为5*/
    
           listen(server_sockfd,5);
    
          
    
           sin_size=sizeof(struct sockaddr_in);
    
          
    
           /*等待客户端连接请求到达*/
    
           if((client_sockfd=accept(server_sockfd,(struct sockaddr *)&remote_addr,&sin_size))<0)
    
           {
    
                  perror("accept");
    
                  return 1;
    
           }
    
           printf("accept client %s
    ",inet_ntoa(remote_addr.sin_addr));
    
           len=send(client_sockfd,"Welcome to my server
    ",21,0);//发送欢迎信息
    
          
    
           /*接收客户端的数据并将其发送给客户端--recv返回接收到的字节数,send返回发送的字节数*/
    
           while((len=recv(client_sockfd,buf,BUFSIZ,0))>0)
    
           {
    
                  buf[len]='';
    
                  printf("%s
    ",buf);
    
                  if(send(client_sockfd,buf,len,0)<0)
    
                  {
    
                         perror("write");
    
                         return 1;
    
                  }
    
           }
    
           close(client_sockfd);
    
           close(server_sockfd);
    
           return 0;
    
    }
    

      

     

    客户端程序:

    1 、加载套接字库

    2 、创建套接字(socket )。

    3 、向服务器发出连接请求(connect )。

    4 、和服务器端进行通信(send/recv )。

    5 、关闭套接字。

     

    #include <stdio.h>
    
    #include <string.h>
    
    #include <sys/types.h>
    
    #include <sys/socket.h>
    
    #include <netinet/in.h>
    
    #include <arpa/inet.h>
    
    #include <unistd.h>
    
     
    
    int main(int argc, char *argv[])
    
    {
    
           int client_sockfd;
    
           int len;
    
           struct sockaddr_in remote_addr; //服务器端网络地址结构体
    
           char buf[BUFSIZ];  //数据传送的缓冲区
    
           memset(&remote_addr,0,sizeof(remote_addr)); //数据初始化--清零
    
           remote_addr.sin_family=AF_INET; //设置为IP通信
    
           remote_addr.sin_addr.s_addr=inet_addr("127.0.0.1");//服务器IP地址
    
           remote_addr.sin_port=htons(8000); //服务器端口号
    
          
    
           /*创建客户端套接字--IPv4协议,面向连接通信,TCP协议*/
    
           if((client_sockfd=socket(PF_INET,SOCK_STREAM,0))<0)
    
           {
    
                  perror("socket");
    
                  return 1;
    
           }
    
          
    
           /*将套接字绑定到服务器的网络地址上*/
    
           if(connect(client_sockfd,(struct sockaddr *)&remote_addr,sizeof(struct sockaddr))<0)
    
           {
    
                  perror("connect");
    
                  return 1;
    
           }
    
           printf("connected to server
    ");
    
           len=recv(client_sockfd,buf,BUFSIZ,0);//接收服务器端信息
    
             buf[len]='';
    
           printf("%s",buf); //打印服务器端信息
    
          
    
           /*循环的发送接收信息并打印接收信息--recv返回接收到的字节数,send返回发送的字节数*/
    
           while(1)
    
           {
    
                  printf("Enter string to send:");
    
                  scanf("%s",buf);
    
                  if(!strcmp(buf,"quit"))
    
                         break;
    
                  len=send(client_sockfd,buf,strlen(buf),0);
    
                  len=recv(client_sockfd,buf,BUFSIZ,0);
    
                  buf[len]='';
    
                  printf("received:%s
    ",buf);
    
           }
    
           close(client_sockfd);//关闭套接字
    
        return 0;
    
     
    
    }
    
     
    

      

     

     

    这里总结GDB调试过程以及命令:

    gcc server.c -o server_gdb -g

    gdb server_gdb

     

    命令

    命令缩写

    命令说明

    list

    l

    显示多行源代码

    break

    b

    设置断点,程序运行到断点的位置会停下来

    info

    i

    描述程序的状态

    run

    r

    开始运行程序

    display

    disp

    跟踪查看某个变量,每次停下来都显示它的值

    step

    s

    执行下一条语句,如果该语句为函数调用,则进入函数执行其中的第一条语句

    next

    n

    执行下一条语句,如果该语句为函数调用,不会进入函数内部执行(即不会一步步地调试函数内部语句)

    print

    p

    打印内部变量值

    continue

    c

    继续程序的运行,直到遇到下一个断点

    set var name=v

     

    设置变量的值

    start

    st

    开始执行程序,在main函数的第一条语句前面停下来

    file

     

    装入需要调试的程序

    kill

    k

    终止正在调试的程序

    watch

     

    监视变量值的变化

    backtrace

    bt

    产看函数调用信息(堆栈)

    frame

    f

    查看栈帧

    quit

    q

    退出GDB环境

     下面是socket在linux下系统调用的分析

    1.在include/linux/syscalls.h中定义了sys_socket函数的函数原型

    asmlinkage long sys_socket(int, int, int);

    系统调用函数必须满足:

    asmlinkage long sys_##function-name(##args){ ,return ret}

    2.在arch/arm/include/asm,unistd.h中,将sys_socket系统调用和系统调用好关联起来

    #define __NR_socket 97    

    __SYSCALL(__NR_socket, sys_socket)//系统调用号为97

    在unistd.h中,同时给出注释表明,给函数的实现在socket.c中

    3.进入socket.c(net/中),发现有这样一个函数实现(或者定义)

    SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol){ ….}

    肯定这就是asmlinkage long sys_socket(int,int,int)的实现了。为了表明这一切,需要进一步查看宏SYSCALL_DEFINE3的定义

    SYSCALL_DEINFE3的定义也在syscalls.h中

    #define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)

    #define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__) 

    #define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)

    #define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)

    #define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)

    #define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)

    那么:SYSCALL_DEFINE3(socket,int,family,int,type,int,protocal)===SYSCALL_DEFINEX(3,socket,__VA_ARGS__)

    由此,我们得到

    SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol){ ….}

    SYSCALL_DEFINEX(3,_socket,__VA_ARGS__)

    _SYSCALL_DEFINE(3,_socket,__VA_ARGS__)

    asmlinkage long sys_socket(int family,int type,int protocol)

    而他的原型正是:asmlinkage long sys_socketintintint

    4.

    分析了系统是如何定义和实现sys_socket的系统调用。接下来,仔细分析sys_socket是如何实现创socket的。在前面,我们知道inet_family_ops中的create函数为inet_create,也就是说,如果要创建inet型的socket,将由函数inet_create来创建。

    先来看看inet_family_ops

    static const struct net_proto_family inet_family_ops = {

           .family = PF_INET,

           .create = inet_create,

           .owner  = THIS_MODULE,

    };

     

     

     

    下面看看sys_socket中的函数调用关系:

    sys_socket

           |

           +--------- sock_create

           |                    |

           |                    +------- __sock_create

           |                                         |

           |                                         +------- security_socket_create

           |                                         +-------- sock_alloc()

           |                                         +--------- rcu_dereference(net_families[family])

           |                                         +--------- pf->create(net, sock, protocol, kern)

           |                                         +--------- module_put(pf->owner)

           |                                         +--------- security_socket_post_create

           +---------- sock_map_fd

    sys_socket 调用sock_create函数,最终调用rcu_dereference函数来得到相应的net_family_ops,在这里是inet_family_ops,然后调用inet_family_ops结构中的create函数,这里是inet_create函数,来创建socket。sock_map_fd是得到一个文件号。

    当使用socket(int,int,int)创建一个socket时,socket会调用sys_socket来完成socket的创建。

  • 相关阅读:
    kafka 安装和基本操作
    IPv6表示方法及其简化方法
    Python print输出函数
    同步工具之Vector X
    golang之热加载Fresh&air X
    TOML 1.0格式语法 X
    PHPstorm配置webserver X
    编程辅助工具之Kite X
    golang项目之Makefile X
    高性能消息队列之nsq X
  • 原文地址:https://www.cnblogs.com/buzhidao1/p/11984888.html
Copyright © 2020-2023  润新知