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

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

使用tcp协议来实现来实现

实现效果

alt 实现效果.png

实现过程

对于服务器端:

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\n",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

猜你喜欢

转载自www.cnblogs.com/myguaiguai/p/12009314.html
今日推荐