最简单的代码实现服务器和客户端

之前搭建服务器都是用的开源框架来实现,心中一直有不少的疑惑,操作系统到底是怎么实现服务器功能? 最近结合《详解TCP/IP》和《深入理解计算机系统》这两本书得以管中窥豹,突然间有种豁然开朗的感觉。
将我个人的理解以作笔记,都知道socket是网络通信的基石,用socket写了一个serverapp和clientapp。
其中客户端clientapp.c的代码如下:

#include <stdio.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <string.h>
#include <netinet/in.h>
#include <unistd.h>

int open_clientfd(char *hostname,char *port);
int main(int argc, char * argv[]) {
    int clientfd;
    
    if (argc != 3) {
        printf("must three params \n");
        return 0;
    }
    char *hostname = argv[1];
    char *port = argv[2];
    clientfd = open_clientfd(hostname,port);
    char str[64];
    if (clientfd < 0) {
        return 0;
    }
    read(clientfd,str,64);
    printf("%s", str);
    close(clientfd);
    return 0;
    
}

int open_clientfd(char *hostname, char *port)
{
    int clientfd, rc;
    struct addrinfo hints, *listp, *p;
    memset(&hints,0,sizeof(struct addrinfo));
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags |=  AI_ADDRCONFIG;
    hints.ai_flags = AI_NUMERICSERV;
    if ((rc = getaddrinfo(hostname, port, &hints, &listp)) != 0) {
        printf("getaddrinfo: failed\n");
        return -2;
    }
    
    for (p = listp; p ; p = p->ai_next) {
        if ((clientfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) {
            continue;/*socket faied try the next*/
        }
        
        if (connect(clientfd, p->ai_addr, p->ai_addrlen) != -1) {
            break;//Success
        }
        if (close(clientfd) < 0) {
            printf("open_lientfd: close failed\n");
            return -1;
        }
        
    }
    freeaddrinfo(listp);
    if (!p) { 
        printf("All connects faied \n");
        return -1;
    } else {
        return clientfd;
    }
}


服务器端serverapp.c的代码如下

#include <stdio.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <string.h>
#include <netinet/in.h>
#include <unistd.h>

int open_listenfd(char *port);
int main(int argc, const char * argv[]) {
    int listenfd, connfd = 0;
    socklen_t clientlen;
    listenfd = open_listenfd("8887");
    int connd = accept(listenfd, (struct sockaddr*)&clientlen, &clientlen);
    if (connfd < 1) {
        printf("appect error");
    }
    char str[] = "qiansheng \n";
    write(connd, str, sizeof(str));
    close(connfd);
    close(listenfd);
    return 0;
}


int open_listenfd(char *port) {
    
    struct addrinfo hints, *listp, *p;
    int listenfd = 0, optval = 1;
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; // 被动的  任何IP address
    hints.ai_flags |= AI_NUMERICSERV; // use port number
    getaddrinfo(NULL, port, &hints, &listp);
    
    for (p = listp; p ; p = p->ai_next) {
        if ((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) {
            continue;
        }
        
        setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&optval, sizeof(int));
        
        if (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0) {
            break;//Success
        }
        close(listenfd);
    }
    
    freeaddrinfo(listp);
    if (!p) {
        return -1;
    }
    if (listen(listenfd, 30) < 0) {
        close(listenfd);
        return -1;
    }
    return listenfd;
}

在终端使用gcc命令分别执行

gcc -g serverapp.c  -o  serverapp
gcc -g clientapp.c  -o  clientapp

在serverapp和clientapp可执行文件后面加上参数就可以运行了
以本机电脑为例

./serverapp 8887 /**为了避免跟系统端口重复,选个端口号大一点的*/
./clientapp 127.0.0.1 8887

终端就会打印我们想要的hello word了 其中getaddrinfo函数是核心,可重入,适用于任何协议。

int getaddrinfo(const char *host,const char *service, const struct addrinfo *hints, struct addrinfo **result);//如果成功返回0  错误则为非零的错误代码

getaddrinfo返回result,result 指向一个addrinfo结构的链表。其中每个结构指向一个对应于host和service的套接字地址结构如下图。

addrinfo

在调用getaddrinfo之后,会遍历这个列表,依次尝试每个套接字地址,直到调用socket和connect成功,建立连接。为了避免内存泄漏,最后需要调用freeaddrinfo函数,释放该链表。如果getaddrinfo返回非零的错误代码,程序可以调用gai_streeror将该代码转成消息字符串。

虽然很简单,服务器也是以这做为基石。对于前端同学了解服务器端的运行还是有很大的帮助。
多说两句,serverapp运行在本地,clientapp在局域网任何一台电脑上运行clientapp

./clientapp 172.32.42.42 8887  / **serverapp 所在局域网的IP地址*/

可能你会想,为什么只是局域网不是所有外网?当然也是可以运行外网,可是我们的外网IP地址都被路由器给屏蔽了呀,具体可参见NAT协议
最后吐槽下:在看网络相关的书籍的时候,总是搞不懂为什么会有如此多的协议,比如说有了IP地址为什么还要搞个mac地址,知乎上有这个问题,很多人的回答跟我猜想的一样,网络这个东西经过那么多人的改动,在不停的妥协和兼容下,越来越庞大,也越来越累赘,当然他们也是伟大的先知。

猜你喜欢

转载自blog.csdn.net/qianss520/article/details/89676396