zoukankan      html  css  js  c++  java
  • c语言实现简单的hello/hi聊天程序

    c语言实现简单的hello/hi程序

    使用tcp协议来实现来实现

    实现效果

    实现效果

    实现过程

    对于服务器端:

    1.定义sockadr_in结构体

    struct sockaddr_in add={
    	.sin_family=AF_INET,
    	.sin_port=htons(8000),
    	.sin_addr.s_addr=htonl(INADDR_ANY),
    };
    

    2.初始化:
    sin_family表示协议簇,一般用AF_INET表示TCP/IP协议
    sin_port端口为8000
    sin_addr将本地所有的ip都绑定到地址

    3.通过soket获取文件标识符sock_fd=socket(PF_INET,SOCK_STREAM,0)

    4.将sockaddr_in结构体与sock_fd标识符绑定
    bind(sock_fd,(struct sockaddr*)&add,sizeof(struct sockaddr)

    5.监听端口,如果有链接就能响应
    ret=listen(sock_fd,MAX_QUEUE_SIZE))
    接收请求,同时获取请求方的套接字,通过这个套接字能进行数据的收发
    accept(sock_fd,(struct sockaddr*)&addNew,&sin_size)

    对于客户端

    1.初始化套接字

    struct sockaddr_in server;
    memset(&server,0,sizeof(struct sockaddr_in ));
    server.sin_family = AF_INET;
    server.sin_port =8000;
    server.sin_addr.s_addr = inet_addr("127.0.0.1");
    

    与服务端的套接字不同,这里初始化的套接字表示服务端的套接字,即客户端要向谁请求服务,就应该将套接字初始化为对应的服务端。
    这里定义端口为8000,因为服务端的端口设置为8000,ip地址为127.0.0.1,即回环地址,因为实验在一台主机上完成,服务端也布置在本主机,所以请求服务时的目的地址就是本机的地址。

    2.获取文件描述符

    sockfd = socket( AF_INET, SOCK_STREAM,0)
    

    通过socket接口,定义服务类型为TCP服务,返回文件描述符,有了文件描述符与套接字,我们就能请求服务了。

    3.连接服务端
    connect( sockfd,(struct sockaddr*)&server,sizeof( server ))
    connet函数传递了三个参数:文件描述符,服务端的套接字结构体,套接字的大小,这个函数会根据套接字尝试服务端。如果服务端此时已经执行过了listen,那么服务端就能相应这个请求,并进行三次握手,从此,服务端于客户端的连接就建立完成了。

    4.发送与接收消息

    send(client_sockfd,buf,len,0)
    recv(client_sockfd,buf,BUFSIZ,0);
    

    对于发送消息与接收消息,服务端与客户端没有区别,其实对于接收和发送消息,read与write操作也能实现。
    由于已经建立了连接,所以发送和接收时并不需要目标机的套接字,只需要传递需要发送的数据或者接收数据的缓冲区,自己的文件描述符即可!

    代码实现

    客户端

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #define MAX_len 1024
    int sock_fd;
    struct sockaddr_in add; 
    int main()
    {
            int ret;
            char buf[MAX_len]={0};
            char buf_rec[MAX_len]={0};
            char buf_p[5]={"0"};
            memset(&add,0,sizeof(add));
            add.sin_family=AF_INET;
            add.sin_port=htons(8000);
            add.sin_addr.s_addr=inet_addr("127.0.0.1");
    
            if((sock_fd=socket(PF_INET,SOCK_STREAM,0))<=0)
            {
                    
                    perror("socket");
                    return 1;
            }     
            if((ret=connect(sock_fd,(struct sockaddr*)& add,sizeof(struct sockaddr)))<0)
            {
                    perror("connet");
                    return 1;
            }
            if((ret=send(sock_fd,(void*)buf_p,strlen(buf),0))<0)
            {
                    perror("recvfrom");
                    return 1;
            }  
            while (1)
            {
                    scanf("%s",buf);
                    if((ret=send(sock_fd,(void*)buf,sizeof(buf),0))<0)
                    {
                            perror("sendfrom1");
                            return 1;
                    }
                    if((ret=recv(sock_fd,(void*)buf_rec,sizeof(buf_rec),0))<0)
                    {                
                            perror("recvfrom1");
                            return 1;
                            
                    }
                    printf("%s
    ",buf_rec);
            }
            return 0;
    }
    
    

    服务端

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <stdlib.h>
    #include <string.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #define MAX_len 1024
    #define MAX_QUEUE_SIZE 10
    #define MAX_CON 10
    
    void dealData(int cilient)
    {
        char buf[1024];
        char buf_p[5]={"hi"};
        int des_fd,len=0;
        while( 1 ) {
            memset(buf,0,sizeof(buf));
            len=recv(cilient,(void*)buf,sizeof(buf),0);
            send(cilient,buf_p,strlen(buf_p),0);   
        }  
    }
    int main()
    {
        int len=0;
        int sock_fd_work;
        int cilient;
        int sin_size=sizeof(struct sockaddr_in);
        int sock_fd;
        char buf_p[5];
        char buf[100];
        int ret,current=-1,des;
        struct sockaddr_in addNew;
        struct sockaddr_in add
        ={
                .sin_family=AF_INET,
                .sin_port=htons(8000),
                .sin_addr.s_addr=htonl(INADDR_ANY),
        };
        if((sock_fd=socket(PF_INET,SOCK_STREAM,0))<0)
        {
            perror("socket");
            return 1;
        }
    	if((ret=bind(sock_fd,(struct sockaddr*)&add,sizeof(struct sockaddr)))<0)
        {
            perror("bind");
            return 1;
        }
        if((ret=listen(sock_fd,MAX_QUEUE_SIZE))<0)
        {
            perror("listen");
            return 1;
        } 
        while(1)
        {
            if((sock_fd_work=accept(sock_fd,(struct sockaddr*)&addNew,&sin_size))>0)
                if(!fork())
                {
                    dealData(sock_fd_work);
                    exit(0);
                }
        }
        close(sock_fd_work);
        return 0;        /* code */
    
    }
    

    实现原理

    TCP模型.png

    跟踪分析

    在linux中,socket也被看做是文件,也正是因为如此,对文件的readwrite操作也能对socket使用,进一步来讲,内核实现socket与实现其他文件系统基本类似,也就是说,socket也是一个文件系统。
    作为一个linux下的文件系统,我们关注的数据结构有:
    超级块

    struct super_block {
    	...
    	struct file_system_type *s_type;
    	struct super_operations *s_op;
    	...
    }
    

    显然,file_system_type为文件系统的类型,socket文件系统对应的内容为

    static struct file_system_type sock_fs_type ={
       .name ="sockfs",// 文件系统名称
       .mount = sockfs_mount,// 挂载sockfs函数,其中会创建super block
       .kill_sb = kill_anon_super,// 销毁super block函数
    };
    

    里面的内容其实很少,名称、挂载操作,还有销毁操作。
    那么struct super_operations呢? 这里面保存了一些文件系统在创建或删除文件时会用到的操作

    static const struct super_operations sockfs_ops ={
        .alloc_inode = sock_alloc_inode,// 分配inode
        .destroy_inode = sock_destroy_inode,// 释放inode
        .statfs = simple_statfs,// 用于获取sockfs文件系统的状态信息
    };
    

    由于linux将socket也当作一个文件对待,那么在新建一个连接(执行socket())时,应该也会创建一个文件才对,一个磁盘文件对应了一个inode,也就是每执行一次socket,都会执行sock_alloc_inode操作,然后创建一个inode,然后创建一个file文件,将file文件指向那个inode,然后返回文件描述符。

    验证

    若执行socket(PF_INET,SOCK_STREAM,0),就会执行系统调用 sys_socketcall,实际上就是SYSCALL_DEFINE2

    SYSCALL_DEFINE2(socketcall,int, call,unsignedlong __user *, args)
    {
        switch(call){
            case SYS_SOCKET:
            // 与 socket(int domain, int type, int protocol) 对应,创建socket
            err = sys_socket(a0, a1, a[2]);
            break;
            case SYS_BIND:
            err = sys_bind(a0,(struct sockaddr __user *)a1, a[2]);
            break;
            case SYS_CONNECT:
           err = sys_connect(a0,(struct sockaddr __user *)a1, a[2]);
            break;
    }                    
    

    从这里的代码可以看到,sys_socketcall是几乎所有socket操作的入口(socket、bind、connect、listen),每次执行都会根据系统调用号SYS_SOCKET来判断用户程序的请求,进而执行对应的系统调用:sys_socket、sys_bind、sys_connet
    sys_socket:内核将sys_socket重定位为SYSCALL_DEFINE3

    SYSCALL_DEFINE3(socket,int, family,int, type,int, protocol)
    {
        ...
        retval = sock_create(family, type, protocol,&sock);
        ....
        retval = sock_map_fd(sock, flags &(O_CLOEXEC | O_NONBLOCK));
    }
    

    对于sock_create内核会分配一个socket结构体,结构体内包括socket的操作,等待队列(监听会用到)等

    struct socket {
        socket_state state;// 连接状态:SS_CONNECTING, SS_CONNECTED 等
        short type;// 类型:SOCK_STREAM, SOCK_DGRAM 等
        unsignedlong flags;// 标志位:SOCK_ASYNC_NOSPACE(发送队列是否已满)等
        struct socket_wq __rcu *wq;// 等待队列
        struct file *file;// 该socket结构体对应VFS中的file指针
        struct sock *sk;// socket网络层表示,真正处理网络协议的地方
        conststruct proto_ops *ops;// socket操作函数集:bind, connect,     accept 等
    };
    

    sock_create的内容:

    int __sock_create(struct net *net,int family,int type,int protocol,
            struct socket **res,int kern)
    {
        ...
        err = security_socket_create(family, type, protocol, kern);
        sock = sock_alloc();
        ...
    }
    

    security_socket_create函数主要是是判断了传递的参数是否合法,在用户态创建还是内核空间创建,它的实现如下:

    static struct socket *sock_alloc(void)
    {
        struct inode *inode;
        struct socket *sock;
        inode = new_inode_pseudo(sock_mnt->mnt_sb);
        sock = SOCKET_I(inode);
        ....
    }
    

    new_inode_pseudo调用了socket文件系统的分配节点操作alloc_inode,最终实现了socket节点的创建

    static struct inode *alloc_inode(struct super_block *sb)
    {
        if(sb->s_op->alloc_inode)
        inode = sb->s_op->alloc_inode(sb);
        ...
    }
    

    这里的sb就是socket的超级块,调用的alloc_inode,正是在初始化文件系统时赋值给超级块sockfs_ops
    域的函数指针,这也部分验证了我们的猜想,那打开的文件呢?显然就是由系统调用中sock_map_fd来完成。
    对于sock_map_fd,内核会创建一个文件,并将socket与文件绑定起来,同时会分配文件描述符,也就是socket的返回值,通过文件描述符,既能将socket看着文件来对待,又能执行socket特有的函数。

    staticint sock_map_fd(struct socket *sock,int flags)
    {
        struct file *newfile;
        // 从本进程的文件描述符表中获取一个可用的文件描述符
        int fd = get_unused_fd_flags(flags);、
        // 创建一个新的file,并将file和inode以及socket关联
        newfile = sock_alloc_file(sock, flags, NULL);
    }
    

    这一步通过get_unused_fd_flags得到了文件描述符,sock_alloc_file负责创建对应的文件

    struct file *sock_alloc_file(struct socket *sock,int             
                                               flags,constchar*dname)
    {
        struct file *file;
        file = alloc_file(&path, FMODE_READ | FMODE_WRITE, &socket_file_ops);
        file->private_data = sock;
    }
    

    清晰的看到,文件被创建、得到文件指针、将socket文件操作赋值给file、将scket结构体sock赋值到file->private——data。这一步完成,我们就能将socket看做一个文件了。

    验证完毕

    参考:https://blog.csdn.net/qq_14978113/article/details/80738787

  • 相关阅读:
    MySQL管理
    MySQL触发器
    板块龙头与行业龙头
    货币宽松结束
    调整
    职业操盘手不传之秘:识别平台突破的技巧原则详解---赶牛寻渔
    chan
    2017板块轮动参考
    各种建筑风格及其代表建筑
    3分钟看懂各种建筑结构优劣
  • 原文地址:https://www.cnblogs.com/myguaiguai/p/12009314.html
Copyright © 2011-2022 走看看