美文网首页
浅谈Linux C Socket编程

浅谈Linux C Socket编程

作者: Ezreallp | 来源:发表于2019-04-03 16:40 被阅读0次

    Socket

        在 UNIX/Linux 系统中,为了统一对各种硬件的操作,简化接口,不同的硬件设备也都被看成一个文件。对这些文件的操作,等同于对磁盘上普通文件的操作。
    
        UNIX/Linux 程序在执行任何形式的 I/O 操作时,都是在读取或者写入一个文件描述符。一个文件描述符只是一个和打开的文件相关联的整数,它的背后可能是一个硬盘上的普通文件、FIFO、管道、终端、键盘、显示器,甚至是一个网络连接。注意:网络连接也是一个文件,它也有文件描述符。
    
        我们可以通过socket()函数来创建一个网络连接,socket()函数的返回值就是文件描述符。有了文件描述符,我们就可以用read()读取从远程计算机传来的数据,用write()向远程计算机写入数据。
    
        socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口,在设计模式中就是一个门面模式。1.所谓socket通常也称作“套接字”,用于描述IP地址和端口,是一个通信链的语抦。应用程序通常通过“套接字”向网络发出请求或者应答网络请求。2.Socket是连接运行在网络上的两个程序间的双向通信的端点。3.网络通讯其实指的就是Socket间的通讯。通讯的两端都有Socket,数据在两个Socket之间通过IO来进行传输。
    
    image.png image.png

    socket类型

    流格式套接字(SOCK_STREAM)

        流格式套接字(SOCK_STREAM)也叫“面向连接的套接字”,它使用TCP协议,从而保证了数据传输的正确性和顺序性。它有以下几个特征:1、数据在传输过程中不会丢失。2、数据是按照顺序传输的。3、数据的发送和接受不是同步的。
    

    数据报格式套接字(SOCK_DGRAM)

        数据报格式套接字(Datagram Sockets)也叫“无连接的套接字”,它使用UDP协议,一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证是可靠、无差错的。它有以下几个特征:1、强调快速传输而非传输顺序。2、传输的数据可能丢失也可能损毁。3、限制每次传输数据的大小。4、数据的发送和接受是同步的。
    

    Linux下socket程序 这里只写了基于TCP的socket代码

    服务端代码:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <netinet/tcp.h>
    #include <netdb.h>
    #include <errno.h>
    
    int main(int argc, const char * argv[]) {
        // insert code here...
        printf("Hello, World!\n");
        
        int server_sockfd; //服务端套接字
        int client_sockfd; //客户端套接字
        int len;
        struct sockaddr_in my_add; //服务器网络地址结构体
        struct sockaddr_in remote_addr; //客户端网络地址结构体
        socklen_t sin_size;
        char buf[BUFSIZ]; //数据传送的缓冲区
        memset(&my_add,0,sizeof(my_add)); //数据初始化--清零
        my_add.sin_family = AF_INET; //设置为IP通信
        my_add.sin_addr.s_addr = INADDR_ANY; //服务器IP地址--允许连接到所有本地地址上
        my_add.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_add, 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\n",inet_ntoa(remote_addr.sin_addr));
        len = send(client_sockfd, "welcome to my server\n", 21, 0); //发送欢迎信息
        
        //接受客户端的数据并将其发送给客户端--recv返回接收到的字节数,send返回发送的字节数
        while ((len==recv(client_sockfd, buf, BUFSIZ, 0))>0) {
            buf[len]='\0';
            printf("receive:%s\n",buf);
            if (send(client_sockfd, buf, len, 0)<0) {
                perror("write");
                return 1;
            }
        }
        
        close(client_sockfd);
        close(server_sockfd);
        
        return 0;
    }
    
    

    客户端代码:

    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <netinet/tcp.h>
    #include <netdb.h>
    
    
    int main(int argc, const char * argv[]) {
        // insert code here...
        printf("Hello, World!\n");
        
        int client_sockfd;
        int len;
        struct sockaddr_in remota_addr; //服务器端网络地址结构体
        char buf[BUFSIZ];  //数据传送的缓冲区
        memset(&remota_addr,0,sizeof(remota_addr)); //数据初始化--清零
        remota_addr.sin_family = AF_INET;  //设置为IPV4通信
        remota_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
        remota_addr.sin_port = htons(8000); //服务器端口号
        
        //创建客户端套接字--Ipv4协议,面向连接通信,TCP协议
        //成功,返回0 ,失败返回-1
        if ((client_sockfd=socket(PF_INET, SOCK_STREAM, 0))<0) {
            perror("socket");
            return 1;
        }
        
        //将套接字绑定到服务器的网络地址上
        if (connect(client_sockfd, (struct sockaddr *)&remota_addr, sizeof(struct sockaddr))<0) {
            perror("connect");
            return 1;
        }
        
        printf("connect to server\n");
        
        len = recv(client_sockfd, buf, BUFSIZ, 0); //接受服务器端消息
        buf[len]='/0';
        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]='/0';
            printf("received:%s\n",buf);
            
        }
        
        close(client_sockfd);
        
        return 0;
    }
    
    

    在 Linux 下使用 <sys/socket.h> 头文件中 socket() 函数来创建套接字。

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

    (1)、af为地址族(Address Family),也就是IP地址类型,常用的有AF_INET 和 AF_INET6。AF_INET 表示 IPv4 地址,AF_INET6 表示 IPv6 地址。也可以使用 PF 前缀(PF_INET 等价于 AF_INET,PF_INET6 等价于 AF_INET6)。

    (2)、type 为数据传输方式/套接字类型,常用的有 SOCK_STREAM(流格式套接字/面向连接的套接字)和 SOCK_DGRAM(数据报套接字/无连接的套接字)。

    (3)、protocol 表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议。

    这种套接字称为 TCP 套接字:
    int tcp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //IPPROTO_TCP表示TCP协议
    这种套接字称为 UDP 套接字:
    int udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); //IPPROTO_UDP表示UDP协议

    也可以写成:
    int tcp_socket = socket(AF_INET, SOCK_STREAM, 0); //创建TCP套接字
    int udp_socket = socket(AF_INET, SOCK_DGRAM, 0); //创建UDP套接字

    bind()函数

    
        int bind(int sock, struct sockaddr *addr, socklen_t addrlen);
    
    

    sock 为 socket 文件描述符,addr 为 sockaddr 结构体变量的指针,addrlen 为 addr 变量的大小,可由 sizeof() 计算得出。

    
        int server_sockfd; //服务端套接字
        struct sockaddr_in my_add; //服务器网络地址结构体
        char buf[BUFSIZ]; //数据传送的缓冲区
        memset(&my_add,0,sizeof(my_add)); //数据初始化--清零
        my_add.sin_family = AF_INET; //设置为IP通信
        my_add.sin_addr.s_addr = INADDR_ANY; //服务器IP地址--允许连接到所有本地地址上
        my_add.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_add, sizeof(struct sockaddr))<0) {
            perror("bind");
            return 1;
        }
    
    

    sockaddr_in 结构体:

    
        struct sockaddr_in{
            sa_family_t     sin_family;   //地址族(Address Family),也就是地址类型
            uint16_t        sin_port;     //16位的端口号
            struct in_addr  sin_addr;     //32位IP地址
            char            sin_zero[8];  //不使用,一般用0填充
        };
    
    

    (1)、sin_family 和 socket() 的第一个参数的含义相同,取值也要保持一致。
    (2)、sin_prot 为端口号。
    (3)、sin_addr 是 struct in_addr 结构体类型的变量
    (4)、sin_zero[8] 是多余的8个字节,没有用,一般使用 memset() 函数填充为 0。

    in_addr 结构体

    
        struct in_addr{
            in_addr_t  s_addr;  //32位的IP地址
        };
    
    

    in_addr_t 在头文件 <netinet/in.h> 中定义,等价于 unsigned long,长度为4个字节。也就是说,s_addr 是一个整数,而IP地址是一个字符串,所以需要 inet_addr() 函数进行转换,例如:unsigned long ip = inet_addr("127.0.0.1");

    connect() 函数:connect() 函数用来建立连接

    
        int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen); 
    
    

    各个参数的说明和 bind() 相同。

    listen() 函数:通过 listen() 函数可以让套接字进入被动监听状态

    
        int listen(int sock, int backlog);
    
    

    sock 为需要进入监听状态的套接字,backlog 为请求队列的最大长度。
    请求队列:当套接字正在处理客户端的请求时,如果这个时候有新的请求进来,套接字是没法处理的,只能把它放在缓冲区,直到当前请求处理完毕后,再从缓冲区读取出来处理。如果不断有新的请求进来,它们就按照先后顺序在缓冲区中排队,直到缓冲区满。这个缓冲区就成为请求队列。缓冲区的大小可以通过listen()函数的backlog参数指定。如果设置为SOMAXCONN,就由系统来决定请求队列长度。
    listen() 只是让套接字处于监听状态,并没有接收请求。

    accept() 函数:通过 accept() 函数来接收客户端请求

    
        int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);·
    
    

    sock 为服务器端套接字,addr 为 sockaddr_in 结构体变量,addrlen 为参数 addr 的长度,可由 sizeof() 求得。
    accept() 会阻塞程序执行(后面代码不能被执行),直到有新的请求到来。

    write()函数:

    
        ssize_t write(int fd, const void *buf, size_t nbytes);
    
    

    fd 为要写入的文件的描述符,buf 为要写入的数据的缓冲区地址,nbytes 为要写入的数据的字节数。

    read()函数:

    
        ssize_t read(int fd, void *buf, size_t nbytes);
    
    

    fd 为要读取的文件的描述符,buf 为要接收数据的缓冲区地址,nbytes 为要读取的数据的字节数。

    相关文章

      网友评论

          本文标题:浅谈Linux C Socket编程

          本文链接:https://www.haomeiwen.com/subject/enxnbqtx.html