Linux下SOCKET编程学习笔记



  • 初次学习网络编程,参考资料使用的是《UNIX环境高级编程》

    emmm...新人第一篇帖子,没什么技术含量,就记录一下自己的思路,各位大佬不要嘲笑我🌞
    这本书讲的太太太细致以至于我不能很好的把握要点(是因为我比较菜☹
    所以想记录一下学习过程理顺一下思路
    可以直接看一遍前面写的介绍然后对照后面我给出的程序例子来学习

    socket是什么

    套接字(socket)是一种通信机制,可以利用他在设备之间通信,收发信息。

    编写socket程序的步骤

    • 服务器server:
      创建套接字
      把套接字和ip、端口绑定
      监听
      服务器等待接受客户连接
      发送或接收消息
      关闭套接字
    • 客户client:
      创建套接字
      把套接字和ip、端口绑定
      客户请求连接服务器
      发送或接收信息
      关闭套接字

    一、创建套接字

    调用socket函数来创建套接字

    #include<sys/socket.h>
    int socket(int domain, int type, int protocol);
    

    1.domain(协议族)包括AF_UNIX、AF_INET
    前者是通过文件系统实现的本地套接字,后者是用于网络套接字
    2.type(套接字的通信类型)包括SOCK_STREAM、SOCK_DGRAM
    SOCK_STREAM 流套接字,基于TCP,可靠、有序
    SOCK_DGRAM 数据报套接字,基于UDP,不可靠,无序
    第二种我没有用过,字面上看起来流套接字好像更YX一点
    3.protocol(为套接字指定一种协议)
    目前遇到的情况,对于AF_UNIX、AF_INET使用默认值 0 就行

    二、把套接字和ip、端口绑定

    将该套接字关联一个IP地址和端口号

    #include<sys/socket.h>
    int bind(int socket, const struct sockaddr *address, size_t address_len);
    

    可以看到这里的address是一个结构体,就是通过这个结构把ip端口与套接字关联的,
    address_len则是地址的长度
    套接字的地址由结构体指定,对于AF_INET用到的是下面的结构

    struct sockaddr_in {
        short int sin_family;         // AF_INET
        unsigned short int sin_port;  // 端口号
        struct in_addr  sin_addr;     // IP地址
    };
    

    看到这里你会发现bind函数里面的地址信息结构体与我们用到的sockaddr_in不一致,我们只需要把它强制转换成sockaddr就可以了,笔记末尾会附上实例。

    三、监听

    #include<sys/socket.h>
    int listen(int socket, int backlog);
    

    1.socket就是你创建的套接字
    2.backlog是等待处理的连接个数,最大不能超过这个数

    四、服务器接收客户连接

    #include<sys/socket.h>
    int accept(int socket, struct sockaddr *address, socklen_t *address_len);
    

    accept是阻塞式的,如果没有客户请求连接,它就会一直等待请求,只有当客户发生请求的时候才会返回
    如果收到连接请求将会返回一个新的套接字描述符,与之前创建的套接字类型相同都是SOCK_STREAM套接字。
    返回的新套接字称为已连接套接字,原本用来监听的称为监听套接字
    实际函数会修改address结构的内容,获得客户的ip信息,所以你只需要新建一个结构传进去就可以了

    五、客户请求连接服务器

    #include<sys/socket.h>
    int connect(int socket, const struct sockaddr *address, site_t address_len);
    

    1.socket 就是 client 套接字
    2.address 就是服务器套接字
    3.address_len 指定address指向的结构的长度

    connect就是将socket(client)连接到address(server),如果调用connect以后不能立刻连接,connect会
    阻塞一段不确定的时间,超过这个时间后连接将会失败

    六、利用套接字收发数据

    由于套接字和文件的行为类似,被抽象成文件(文件描述符所表示的一个io对象,不是文件系统里的文件)
    如:
    客服发送数据

    const int MAXBUF = 256;
    char buffer[MAXBUF];
    strcpy(buffer) = "hello world";
    int nbytes = write(client_fd, buffer, sizeof(buffer));
    

    服务器接收数据

    const int MAXBUF = 256;
    char buffer[MAXBUF];
    int nbytes = read(client_fd, buffer, MAXBUF);  
    

    七、关闭连接

    #include<unistd.h>
    int close(int socket);
    

    对于多进程并发服务器,close只是导致套接字描述符的引用计数-1,如果引用计数值仍大于0,
    该次close调用不会引发TCP终止连接的四次挥手过程。

    附录:程序实例

    在自己的电脑上同时跑两个程序,实现一台设备的自发自收。

    服务器程序:

    #include <stdio.h>
    #include <sys/socket.h>
    #include <stdlib.h>
    #include <string.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    int main() {
        int server_fd;
        // 创建套接字
        server_fd = socket(AF_INET, SOCK_STREAM, 0);
        if(server_fd < 0){
            printf("Cannot creat socket.");
            return 0;
        }
        else{
            printf("Creat socket fd: %d\n",server_fd);
        }
        // 将套接字与ip和端口关联
        struct sockaddr_in myaddr;
        memset((void *)&myaddr, 0, sizeof(myaddr));
        myaddr.sin_family = AF_INET;
        myaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        myaddr.sin_port = htons(6240);
        if(bind(server_fd, (struct sockaddr *)&myaddr, sizeof(myaddr)) < 0){
            perror("Bind failed.");
            return 0;
        }
        printf("bind complete, port number : %d\n", ntohs(myaddr.sin_port));
    
        // 监听
        if (listen(server_fd, 5) < 0){
            perror("listen failed.");
            return  0;
        }
        else {
            printf("Socket listen, server_fd: %d\n",server_fd);
        }
    
        //服务器等待客户连接
        const int MAXBUF = 256;
        char buffer[MAXBUF];
        struct sockaddr_in client_addr;
        int client_addr_len = sizeof(client_addr);
        int client_fd;
        client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len);
        printf("accept client, client fd: %d, ip: %s, port %d \n",client_fd,
               inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
        while(1){
            int nbytes = read(client_fd, buffer, MAXBUF);
            printf("read from client, bytes: %d, data: %s\n",nbytes, buffer);
    
        }
        close(server_fd);
    
    
        return 0;
    }
    

    客户端程序:

    #include <stdio.h>
    #include <sys/socket.h>
    #include <unistd.h>
    #include <string.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    int main() {
        //创建套接字
        int client_fd = socket(AF_INET, SOCK_STREAM, 0);
        if(client_fd < 0){
            perror("cannot creat socket.");
            return 0;
        }
        else{
            printf("created client_fd: %d \n",client_fd);
        }
    
    
        //将套接字与ip、端口关联
        struct sockaddr_in client_addr;
        memset((char *)&client_addr, 0, sizeof(client_addr));
        client_addr.sin_family = AF_INET;
        client_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        client_addr.sin_port = htons(0);
        if(bind(client_fd, (struct sockaddr *)&client_addr,
                sizeof(client_addr)) < 0){
            perror("bind failed.");
            return 0;
        }
    
        //构造服务器地址
        const  char *server = "127.0.0.1";
        struct sockaddr_in server_addr;
        memset((char *)&server_addr, 0, sizeof(server_addr));
        server_addr.sin_family = AF_INET;
        inet_aton(server, &server_addr.sin_addr);
        server_addr.sin_port = htons(6240);
    
        //请求服务器连接
        int result = connect(client_fd,(struct sockaddr *)&server_addr,
                             sizeof(server_addr));
        if(result < 0){
            perror("connect failed");
        }
        else{
            printf("connect succsess\n");
        }
    
    
        //发送数据
        const int MAXBUF = 256;
        char buffer[MAXBUF];
        strcpy(buffer,"hello tcp!");
        int nbytes = write(client_fd, buffer, sizeof(buffer));
        printf("write to server,bytes: %d, data: %s\n",nbytes,buffer);
        while(1){
            printf("请输入传送信息:");
            scanf("%s",&buffer);
            nbytes = write(client_fd, buffer, sizeof(buffer));
            printf("write to server,bytes: %d, data: %s\n",nbytes,buffer);
    
        }
        //关闭套接字
    
        close(client_fd);
        return 0;
    }
    


  • 有些说法不太对啊,命名套接字和创建套接字队列这两条,bind 是绑定,listen 是监听,哪来的命名和创建队列之说。
    (虽然说 listen 的确会创建队列,但是和你理解的队列可能有些出入。)



  • 而且客户端也不一定要绑定的,你的程序没有指定任何ip和port,所以你写的那段绑定代码没有任何作用



  • 套接字其实是利用文件,这个说法也不恰当,应该说套接字的行为与文件类似,所以被抽象成文件,这里说的文件是指文件描述符所表示的一个io对象,与文件系统里的文件是两个概念。套接字与文件的区别在于,文件描述符的对端,一个是网络,一个是文件系统。



  • @wqy 奥对,这个说法的确有问题,啊受教啦!



  • @wqy 恩恩,初次学习,理解上有些偏差,谢谢学长指导😃



  • 第一次学习网络编程,有一些地方理解有误,幸亏有学长指出,呀,我们的bbs真是个好地方,如果不放到这里可能我永远都是这么理解下去了,感谢大家!



  • @nijunpei 把错误总结修改下哦,这样收获会更大


 

Copyright © 2018 bbs.dian.org.cn All rights reserved.

与 Dian 的连接断开,我们正在尝试重连,请耐心等待